Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

Setting up Iframe protection #1178

Merged
merged 8 commits into from
Sep 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/Http/Middleware/IframeProtection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Osiset\ShopifyApp\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;

/**
* Responsibility for protection against clickjaking
*/
class IframeProtection
{
/**
* The shop querier.
*
* @var IShopQuery
*/
protected $shopQuery;

/**
* Constructor.
*
* @param IShopQuery $shopQuery The shop querier.
*
* @return void
*/
public function __construct(
IShopQuery $shopQuery
) {
$this->shopQuery = $shopQuery;
}

/**
* Set frame-ancestors header
*
* @param Request $request The request object.
* @param \Closure $next The next action.
*
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);

$shop = Cache::remember(
'frame-ancestors_'.$request->get('shop'),
now()->addMinutes(20),
function () use ($request) {
return $this->shopQuery->getByDomain(ShopDomain::fromRequest($request));
}
);

$domain = $shop
? $shop->name
: '*.myshopify.com';

$response->headers->set(
'Content-Security-Policy',
"frame-ancestors https://$domain https://admin.shopify.com"
);

return $response;
}
}
3 changes: 3 additions & 0 deletions src/ShopifyAppProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use Osiset\ShopifyApp\Http\Middleware\AuthProxy;
use Osiset\ShopifyApp\Http\Middleware\AuthWebhook;
use Osiset\ShopifyApp\Http\Middleware\Billable;
use Osiset\ShopifyApp\Http\Middleware\IframeProtection;
use Osiset\ShopifyApp\Http\Middleware\VerifyShopify;
use Osiset\ShopifyApp\Macros\TokenRedirect;
use Osiset\ShopifyApp\Macros\TokenRoute;
Expand Down Expand Up @@ -305,6 +306,8 @@ 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']->pushMiddlewareToGroup('web', IframeProtection::class);
}

/**
Expand Down
66 changes: 66 additions & 0 deletions tests/Http/Middleware/IframeProtectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Osiset\ShopifyApp\Test\Http\Middleware;

use Illuminate\Auth\AuthManager;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Osiset\ShopifyApp\Http\Middleware\IframeProtection;
use Osiset\ShopifyApp\Storage\Queries\Shop as ShopQuery;
use Osiset\ShopifyApp\Test\TestCase;

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

public function setUp(): void
{
parent::setUp();

$this->auth = $this->app->make(AuthManager::class);
}

public function testIframeProtectionWithAuthorizedShop(): void
{
$shop = factory($this->model)->create();
$this->auth->login($shop);

$domain = auth()->user()->name;
$expectedHeader = "frame-ancestors https://$domain https://admin.shopify.com";

$request = new Request();
$shopQueryStub = $this->createStub(ShopQuery::class);
$shopQueryStub->method('getByDomain')->willReturn($shop);
$next = function () {
return new Response('Test Response');
};

$middleware = new IframeProtection($shopQueryStub);
$response = $middleware->handle($request, $next);
$currentHeader = $response->headers->get('content-security-policy');

$this->assertNotEmpty($currentHeader);
$this->assertEquals($expectedHeader, $currentHeader);
}

public function testIframeProtectionWithUnauthorizedShop(): void
{
$expectedHeader = 'frame-ancestors https://*.myshopify.com https://admin.shopify.com';

$request = new Request();
$shopQuery = new ShopQuery();
$next = function () {
return new Response('Test Response');
};

$middleware = new IframeProtection($shopQuery);
$response = $middleware->handle($request, $next);
$currentHeader = $response->headers->get('content-security-policy');

$this->assertNotEmpty($currentHeader);
$this->assertEquals($expectedHeader, $currentHeader);
}
}