Skip to content

Commit 07d6058

Browse files
authored
Merge pull request #71 from ipinfo/silvano/eng-514-add-core-bundle-support-in-ipinfolaravel-library
Add support for Core bundle
2 parents a595a47 + e946655 commit 07d6058

File tree

5 files changed

+347
-1
lines changed

5 files changed

+347
-1
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"keywords": ["Laravel", "ipinfolaravel"],
1515
"require": {
1616
"illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
17-
"ipinfo/ipinfo": "^3.2.0"
17+
"ipinfo/ipinfo": "^3.3.0"
1818
},
1919
"require-dev": {
2020
"phpunit/phpunit": "^12.0",

config/ipinfocorelaravel.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
return [
4+
//
5+
];

src/core/ipinfocorelaravel.php

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
namespace ipinfo\ipinfolaravel\core;
4+
5+
use Closure;
6+
use ipinfo\ipinfo\IPinfoCore as IPinfoCoreClient;
7+
use ipinfo\ipinfolaravel\DefaultCache;
8+
use ipinfo\ipinfolaravel\iphandler\DefaultIPSelector;
9+
10+
class ipinfocorelaravel
11+
{
12+
/**
13+
* IPinfo API access token.
14+
* @var string
15+
*/
16+
public $access_token = null;
17+
18+
/**
19+
* IPinfo client object settings.
20+
* @var array
21+
*/
22+
public $settings = [];
23+
24+
/**
25+
* Return true to skip IPinfo lookup, otherwise return false.
26+
* @var function
27+
*/
28+
public $filter = null;
29+
30+
/**
31+
* Provides ip.
32+
* @var ipinfo\ipinfolaravel\iphandler\IPHandlerInterface
33+
*/
34+
public $ip_selector = null;
35+
36+
const CACHE_MAXSIZE = 4096;
37+
const CACHE_TTL = 60 * 24;
38+
39+
/**
40+
* Handle an incoming request.
41+
* @param \Illuminate\Http\Request $request
42+
* @param \Closure $next
43+
* @return mixed
44+
*/
45+
public function handle($request, Closure $next)
46+
{
47+
$this->configure();
48+
49+
if ($this->filter && call_user_func($this->filter, $request)) {
50+
$details = null;
51+
} else {
52+
try {
53+
$details = $this->ipinfo->getDetails(
54+
$this->ip_selector->getIP($request),
55+
);
56+
} catch (\Exception $e) {
57+
$details = null;
58+
59+
// users can't catch this exception with their own wrapper
60+
// middleware unfortunately, so we catch it for them. but for
61+
// backwards-compatibility, we throw the exception again unless
62+
// they've told us not to.
63+
if ($this->no_except != true) {
64+
throw $e;
65+
}
66+
}
67+
}
68+
69+
$request->attributes->set("ipinfo", $details);
70+
71+
return $next($request);
72+
}
73+
74+
/**
75+
* Determine settings based on user-defined configs or use defaults.
76+
*/
77+
public function configure()
78+
{
79+
$this->access_token = config("services.ipinfo.access_token", null);
80+
$this->filter = config("services.ipinfo.filter", [
81+
$this,
82+
"defaultFilter",
83+
]);
84+
$this->no_except = config("services.ipinfo.no_except", false);
85+
$this->ip_selector = config(
86+
"services.ipinfo.ip_selector",
87+
new DefaultIPSelector(),
88+
);
89+
90+
if (
91+
$custom_countries = config("services.ipinfo.countries_file", null)
92+
) {
93+
$this->settings["countries_file"] = $custom_countries;
94+
}
95+
96+
if ($custom_cache = config("services.ipinfo.cache", null)) {
97+
$this->settings["cache"] = $custom_cache;
98+
} else {
99+
$maxsize = config(
100+
"services.ipinfo.cache_maxsize",
101+
self::CACHE_MAXSIZE,
102+
);
103+
$ttl = config("services.ipinfo.cache_ttl", self::CACHE_TTL);
104+
$this->settings["cache"] = new DefaultCache($maxsize, $ttl);
105+
}
106+
107+
$this->ipinfo = new IPinfoCoreClient(
108+
$this->access_token,
109+
$this->settings,
110+
);
111+
}
112+
113+
/**
114+
* Should IP lookup be skipped.
115+
* @param Request $request Request object.
116+
* @return bool Whether or not to filter out.
117+
*/
118+
public function defaultFilter($request)
119+
{
120+
$user_agent = $request->header("user-agent");
121+
if ($user_agent) {
122+
$lower_user_agent = strtolower($user_agent);
123+
124+
$is_spider = strpos($lower_user_agent, "spider") !== false;
125+
$is_bot = strpos($lower_user_agent, "bot") !== false;
126+
127+
return $is_spider || $is_bot;
128+
}
129+
130+
return false;
131+
}
132+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace ipinfo\ipinfolaravel\core;
4+
5+
use Illuminate\Support\ServiceProvider;
6+
7+
class ipinfocorelaravelServiceProvider extends ServiceProvider
8+
{
9+
/**
10+
* Perform post-registration booting of services.
11+
* @return void
12+
*/
13+
public function boot()
14+
{
15+
// Publishing is only necessary when using the CLI.
16+
if ($this->app->runningInConsole()) {
17+
$this->bootForConsole();
18+
}
19+
}
20+
21+
/**
22+
* Register any package services.
23+
* @return void
24+
*/
25+
public function register()
26+
{
27+
$this->mergeConfigFrom(
28+
__DIR__ . "/../../config/ipinfocorelaravel.php",
29+
"ipinfocorelaravel",
30+
);
31+
32+
// Register the service the package provides.
33+
$this->app->singleton(
34+
"ipinfocorelaravel",
35+
fn($app) => new ipinfocorelaravel(),
36+
);
37+
}
38+
39+
/**
40+
* Get the services provided by the provider.
41+
* @return array
42+
*/
43+
public function provides()
44+
{
45+
return ["ipinfocorelaravel"];
46+
}
47+
48+
/**
49+
* Console-specific booting.
50+
* @return void
51+
*/
52+
protected function bootForConsole()
53+
{
54+
// Publishing the configuration file.
55+
$this->publishes(
56+
[
57+
__DIR__ . "/../../config/ipinfocorelaravel.php" => config_path(
58+
"ipinfocorelaravel.php",
59+
),
60+
],
61+
"ipinfocorelaravel.config",
62+
);
63+
}
64+
}

tests/IpinfocorelaravelTest.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
namespace ipinfo\ipinfolaravel\Tests;
3+
4+
use Orchestra\Testbench\TestCase;
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Response;
7+
use ipinfo\ipinfo\IPinfoCore as IPinfoCoreClient;
8+
use ipinfo\ipinfolaravel\iphandler\IPHandlerInterface;
9+
use ipinfo\ipinfolaravel\core\ipinfocorelaravel;
10+
11+
class IpinfocorelaravelTest extends TestCase
12+
{
13+
protected function getPackageProviders($app)
14+
{
15+
return [
16+
\ipinfo\ipinfolaravel\core\ipinfocorelaravelServiceProvider::class,
17+
];
18+
}
19+
20+
/** Create a middleware with injected mocks, stubbing configure() */
21+
protected function makeMiddleware(
22+
$client,
23+
$selector,
24+
$filter = null,
25+
$noExcept = false,
26+
) {
27+
$mw = $this->getMockBuilder(ipinfocorelaravel::class)
28+
->onlyMethods(["configure"])
29+
->getMock();
30+
$mw->method("configure")->willReturn(null);
31+
$mw->ipinfo = $client;
32+
$mw->ip_selector = $selector;
33+
$mw->filter = $filter;
34+
$mw->no_except = $noExcept;
35+
return $mw;
36+
}
37+
38+
public function test_handle_merges_details_on_success()
39+
{
40+
$details = (object) ["country" => "US", "ip" => "8.8.8.8"];
41+
$client = $this->createMock(IPinfoCoreClient::class);
42+
$client
43+
->expects($this->once())
44+
->method("getDetails")
45+
->with("8.8.8.8")
46+
->willReturn($details);
47+
48+
$selector = $this->createMock(IPHandlerInterface::class);
49+
$selector->method("getIP")->willReturn("8.8.8.8");
50+
51+
$mw = $this->makeMiddleware($client, $selector);
52+
53+
$request = Request::create("/foo", "GET");
54+
$next = function ($req) use (&$out) {
55+
$out = $req->get("ipinfo");
56+
return new Response("OK", 200);
57+
};
58+
59+
$resp = $mw->handle($request, $next);
60+
61+
$this->assertSame($details, $out);
62+
$this->assertEquals(200, $resp->getStatusCode());
63+
$this->assertEquals("OK", $resp->getContent());
64+
}
65+
66+
public function test_handle_skips_lookup_when_filter_true()
67+
{
68+
$client = $this->createMock(IPinfoCoreClient::class);
69+
$client->expects($this->never())->method("getDetails");
70+
71+
$selector = $this->createMock(IPHandlerInterface::class);
72+
$selector->expects($this->never())->method("getIP");
73+
74+
$filter = fn($req) => true;
75+
$mw = $this->makeMiddleware($client, $selector, $filter);
76+
77+
$request = Request::create("/", "GET");
78+
$request->headers->set("user-agent", "GoogleBot");
79+
80+
$next = function ($req) use (&$out) {
81+
$out = $req->get("ipinfo");
82+
return new Response();
83+
};
84+
85+
$mw->handle($request, $next);
86+
$this->assertNull($out);
87+
}
88+
89+
public function test_handle_throws_if_client_throws_and_no_except_false()
90+
{
91+
$this->expectException(\Exception::class);
92+
93+
$client = $this->createMock(IPinfoCoreClient::class);
94+
$client
95+
->method("getDetails")
96+
->willThrowException(new \Exception("boom"));
97+
98+
$selector = $this->createMock(IPHandlerInterface::class);
99+
$selector->method("getIP")->willReturn("1.1.1.1");
100+
101+
$mw = $this->makeMiddleware($client, $selector, null, false);
102+
$mw->handle(Request::create("/", "GET"), fn($r) => new Response());
103+
}
104+
105+
public function test_handle_swallows_if_client_throws_and_no_except_true()
106+
{
107+
$client = $this->createMock(IPinfoCoreClient::class);
108+
$client
109+
->method("getDetails")
110+
->willThrowException(new \Exception("boom"));
111+
112+
$selector = $this->createMock(IPHandlerInterface::class);
113+
$selector->method("getIP")->willReturn("1.1.1.1");
114+
115+
$mw = $this->makeMiddleware($client, $selector, null, true);
116+
117+
$next = function ($req) use (&$out) {
118+
$out = $req->get("ipinfo");
119+
return new Response();
120+
};
121+
122+
$mw->handle(Request::create("/", "GET"), $next);
123+
$this->assertNull($out);
124+
}
125+
126+
public function test_defaultFilter_detects_bots_and_spiders()
127+
{
128+
$mw = new ipinfocorelaravel();
129+
130+
$r1 = Request::create("/", "GET");
131+
$r1->headers->set("user-agent", "MySpider");
132+
$this->assertTrue($mw->defaultFilter($r1));
133+
134+
$r2 = Request::create("/", "GET");
135+
$r2->headers->set("user-agent", "someBOT/2.0");
136+
$this->assertTrue($mw->defaultFilter($r2));
137+
138+
$r3 = Request::create("/", "GET");
139+
$r3->headers->set("user-agent", "normal");
140+
$this->assertFalse($mw->defaultFilter($r3));
141+
142+
$r4 = Request::create("/", "GET");
143+
$this->assertFalse($mw->defaultFilter($r4));
144+
}
145+
}

0 commit comments

Comments
 (0)