Skip to content

Commit 5a68ef8

Browse files
Created Middleware to Verify Scopes and reauthenticate if required (#187)
1 parent 444ef60 commit 5a68ef8

File tree

6 files changed

+162
-0
lines changed

6 files changed

+162
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,5 @@ fabric.properties
7373

7474
# Android studio 3.1+ serialized cache file
7575
.idea/caches/build_file_checksums.ser
76+
/.idea/codeception.xml
77+
/.idea/phpspec.xml

src/Http/Middleware/VerifyScopes.php

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Osiset\ShopifyApp\Http\Middleware;
4+
5+
use Closure;
6+
use Exception;
7+
use Illuminate\Http\Request;
8+
use Osiset\ShopifyApp\Contracts\ShopModel as IShopModel;
9+
use Osiset\ShopifyApp\Util;
10+
11+
class VerifyScopes
12+
{
13+
/**
14+
* Checks if a shop has all required access scopes.
15+
* If a required access scope is missing, it will redirect the app
16+
* for re-authentication
17+
*
18+
* @param Request $request The request object.
19+
* @param Closure $next The next action.
20+
*
21+
* @throws Exception
22+
*
23+
* @return mixed
24+
*/
25+
public function handle(Request $request, Closure $next)
26+
{
27+
/** @var $shop IShopModel */
28+
$shop = auth()->user();
29+
$scopesResponse = $shop->api()->rest('GET', '/admin/oauth/access_scopes.json');
30+
if ($scopesResponse && $scopesResponse['errors']) {
31+
return $next($request);
32+
}
33+
$scopes = json_decode(json_encode($scopesResponse['body']['access_scopes']), false);
34+
$scopes = array_map(static function ($scope) {
35+
return $scope->handle;
36+
}, $scopes);
37+
38+
$requiredScopes = explode(',', Util::getShopifyConfig('api_scopes'));
39+
$missingScopes = array_diff($requiredScopes, $scopes);
40+
if (count($missingScopes) === 0) {
41+
return $next($request);
42+
}
43+
44+
return redirect()->route(
45+
Util::getShopifyConfig('route_names.authenticate'),
46+
[
47+
'shop' => $shop->getDomain()->toNative(),
48+
'host' => $request->get('host'),
49+
]
50+
);
51+
}
52+
}

src/ShopifyAppProvider.php

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Osiset\ShopifyApp\Http\Middleware\AuthWebhook;
3434
use Osiset\ShopifyApp\Http\Middleware\Billable;
3535
use Osiset\ShopifyApp\Http\Middleware\IframeProtection;
36+
use Osiset\ShopifyApp\Http\Middleware\VerifyScopes;
3637
use Osiset\ShopifyApp\Http\Middleware\VerifyShopify;
3738
use Osiset\ShopifyApp\Macros\TokenRedirect;
3839
use Osiset\ShopifyApp\Macros\TokenRoute;
@@ -318,6 +319,7 @@ private function bootMiddlewares(): void
318319
$this->app['router']->aliasMiddleware('auth.webhook', AuthWebhook::class);
319320
$this->app['router']->aliasMiddleware('billable', Billable::class);
320321
$this->app['router']->aliasMiddleware('verify.shopify', VerifyShopify::class);
322+
$this->app['router']->aliasMiddleware('verify.scopes', VerifyScopes::class);
321323

322324
$this->app->booted(function () {
323325
$this->app['router']->pushMiddlewareToGroup('web', IframeProtection::class);
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Osiset\ShopifyApp\Test\Http\Middleware;
4+
5+
use Illuminate\Auth\AuthManager;
6+
use Illuminate\Http\Request;
7+
use Osiset\ShopifyApp\Http\Middleware\VerifyScopes as VerifyScopesMiddleware;
8+
use Osiset\ShopifyApp\Test\Stubs\Api as ApiStub;
9+
use Osiset\ShopifyApp\Test\TestCase;
10+
11+
class VerifyScopesTest extends TestCase
12+
{
13+
/**
14+
* @var AuthManager
15+
*/
16+
protected $auth;
17+
18+
public function setUp(): void
19+
{
20+
parent::setUp();
21+
$this->auth = $this->app->make(AuthManager::class);
22+
}
23+
24+
public function testMissingScopes(): void
25+
{
26+
// Setup API stub
27+
$this->setApiStub();
28+
ApiStub::stubResponses(['access_scopes']);
29+
30+
$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products,read_orders');
31+
32+
$shop = factory($this->model)->create();
33+
$this->auth->login($shop);
34+
35+
$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);
36+
37+
// Run the middleware
38+
$middleware = new VerifyScopesMiddleware();
39+
$result = $middleware->handle($request, function () {
40+
});
41+
42+
//this line needs to assert if proper redirect was made
43+
$this->assertEquals(302, $result->getStatusCode());
44+
}
45+
46+
public function testMatchingScopes(): void
47+
{
48+
// Setup API stub
49+
$this->setApiStub();
50+
ApiStub::stubResponses(['access_scopes']);
51+
52+
$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products');
53+
54+
$shop = factory($this->model)->create();
55+
$this->auth->login($shop);
56+
57+
$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);
58+
59+
// Run the middleware
60+
$middleware = new VerifyScopesMiddleware();
61+
$result = $middleware->handle($request, function () {
62+
});
63+
64+
//this line needs to assert if proper redirect was made
65+
$this->assertEquals($result, null);
66+
}
67+
68+
public function testScopeApiFailure(): void
69+
{
70+
// Setup API stub
71+
$this->setApiStub();
72+
ApiStub::stubResponses(['access_scopes_error']);
73+
74+
$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products');
75+
76+
$shop = factory($this->model)->create();
77+
$this->auth->login($shop);
78+
79+
$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);
80+
81+
// Run the middleware
82+
$middleware = new VerifyScopesMiddleware();
83+
$result = $middleware->handle($request, function () {
84+
});
85+
86+
//this line needs to assert if proper redirect was made
87+
$this->assertEquals($result, null);
88+
}
89+
}

tests/fixtures/access_scopes.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"access_scopes": [
3+
{
4+
"handle": "read_products"
5+
},
6+
{
7+
"handle": "write_products"
8+
}
9+
]
10+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"errors": [
3+
{
4+
"message": "Could not get access copes"
5+
}
6+
]
7+
}

0 commit comments

Comments
 (0)