From 9cdd1b271049d7c8d1d6d37669855f1c09a4639b Mon Sep 17 00:00:00 2001 From: Stephen Sweetland Date: Fri, 26 Aug 2022 08:07:19 +0100 Subject: [PATCH 01/22] Olavosantos 1094 issue branch w/ updated tests (#1191) * fix: fixed app-bridge redirect on fullpage_redirect auth view * refactor: extracted variable logic from view into the controller * style: fixed lint error * fix: fixed serialization of shopDomain * :art: CS fix * :white_check_mark: Add composer normalize plugin to CI setup * :white_check_mark: Roll back test to check against view Co-authored-by: Olavo Santos Co-authored-by: Luke Walsh <32519106+Kyon147@users.noreply.github.com> Co-authored-by: Stephen Sweetland --- .github/workflows/ci.yml | 3 +++ src/Traits/AuthController.php | 16 ++++++++++++++-- .../views/auth/fullpage_redirect.blade.php | 16 ++++++++++++---- tests/Traits/AuthControllerTest.php | 7 +++++-- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eebdc3ee..62839af9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,9 @@ jobs: - name: Install Laravel and Orchestra Testbench run: composer require "illuminate/contracts:${{ matrix.laravel }}" --no-interaction --no-update + - name: Allow composer-normalize plugin + run: composer config allow-plugins.ergebnis/composer-normalize true + - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" diff --git a/src/Traits/AuthController.php b/src/Traits/AuthController.php index 6674ef5b..df537b34 100644 --- a/src/Traits/AuthController.php +++ b/src/Traits/AuthController.php @@ -54,8 +54,20 @@ public function authenticate(Request $request, AuthenticateShop $authShop) throw new MissingAuthUrlException('Missing auth url'); } - // Just return them straight to the OAUTH flow. - return Redirect::to($result['url']); + $shopDomain = $shopDomain->toNative(); + $shopOrigin = $shopDomain ?? $request->user()->name; + + return View::make( + 'shopify-app::auth.fullpage_redirect', + [ + 'apiKey' => Util::getShopifyConfig('api_key', $shopOrigin), + 'appBridgeVersion' => Util::getShopifyConfig('appbridge_version') ? '@'.config('shopify-app.appbridge_version') : '', + 'authUrl' => $result['url'], + 'host' => $request->host ?? base64_encode($shopOrigin.'/admin'), + 'shopDomain' => $shopDomain, + 'shopOrigin' => $shopOrigin, + ] + ); } else { // Go to home route return Redirect::route( diff --git a/src/resources/views/auth/fullpage_redirect.blade.php b/src/resources/views/auth/fullpage_redirect.blade.php index c6feecff..8d438b4d 100644 --- a/src/resources/views/auth/fullpage_redirect.blade.php +++ b/src/resources/views/auth/fullpage_redirect.blade.php @@ -6,6 +6,8 @@ Redirecting... + + diff --git a/tests/Traits/AuthControllerTest.php b/tests/Traits/AuthControllerTest.php index efdb9a5a..bcbc3355 100644 --- a/tests/Traits/AuthControllerTest.php +++ b/tests/Traits/AuthControllerTest.php @@ -24,8 +24,11 @@ public function testAuthRedirectsToShopifyWhenNoCode(): void $response = $this->call('post', '/authenticate', ['shop' => 'example.myshopify.com']); // Check the redirect happens and location is set properly in the header. - $response->assertStatus(302); - $response->assertHeader('location', 'https://example.myshopify.com/admin/oauth/authorize?client_id='.Util::getShopifyConfig('api_key').'&scope=read_products%2Cwrite_products&redirect_uri=https%3A%2F%2Flocalhost%2Fauthenticate'); + $response->assertViewHas('shopDomain', 'example.myshopify.com'); + $response->assertViewHas( + 'authUrl', + 'https://example.myshopify.com/admin/oauth/authorize?client_id='.Util::getShopifyConfig('api_key').'&scope=read_products%2Cwrite_products&redirect_uri=https%3A%2F%2Flocalhost%2Fauthenticate' + ); } public function testAuthAcceptsShopWithCode(): void From 7beefe780bdf60a91663176c2c61b626d95e3c60 Mon Sep 17 00:00:00 2001 From: Steve Perry Date: Fri, 26 Aug 2022 08:19:14 +0100 Subject: [PATCH 02/22] Adding support for Laravel 9 (#1100) * Adding support for Laravel 9 * Update tests for laravel 9. * Ignore some strategy * update orchestra version dependencies * Fix tests Co-authored-by: Lucas Michot --- .github/workflows/ci.yml | 13 ++++++++++--- composer.json | 6 +++--- src/Traits/BillingController.php | 6 +++--- tests/Stubs/Kernel.php | 24 +++++++++++++++++++++++- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62839af9..48c7c439 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,16 +21,23 @@ jobs: laravel: - '7.0' - '8.22' + - '9.0' exclude: - php: '7.2' laravel: '8.22' + - php: '7.2' + laravel: '9.0' + - php: '7.3' + laravel: '9.0' + - php: '7.4' + laravel: '9.0' - php: '8.0' laravel: '7.0' - php: '8.0' - laravel: '8.22' + laravel: '9.0' include: - php: '8.0' - laravel: '8.22' + laravel: '9.0' analysis: true coverage: 'xdebug' normalize: true @@ -54,7 +61,7 @@ jobs: run: composer validate --strict - name: Install Laravel legacy factories support - if: matrix.laravel == '8.22' + if: matrix.laravel != '7.0' run: composer require "laravel/legacy-factories:^1.1" --dev --no-interaction --no-update - name: Install Laravel and Orchestra Testbench diff --git a/composer.json b/composer.json index 660df275..d8b6f83d 100644 --- a/composer.json +++ b/composer.json @@ -28,15 +28,15 @@ "ext-json": "*", "funeralzone/valueobjects": "^0.5", "jenssegers/agent": "^2.6", - "laravel/framework": "^7.0 || ^8.0", + "laravel/framework": "^7.0 || ^8.0 || ^9.0", "osiset/basic-shopify-api": "^9.0 || ^10.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.8", "friendsofphp/php-cs-fixer": "^3.0", "mockery/mockery": "^1.0", - "orchestra/database": "~3.8 || ~4.0 || ~5.0 || ~6.0", - "orchestra/testbench": "~3.8 || ~4.0 || ~5.0 || ~6.0", + "orchestra/database": "~3.8 || ~4.0 || ~5.0 || ~6.0 || ~7.0", + "orchestra/testbench": "~3.8 || ~4.0 || ~5.0 || ~6.0 || ~7.0", "phpstan/phpstan": "^0.12", "phpunit/phpunit": "~8.0 || ^9.0" }, diff --git a/src/Traits/BillingController.php b/src/Traits/BillingController.php index 1d24742d..6cae42de 100644 --- a/src/Traits/BillingController.php +++ b/src/Traits/BillingController.php @@ -27,18 +27,18 @@ trait BillingController /** * Redirects to billing screen for Shopify. * - * @param int|null $plan The plan's ID, if provided in route. * @param Request $request The request object. * @param ShopQuery $shopQuery The shop querier. * @param GetPlanUrl $getPlanUrl The action for getting the plan URL. + * @param int|null $plan The plan's ID, if provided in route. * * @return ViewView */ public function index( - ?int $plan = null, Request $request, ShopQuery $shopQuery, - GetPlanUrl $getPlanUrl + GetPlanUrl $getPlanUrl, + ?int $plan = null ): ViewView { // Get the shop $shop = $shopQuery->getByDomain(ShopDomain::fromNative($request->get('shop'))); diff --git a/tests/Stubs/Kernel.php b/tests/Stubs/Kernel.php index 15eb36d8..ccdd1328 100644 --- a/tests/Stubs/Kernel.php +++ b/tests/Stubs/Kernel.php @@ -13,7 +13,7 @@ use Osiset\ShopifyApp\Http\Middleware\Billable; use Osiset\ShopifyApp\Http\Middleware\VerifyShopify; -class Kernel extends \Orchestra\Testbench\Http\Kernel +class Kernel extends \Orchestra\Testbench\Foundation\Http\Kernel { /** * The application's route middleware. @@ -36,4 +36,26 @@ class Kernel extends \Orchestra\Testbench\Http\Kernel 'auth.proxy' => AuthProxy::class, 'billable' => Billable::class, ]; + + /** + * The application's route middleware groups. + * + * @var array + */ + protected $middlewareGroups = [ + 'web' => [ + \Illuminate\Cookie\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + 'throttle:api', + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + ]; } From 3a7f4be74b906387f0d1e2a0f7d6d8590472c544 Mon Sep 17 00:00:00 2001 From: Luke Walsh <32519106+Kyon147@users.noreply.github.com> Date: Fri, 26 Aug 2022 15:46:41 +0100 Subject: [PATCH 03/22] Allow ergebnis/composer-normalize to run the code clean up on the Github workflows. (#1194) --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d8b6f83d..fbe8e0e6 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,10 @@ "phpunit/phpunit": "~8.0 || ^9.0" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "ergebnis/composer-normalize": true + } }, "extra": { "laravel": { From e449664926ca411da958a032ba987583733ee106 Mon Sep 17 00:00:00 2001 From: Tony Le <55417634+thang12l@users.noreply.github.com> Date: Sat, 27 Aug 2022 03:18:41 +1000 Subject: [PATCH 04/22] Update BillingController.php (#1145) Fix PHP 8.0: Deprecate required parameters after optional parameters in function/method signatures From 7ac78296886de5f0d9fcb5d02065b40d403a8973 Mon Sep 17 00:00:00 2001 From: Vitaliy Dubov Date: Sat, 3 Sep 2022 23:59:36 +0400 Subject: [PATCH 05/22] Setting up Iframe protection (#1178) --- src/Http/Middleware/IframeProtection.php | 67 +++++++++++++++++++ src/ShopifyAppProvider.php | 3 + .../Http/Middleware/IframeProtectionTest.php | 66 ++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/Http/Middleware/IframeProtection.php create mode 100644 tests/Http/Middleware/IframeProtectionTest.php diff --git a/src/Http/Middleware/IframeProtection.php b/src/Http/Middleware/IframeProtection.php new file mode 100644 index 00000000..fb297530 --- /dev/null +++ b/src/Http/Middleware/IframeProtection.php @@ -0,0 +1,67 @@ +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; + } +} diff --git a/src/ShopifyAppProvider.php b/src/ShopifyAppProvider.php index eafff592..b05caa82 100644 --- a/src/ShopifyAppProvider.php +++ b/src/ShopifyAppProvider.php @@ -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; @@ -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); } /** diff --git a/tests/Http/Middleware/IframeProtectionTest.php b/tests/Http/Middleware/IframeProtectionTest.php new file mode 100644 index 00000000..a02448db --- /dev/null +++ b/tests/Http/Middleware/IframeProtectionTest.php @@ -0,0 +1,66 @@ +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); + } +} From 68eb2a119313ddced470e965df40adc881ab306d Mon Sep 17 00:00:00 2001 From: Usman Shaukat Date: Tue, 6 Sep 2022 16:01:37 +0500 Subject: [PATCH 06/22] Update BillingController.php (#1181) --- src/Traits/BillingController.php | 34 ++++++++++++++------------ tests/Traits/BillingControllerTest.php | 19 ++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/Traits/BillingController.php b/src/Traits/BillingController.php index 6cae42de..5b07e29d 100644 --- a/src/Traits/BillingController.php +++ b/src/Traits/BillingController.php @@ -27,18 +27,18 @@ trait BillingController /** * Redirects to billing screen for Shopify. * - * @param Request $request The request object. - * @param ShopQuery $shopQuery The shop querier. - * @param GetPlanUrl $getPlanUrl The action for getting the plan URL. - * @param int|null $plan The plan's ID, if provided in route. + * @param Request $request The request object. + * @param ShopQuery $shopQuery The shop querier. + * @param GetPlanUrl $getPlanUrl The action for getting the plan URL. + * @param int|null $plan The plan's ID, if provided in route. * * @return ViewView */ public function index( - Request $request, - ShopQuery $shopQuery, + Request $request, + ShopQuery $shopQuery, GetPlanUrl $getPlanUrl, - ?int $plan = null + ?int $plan = null ): ViewView { // Get the shop $shop = $shopQuery->getByDomain(ShopDomain::fromNative($request->get('shop'))); @@ -59,22 +59,26 @@ public function index( /** * Processes the response from the customer. * - * @param int $plan The plan's ID. - * @param Request $request The HTTP request object. - * @param ShopQuery $shopQuery The shop querier. + * @param int $plan The plan's ID. + * @param Request $request The HTTP request object. + * @param ShopQuery $shopQuery The shop querier. * @param ActivatePlan $activatePlan The action for activating the plan for a shop. * * @return RedirectResponse */ public function process( - int $plan, - Request $request, - ShopQuery $shopQuery, + int $plan, + Request $request, + ShopQuery $shopQuery, ActivatePlan $activatePlan ): RedirectResponse { // Get the shop $shop = $shopQuery->getByDomain(ShopDomain::fromNative($request->query('shop'))); - + if (!$request->has('charge_id')) { + return Redirect::route(Util::getShopifyConfig('route_names.home'), [ + 'shop' => $shop->getDomain()->toNative(), + ]); + } // Activate the plan and save $result = $activatePlan( $shop->getId(), @@ -94,7 +98,7 @@ public function process( /** * Allows for setting a usage charge. * - * @param StoreUsageCharge $request The verified request. + * @param StoreUsageCharge $request The verified request. * @param ActivateUsageCharge $activateUsageCharge The action for activating a usage charge. * * @return RedirectResponse diff --git a/tests/Traits/BillingControllerTest.php b/tests/Traits/BillingControllerTest.php index 3b0b94b4..eef18804 100644 --- a/tests/Traits/BillingControllerTest.php +++ b/tests/Traits/BillingControllerTest.php @@ -130,4 +130,23 @@ public function testUsageChargeSuccess(): void $response->assertRedirect('http://localhost'); $response->assertSessionHas('success'); } + + public function testReturnToSettingScreenNoPlan() + { + // Set up a shop + $shop = factory($this->model)->create([ + 'plan_id' => null, + ]); + //Log in + $this->auth->login($shop); + $url = 'https://example-app.com/billing/process/9999?shop='.$shop->name; + // Try to go to bill without a charge id which happens when you cancel the charge + $response = $this->call( + 'get', + $url, + ['shop' => $shop->name] + ); + //Confirm we get sent back to the homepage of the app + $response->assertRedirect('https://example-app.com?shop='.$shop->name); + } } From d38166925dc6aa695ae3f3a9d939afc8206a3bfb Mon Sep 17 00:00:00 2001 From: Gage Date: Tue, 6 Sep 2022 12:04:05 +0100 Subject: [PATCH 07/22] Update old readme info (#1185) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a4547f42..46543570 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,10 @@ __*__ *Wiki pages* - [Goals](#goals) - [Documentation](#documentation) -- [Installation](https://github.com/osiset/laravel-shopify/wiki/Installation)* *(New video guide to come soon)* +- [Installation](https://github.com/osiset/laravel-shopify/wiki/Installation)* - [Route List](https://github.com/osiset/laravel-shopify/wiki/Route-List)* - [Usage](https://github.com/osiset/laravel-shopify/wiki/Usage)* - [Changelog](https://github.com/osiset/laravel-shopify/wiki/Changelog)* -- [Roadmap](https://github.com/osiset/laravel-shopify/wiki/Roadmap)* - [Contributing Guide](https://github.com/osiset/laravel-shopify/blob/master/CONTRIBUTING.md) - [LICENSE](#license) From d8c7549d4fced7666a0ff5c02f60f810e4e59bce Mon Sep 17 00:00:00 2001 From: Luke Walsh <32519106+Kyon147@users.noreply.github.com> Date: Wed, 7 Sep 2022 15:05:39 +0100 Subject: [PATCH 08/22] Don't allow the latest change to the shopify api as it breaks the tests and charge helper (#1201) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fbe8e0e6..f5feb1d3 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "funeralzone/valueobjects": "^0.5", "jenssegers/agent": "^2.6", "laravel/framework": "^7.0 || ^8.0 || ^9.0", - "osiset/basic-shopify-api": "^9.0 || ^10.0" + "osiset/basic-shopify-api": "^9.0 || <=10.0.5" }, "require-dev": { "ergebnis/composer-normalize": "^2.8", From 1d5b351b621cd266f3cd75e603187ca6289f4240 Mon Sep 17 00:00:00 2001 From: Luke Walsh <32519106+Kyon147@users.noreply.github.com> Date: Fri, 9 Sep 2022 14:18:07 +0100 Subject: [PATCH 09/22] Feature | Add Session Refresh Time to ENV (#1117) Co-authored-by: Lucas Michot <513603+lucasmichot@users.noreply.github.com> --- src/resources/config/shopify-app.php | 4 +++- src/resources/views/partials/token_handler.blade.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/resources/config/shopify-app.php b/src/resources/config/shopify-app.php index 13338313..eb7db3dd 100644 --- a/src/resources/config/shopify-app.php +++ b/src/resources/config/shopify-app.php @@ -470,5 +470,7 @@ * The table name for Plan model. */ 'plans' => 'plans', - ] + ], + + 'session_token_refresh_interval' => env('SESSION_TOKEN_REFRESH_INTERVAL', 2000) ]; diff --git a/src/resources/views/partials/token_handler.blade.php b/src/resources/views/partials/token_handler.blade.php index ca729c90..77210d4f 100644 --- a/src/resources/views/partials/token_handler.blade.php +++ b/src/resources/views/partials/token_handler.blade.php @@ -1,9 +1,9 @@