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

Feature/add shop to api init #631

Merged
merged 5 commits into from
Dec 2, 2020
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
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit"
"test": "vendor/bin/phpunit",
"test-html-cov": "vendor/bin/phpunit --coverage-html ./build/html/",
"test-no-cov": "vendor/bin/phpunit --no-coverage"
},
"support": {
"issues": "https://github.com/osiset/laravel-shopify/issues",
Expand Down
4 changes: 2 additions & 2 deletions src/ShopifyApp/Actions/AuthorizeShop.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ public function __invoke(ShopDomain $shopDomain, ?string $code): stdClass

// Access/grant mode
$grantMode = $shop->hasOfflineAccess() ?
AuthMode::fromNative($this->getConfig('api_grant_mode')) :
AuthMode::fromNative($this->getConfig('api_grant_mode', $shop)) :
AuthMode::OFFLINE();

$return['url'] = $apiHelper->buildAuthUrl($grantMode, $this->getConfig('api_scopes'));
$return['url'] = $apiHelper->buildAuthUrl($grantMode, $this->getConfig('api_scopes', $shop));

// If there's no code
if (empty($code)) {
Expand Down
8 changes: 7 additions & 1 deletion src/ShopifyApp/Http/Middleware/AuthProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ public function handle(Request $request, Closure $next)
}

// Build a local signature
$signatureLocal = createHmac(['data' => $query, 'buildQuery' => true], $this->getConfig('api_secret'));
$signatureLocal = createHmac(
[
'data' => $query,
'buildQuery' => true,
],
$this->getConfig('api_secret', $shop)
);
if ($signature !== $signatureLocal || $shop->isNull()) {
// Issue with HMAC or missing shop header
return Response::make('Invalid proxy signature.', 401);
Expand Down
19 changes: 12 additions & 7 deletions src/ShopifyApp/Http/Middleware/AuthToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ public function handle(Request $request, Closure $next)
}

$parts = explode('.', $token);

$body = base64url_decode($parts[1]);
$signature = $parts[2];

$body = json_decode($body);
$body = json_decode(base64url_decode($parts[1]));

if (! $body ||
! isset($body->iss) ||
Expand Down Expand Up @@ -110,17 +106,26 @@ public function handle(Request $request, Closure $next)
/**
* Checks the validity of the signature sent with the token.
*
* @param string $token The token to check.
* @param string $token The token to check.
*
* @return bool
*/
private function checkSignature($token)
{
// Get the signature data
$parts = explode('.', $token);
$signature = array_pop($parts);
$check = implode('.', $parts);

$secret = $this->getConfig('api_secret');
// Get the shop
$shop = null;
$body = json_decode(base64url_decode($parts[1]));
if (isset($body->dest)) {
$url = parse_url($body->dest);
$shop = isset($url['host']) ? $url['host'] : null;
}

$secret = $this->getConfig('api_secret', $shop);
$hmac = hash_hmac('sha256', $check, $secret, true);
$encoded = base64url_encode($hmac);

Expand Down
9 changes: 8 additions & 1 deletion src/ShopifyApp/Http/Middleware/AuthWebhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ public function handle(Request $request, Closure $next)
$hmac = $request->header('x-shopify-hmac-sha256') ?: '';
$shop = $request->header('x-shopify-shop-domain');
$data = $request->getContent();
$hmacLocal = createHmac(['data' => $data, 'raw' => true, 'encode' => true], $this->getConfig('api_secret'));
$hmacLocal = createHmac(
[
'data' => $data,
'raw' => true,
'encode' => true,
],
$this->getConfig('api_secret', $shop)
);

if (! hash_equals($hmac, $hmacLocal) || empty($shop)) {
// Issue with HMAC or missing shop header
Expand Down
9 changes: 8 additions & 1 deletion src/ShopifyApp/Http/Requests/StoreUsageCharge.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Osiset\ShopifyApp\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Request;
use Illuminate\Validation\Validator;
use function Osiset\ShopifyApp\createHmac;
use Osiset\ShopifyApp\Traits\ConfigAccessible;
Expand Down Expand Up @@ -49,7 +50,13 @@ public function withValidator(Validator $validator): void
unset($data['signature']);

// Confirm the charge hasn't been tampered with
$signatureLocal = createHmac(['data' => $data, 'buildQuery' => true], $this->getConfig('api_secret'));
$signatureLocal = createHmac(
[
'data' => $data,
'buildQuery' => true,
],
$this->getConfig('api_secret')
);
if (! hash_equals($signature, $signatureLocal)) {
// Possible tampering
$validator->errors()->add('signature', 'Signature does not match.');
Expand Down
14 changes: 8 additions & 6 deletions src/ShopifyApp/Services/ApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Exception;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\URL;
use Osiset\BasicShopifyAPI\BasicShopifyAPI;
Expand Down Expand Up @@ -43,9 +44,10 @@ public function make(Session $session = null): self
// Create the options
$opts = new Options();

$opts->setApiKey($this->getConfig('api_key'));
$opts->setApiSecret($this->getConfig('api_secret'));
$opts->setVersion($this->getConfig('api_version'));
$shop = $session ? $session->getShop() : Arr::get(Request::all(), 'shop');
$opts->setApiKey($this->getConfig('api_key', $shop));
$opts->setApiSecret($this->getConfig('api_secret', $shop));
$opts->setVersion($this->getConfig('api_version', $shop));

// Create the instance
if ($this->getConfig('api_init')) {
Expand All @@ -58,9 +60,9 @@ public function make(Session $session = null): self
);
} else {
// Default init
$ts = $this->getConfig('api_time_store');
$ls = $this->getConfig('api_limit_store');
$sd = $this->getConfig('api_deferrer');
$ts = $this->getConfig('api_time_store', $shop);
$ls = $this->getConfig('api_limit_store', $shop);
$sd = $this->getConfig('api_deferrer', $shop);

$this->api = new BasicShopifyAPI(
$opts,
Expand Down
7 changes: 4 additions & 3 deletions src/ShopifyApp/Services/ConfigHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ class ConfigHelper
/**
* Get the config value for a key.
*
* @param string $key The key to lookup.
* @param string $key The key to lookup.
* @param mixed $shop The shop domain (string, ShopDomain, etc).
*
* @return mixed
*
* @see \Osiset\ShopifyApp\Traits\ConfigAccessible::getConfig()
*/
public static function get(string $key)
public static function get(string $key, $shop = null)
{
return (new static)->getConfig($key);
return (new static)->getConfig($key, $shop);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ShopifyApp/Services/ShopSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public function guest(): bool
*/
public function getType(): AuthMode
{
return AuthMode::fromNative(strtoupper($this->getConfig('api_grant_mode')));
return AuthMode::fromNative(strtoupper($this->getConfig('api_grant_mode', $this->getShop())));
}

/**
Expand Down
19 changes: 16 additions & 3 deletions src/ShopifyApp/Traits/ConfigAccessible.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ trait ConfigAccessible
/**
* Get the config value for a key.
*
* @param string $key The key to lookup.
* @param string $key The key to lookup.
* @param mixed $shop The shop domain (string, ShopDomain, etc).
*
* @return mixed
*/
public function getConfig(string $key)
public function getConfig(string $key, $shop = null)
{
$this->config = array_merge(
Config::get('shopify-app'),
Expand All @@ -36,6 +37,18 @@ public function getConfig(string $key)
);
}

// Check if config API callback is defined
if (Str::startsWith($key, 'api')
&& Arr::exists($this->config, 'config_api_callback')
&& is_callable($this->config['config_api_callback'])) {
// It is, use this to get the config value
return call_user_func(
Arr::get($this->config, 'config_api_callback'),
$key,
$shop
);
}

return Arr::get($this->config, $key);
}

Expand All @@ -62,7 +75,7 @@ public function setConfig(string $key, $value): void
public function setConfigArray(array $kvs): void
{
foreach ($kvs as $key => $value) {
Config::set($key, $value);
$this->setConfig($key, $value);
}
}
}
16 changes: 16 additions & 0 deletions src/ShopifyApp/resources/config/shopify-app.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,20 @@
'scripttags' => env('SCRIPTTAGS_JOB_QUEUE', null),
'after_authenticate' => env('AFTER_AUTHENTICATE_JOB_QUEUE', null),
],

/*
|--------------------------------------------------------------------------
| Config API Callback
|--------------------------------------------------------------------------
|
| This option can be used to modify what returns when `getConfig('api_*')` is used.
| A use-case for this is modifying the return of `api_secret` or something similar.
|
| A closure/callable is required.
| The first argument will be the key string.
| The second argument will be something to help identify the shop.
|
*/

'config_api_callback' => null,
];
49 changes: 49 additions & 0 deletions tests/Traits/ConfigAccessibleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Osiset\ShopifyApp\Test\Traits;

use Closure;
use Illuminate\Support\Facades\Config;
use Osiset\ShopifyApp\Test\TestCase;
use Osiset\ShopifyApp\Traits\ConfigAccessible;

class ConfigTest
{
use ConfigAccessible;
}

class ConfigAccessibleTest extends TestCase
{
public function setUp(): void
{
parent::setUp();

Config::set('shopify-app.config_api_callback', function (string $key, $shop) {
if ($key === 'api_secret') {
return 'hello world';
}

return Config::get("shopify-app.{$key}");
});
}

public function testGet(): void
{
$klass = new ConfigTest();
$secret = $klass->getConfig('api_secret');
$grantMode = $klass->getConfig('api_grant_mode');

$this->assertEquals('hello world', $secret);
$this->assertEquals('OFFLINE', $grantMode);
}

public function testSet(): void
{
$klass = new ConfigTest();
$klass->setConfig('shopify-app.api_init', function () {
return true;
});

$this->assertInstanceOf(Closure::class, $klass->getConfig('api_init'));
}
}