Skip to content

Commit 324bef9

Browse files
authored
Merge pull request #43 from cyl3x/fix/iap/silence-jwt-token-exceptions
fix(iap): silence jwt token exceptions
2 parents f769700 + 9d5173f commit 324bef9

File tree

6 files changed

+25
-26
lines changed

6 files changed

+25
-26
lines changed

src/Context/ContextResolver.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@
3737
*/
3838
class ContextResolver
3939
{
40-
public function __construct(private readonly InAppPurchaseProvider $inAppPurchaseProvider)
41-
{
40+
public function __construct(
41+
private readonly ?InAppPurchaseProvider $inAppPurchaseProvider = null
42+
) {
4243
}
4344

4445
/**
@@ -95,7 +96,7 @@ public function assembleModule(RequestInterface $request, ShopInterface $shop):
9596
if (!empty($params['in-app-purchases'])) {
9697
/** @var non-empty-string $inAppPurchaseString */
9798
$inAppPurchaseString = $params['in-app-purchases'];
98-
$inAppPurchases = $this->inAppPurchaseProvider->decodePurchases($inAppPurchaseString, $shop);
99+
$inAppPurchases = $this->inAppPurchaseProvider?->decodePurchases($inAppPurchaseString, $shop);
99100
}
100101

101102
return new ModuleAction(
@@ -260,7 +261,7 @@ public function assembleStorefrontRequest(RequestInterface $request, ShopInterfa
260261
throw new MalformedWebhookBodyException();
261262
}
262263

263-
$inAppPurchases = $this->inAppPurchaseProvider->decodePurchases($claims['inAppPurchases'], $shop);
264+
$inAppPurchases = $this->inAppPurchaseProvider?->decodePurchases($claims['inAppPurchases'], $shop);
264265
}
265266

266267
return new StorefrontAction(
@@ -341,7 +342,7 @@ private function parseSource(array $source, ShopInterface $shop): ActionSource
341342
throw new MalformedWebhookBodyException();
342343
}
343344

344-
$inAppPurchases = $this->inAppPurchaseProvider->decodePurchases($source['inAppPurchases'], $shop);
345+
$inAppPurchases = $this->inAppPurchaseProvider?->decodePurchases($source['inAppPurchases'], $shop);
345346
}
346347

347348

