Skip to content

Commit

Permalink
Created Middleware to Verify Scopes and reauthenticate if required (#187
Browse files Browse the repository at this point in the history
)
  • Loading branch information
usmanpakistan authored Aug 24, 2023
1 parent 444ef60 commit 5a68ef8
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,5 @@ fabric.properties

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
/.idea/codeception.xml
/.idea/phpspec.xml
52 changes: 52 additions & 0 deletions src/Http/Middleware/VerifyScopes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Osiset\ShopifyApp\Http\Middleware;

use Closure;
use Exception;
use Illuminate\Http\Request;
use Osiset\ShopifyApp\Contracts\ShopModel as IShopModel;
use Osiset\ShopifyApp\Util;

class VerifyScopes
{
/**
* Checks if a shop has all required access scopes.
* If a required access scope is missing, it will redirect the app
* for re-authentication
*
* @param Request $request The request object.
* @param Closure $next The next action.
*
* @throws Exception
*
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
/** @var $shop IShopModel */
$shop = auth()->user();
$scopesResponse = $shop->api()->rest('GET', '/admin/oauth/access_scopes.json');
if ($scopesResponse && $scopesResponse['errors']) {
return $next($request);
}
$scopes = json_decode(json_encode($scopesResponse['body']['access_scopes']), false);
$scopes = array_map(static function ($scope) {
return $scope->handle;
}, $scopes);

$requiredScopes = explode(',', Util::getShopifyConfig('api_scopes'));
$missingScopes = array_diff($requiredScopes, $scopes);
if (count($missingScopes) === 0) {
return $next($request);
}

return redirect()->route(
Util::getShopifyConfig('route_names.authenticate'),
[
'shop' => $shop->getDomain()->toNative(),
'host' => $request->get('host'),
]
);
}
}
2 changes: 2 additions & 0 deletions src/ShopifyAppProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Osiset\ShopifyApp\Http\Middleware\AuthWebhook;
use Osiset\ShopifyApp\Http\Middleware\Billable;
use Osiset\ShopifyApp\Http\Middleware\IframeProtection;
use Osiset\ShopifyApp\Http\Middleware\VerifyScopes;
use Osiset\ShopifyApp\Http\Middleware\VerifyShopify;
use Osiset\ShopifyApp\Macros\TokenRedirect;
use Osiset\ShopifyApp\Macros\TokenRoute;
Expand Down Expand Up @@ -318,6 +319,7 @@ private function bootMiddlewares(): void
$this->app['router']->aliasMiddleware('auth.webhook', AuthWebhook::class);
$this->app['router']->aliasMiddleware('billable', Billable::class);
$this->app['router']->aliasMiddleware('verify.shopify', VerifyShopify::class);
$this->app['router']->aliasMiddleware('verify.scopes', VerifyScopes::class);

$this->app->booted(function () {
$this->app['router']->pushMiddlewareToGroup('web', IframeProtection::class);
Expand Down
89 changes: 89 additions & 0 deletions tests/Http/Middleware/VerifyScopesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Osiset\ShopifyApp\Test\Http\Middleware;

use Illuminate\Auth\AuthManager;
use Illuminate\Http\Request;
use Osiset\ShopifyApp\Http\Middleware\VerifyScopes as VerifyScopesMiddleware;
use Osiset\ShopifyApp\Test\Stubs\Api as ApiStub;
use Osiset\ShopifyApp\Test\TestCase;

class VerifyScopesTest extends TestCase
{
/**
* @var AuthManager
*/
protected $auth;

public function setUp(): void
{
parent::setUp();
$this->auth = $this->app->make(AuthManager::class);
}

public function testMissingScopes(): void
{
// Setup API stub
$this->setApiStub();
ApiStub::stubResponses(['access_scopes']);

$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products,read_orders');

$shop = factory($this->model)->create();
$this->auth->login($shop);

$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);

// Run the middleware
$middleware = new VerifyScopesMiddleware();
$result = $middleware->handle($request, function () {
});

//this line needs to assert if proper redirect was made
$this->assertEquals(302, $result->getStatusCode());
}

public function testMatchingScopes(): void
{
// Setup API stub
$this->setApiStub();
ApiStub::stubResponses(['access_scopes']);

$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products');

$shop = factory($this->model)->create();
$this->auth->login($shop);

$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);

// Run the middleware
$middleware = new VerifyScopesMiddleware();
$result = $middleware->handle($request, function () {
});

//this line needs to assert if proper redirect was made
$this->assertEquals($result, null);
}

public function testScopeApiFailure(): void
{
// Setup API stub
$this->setApiStub();
ApiStub::stubResponses(['access_scopes_error']);

$this->app['config']->set('shopify-app.api_scopes', 'read_products,write_products');

$shop = factory($this->model)->create();
$this->auth->login($shop);

$request = Request::create('/', 'GET', ['shop' => $shop->getDomain()->toNative()]);

// Run the middleware
$middleware = new VerifyScopesMiddleware();
$result = $middleware->handle($request, function () {
});

//this line needs to assert if proper redirect was made
$this->assertEquals($result, null);
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/access_scopes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"access_scopes": [
{
"handle": "read_products"
},
{
"handle": "write_products"
}
]
}
7 changes: 7 additions & 0 deletions tests/fixtures/access_scopes_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"errors": [
{
"message": "Could not get access copes"
}
]
}

0 comments on commit 5a68ef8

Please sign in to comment.