src/Context/InAppPurchase/HasMatchingDomain.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
/**
1414
* @phpstan-import-type InAppPurchaseArray from InAppPurchaseProvider
15+
*
16+
* @deprecated Will be removed with version 5.0.0, as no licence domain can be provided for validation
1517
*/
1618
class HasMatchingDomain implements Constraint
1719
{

src/Context/InAppPurchase/HasValidRSAJWKSignature.php

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
use Lcobucci\JWT\Signer\Rsa\Sha384;
1212
use Lcobucci\JWT\Signer\Rsa\Sha512;
1313
use Lcobucci\JWT\Token;
14-
use Lcobucci\JWT\UnencryptedToken;
1514
use Lcobucci\JWT\Validation\Constraint;
16-
use Lcobucci\JWT\Validation\ConstraintViolation;
15+
use Lcobucci\JWT\Validation\Constraint\SignedWith;
1716
use Strobotti\JWK\Key\KeyInterface;
1817
use Strobotti\JWK\Key\Rsa as RsaKey;
1918
use Strobotti\JWK\KeyConverter;
@@ -27,12 +26,11 @@ public function __construct(private readonly KeySet $keys)
2726
{
2827
}
2928

29+
/**
30+
* {@inheritDoc}
31+
*/
3032
public function assert(Token $token): void
3133
{
32-
if (!$token instanceof UnencryptedToken) {
33-
throw new \Exception('Token must be a plain JWT');
34-
}
35-
3634
$this->validateAlgorithm($token);
3735

3836
$key = $this->getValidKey($token);
@@ -45,9 +43,7 @@ public function assert(Token $token): void
4543

4644
$signer = $this->getSigner($alg);
4745

48-
if (!$signer->verify($token->signature()->hash(), $token->payload(), InMemory::plainText($pem))) {
49-
throw ConstraintViolation::error('Token signature mismatch', $this);
50-
}
46+
(new SignedWith($signer, InMemory::plainText($pem)))->assert($token);
5147
}
5248

5349
private function validateAlgorithm(Token $token): void

src/Context/InAppPurchase/InAppPurchaseProvider.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Shopware\App\SDK\Shop\ShopInterface;
1515

1616
/**
17-
* @phpstan-type InAppPurchaseArray=array{identifier: string, quantity: int, nextBookingDate?: string, sub: string}
17+
* @phpstan-type InAppPurchaseArray array{identifier: string, quantity: int, nextBookingDate?: string, sub: string}
1818
*/
1919
class InAppPurchaseProvider
2020
{
@@ -27,21 +27,19 @@ public function __construct(
2727
/**
2828
* @param non-empty-string $encodedPurchases
2929
* @return Collection<InAppPurchase>
30-
* @throws \Exception
3130
*/
3231
public function decodePurchases(string $encodedPurchases, ShopInterface $shop, bool $retried = false): Collection
3332
{
3433
try {
3534
$keys = $this->keyFetcher->getKey($retried);
3635
$signatureValidator = new HasValidRSAJWKSignature($keys);
37-
$domainValidator = new HasMatchingDomain($shop);
3836

3937
$parser = new Parser(new JoseEncoder());
4038
/** @var Token\Plain $token */
4139
$token = $parser->parse($encodedPurchases);
4240

4341
$validator = new Validator();
44-
$validator->assert($token, $signatureValidator, $domainValidator);
42+
$validator->assert($token, $signatureValidator);
4543

4644
return $this->transformClaims($token);
4745
} catch (\Exception $e) {
@@ -51,7 +49,7 @@ public function decodePurchases(string $encodedPurchases, ShopInterface $shop, b
5149

5250
$this->logger->error('Failed to decode in-app purchases: ' . $e->getMessage());
5351

54-
throw $e;
52+
return new Collection();
5553
}
5654
}
5755

tests/Context/InAppPurchase/HasValidRSAJWKSignatureTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ public function testAssertWithWrongTokenType(): void
112112
$token = new class () implements Token {
113113
public function headers(): DataSet
114114
{
115-
return new DataSet([], '');
115+
return new DataSet([
116+
'alg' => 'RS256',
117+
'kid' => JWKSHelper::getStaticKid(),
118+
], '');
116119
}
117120

118121
public function isPermittedFor(string $audience): bool
@@ -157,9 +160,9 @@ public function toString(): string
157160
};
158161

159162
static::expectException(\Exception::class);
160-
static::expectExceptionMessage('Token must be a plain JWT');
163+
static::expectExceptionMessage('You should pass a plain token');
161164

162-
$constraint = new HasValidRSAJWKSignature(new KeySet());
165+
$constraint = new HasValidRSAJWKSignature($this->jwks);
163166
$constraint->assert($token);
164167
}
165168
}

tests/Context/InAppPurchase/InAppPurchaseProviderTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Shopware\App\SDK\Tests\Context\InAppPurchase;
66

7-
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
87
use PHPUnit\Framework\Attributes\CoversClass;
98
use PHPUnit\Framework\TestCase;
109
use Psr\Log\LoggerInterface;
@@ -122,9 +121,9 @@ public function testDecodePurchaseRetryFails(): void
122121
(new KeySetFactory())->createFromJSON('{"keys": [{"kty": "RSA","n": "yHenasOsOl-Vv2BmpayS1R8l5L-JN99FwaRRKXFssGTjJDwbYdbe3CqTSKqtOfdqZLzE6-bN2-Q1xqZZsgs0_zHNx7EROXNG_uQs1uuGkS6bgGhnq_2d7wzFvCsyI00CDXZxRlGjKAEhvcXormomF1jpUW08Y5tPeUvMSdEZbZxW1ydir-UrMm1RUSgJgSP-sUqLG7kTIJ6SG7cLtF8c8cHcVXFljMyiYLQHYOECj1oklwvfrfaoT3OKdKGumi39rDthXtFa0Aq1OS_P9qfZJ-yXiQlpf2RxRr3Q5EQJ8E9iqrlOndbkSq7eXne2DvvgsiNdyzRWFvxWSPSd9GZXkw","e": "AQAB","kid": "-1xljHNcPM59Qx9OcULA9LS219bsmKCZueVXhdF0N0k","use": "sig","alg": "RS256"}]}'),
123122
);
124123

125-
static::expectException(RequiredConstraintsViolated::class);
126-
127124
$provider = new InAppPurchaseProvider($fetcher, $logger);
128-
$provider->decodePurchases($token->toString(), $shop, true);
125+
$decoded = $provider->decodePurchases($token->toString(), $shop, true);
126+
127+
static::assertEmpty($decoded);
129128
}
130129
}

0 commit comments

Comments
 (0)