From 4f748962f324c898d1b99476e7c0c0a062574c38 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 16 Feb 2022 17:38:33 +0100 Subject: [PATCH 01/14] Adding generateTenantToken method to the client --- src/Delegates/HandlesSystem.php | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index 56fa8ba6..d79a6c40 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace MeiliSearch\Delegates; +use MeiliSearch\Http\Serialize\Json; trait HandlesSystem { @@ -31,4 +32,45 @@ public function stats(): array { return $this->stats->show(); } + + function base64url_encode($data) { + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); + } + + public function generateTenantToken($parentApiKey, $payload) + { + $json = new Json(); + + // Standar JWT header for encryption with SHA256/HS256 algorithm + $header = array( + "typ"=> "JWT", + "alg"=> "HS256" + ); + + // Add the required fields with the prefix of the key + $payload['apiKeyPrefix'] = substr($parentApiKey, 0, 8); + + // Serialize the Header + $jsonHeader = $json->serialize($header); + + // Serialize the Payload + $jsonPayload = $json->serialize($payload); + + // Encode Header to Base64Url String + $encodedHeader = $this->base64url_encode($jsonHeader); + + // Encode Payload to Base64Url String + $encodedPayload = $this->base64url_encode($jsonPayload); + + // Create Signature Hash + $signature = hash_hmac('sha256', $encodedHeader . "." . $encodedPayload, $parentApiKey, true); + + // Encode Signature to Base64Url String + $encodedSignature = $this->base64url_encode($signature); + + // Create JWT + $jwtToken = $encodedHeader . "." . $encodedPayload . "." . $encodedSignature; + + return $jwtToken; + } } From d04ad0e430849b24d159eb94f1ddb026d913d4b7 Mon Sep 17 00:00:00 2001 From: alallema Date: Thu, 17 Feb 2022 10:06:36 +0100 Subject: [PATCH 02/14] Changes of parameters --- src/Delegates/HandlesSystem.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index d79a6c40..3950017c 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace MeiliSearch\Delegates; + use MeiliSearch\Http\Serialize\Json; trait HandlesSystem @@ -33,28 +34,33 @@ public function stats(): array return $this->stats->show(); } - function base64url_encode($data) { + public static function base64url_encode($data) + { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - public function generateTenantToken($parentApiKey, $payload) + public function generateTenantToken($options, ?string $apiKey = null) { $json = new Json(); // Standar JWT header for encryption with SHA256/HS256 algorithm - $header = array( - "typ"=> "JWT", - "alg"=> "HS256" - ); + $header = [ + 'typ' => 'JWT', + 'alg' => 'HS256', + ]; + + if (!$apiKey) { + $apiKey = $this->apiKey; + } // Add the required fields with the prefix of the key - $payload['apiKeyPrefix'] = substr($parentApiKey, 0, 8); + $options['apiKeyPrefix'] = substr($apiKey, 0, 8); // Serialize the Header $jsonHeader = $json->serialize($header); // Serialize the Payload - $jsonPayload = $json->serialize($payload); + $jsonPayload = $json->serialize($options); // Encode Header to Base64Url String $encodedHeader = $this->base64url_encode($jsonHeader); @@ -63,13 +69,13 @@ public function generateTenantToken($parentApiKey, $payload) $encodedPayload = $this->base64url_encode($jsonPayload); // Create Signature Hash - $signature = hash_hmac('sha256', $encodedHeader . "." . $encodedPayload, $parentApiKey, true); + $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $apiKey, true); // Encode Signature to Base64Url String $encodedSignature = $this->base64url_encode($signature); // Create JWT - $jwtToken = $encodedHeader . "." . $encodedPayload . "." . $encodedSignature; + $jwtToken = $encodedHeader.'.'.$encodedPayload.'.'.$encodedSignature; return $jwtToken; } From 1de659b573fa27ee097afc6f861b90691e960aa2 Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 21 Feb 2022 13:02:42 +0100 Subject: [PATCH 03/14] Changes due to choice of implementation --- src/Client.php | 1 + src/Delegates/HandlesSystem.php | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Client.php b/src/Client.php index 9a8a4752..d330c1c3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -42,6 +42,7 @@ public function __construct( ClientInterface $httpClient = null, RequestFactoryInterface $requestFactory = null ) { + $this->apiKey = $apiKey; $this->http = new Http\Client($url, $apiKey, $httpClient, $requestFactory); $this->index = new Indexes($this->http); $this->health = new Health($this->http); diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index 3950017c..eb674eec 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -34,12 +34,12 @@ public function stats(): array return $this->stats->show(); } - public static function base64url_encode($data) + public function base64url_encode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - public function generateTenantToken($options, ?string $apiKey = null) + public function generateTenantToken($searchRules, ?string $expiresAt = null, ?string $apiKey = null): string { $json = new Json(); @@ -49,18 +49,22 @@ public function generateTenantToken($options, ?string $apiKey = null) 'alg' => 'HS256', ]; - if (!$apiKey) { + if (null == $apiKey) { $apiKey = $this->apiKey; } - // Add the required fields with the prefix of the key - $options['apiKeyPrefix'] = substr($apiKey, 0, 8); + // Add the required fields to the payload + $payload['apiKeyPrefix'] = substr($apiKey, 0, 8); + $payload['searchRules'] = $searchRules; + if ($expiresAt) { + $payload['exp'] = $expiredAt; + } // Serialize the Header $jsonHeader = $json->serialize($header); // Serialize the Payload - $jsonPayload = $json->serialize($options); + $jsonPayload = $json->serialize($payload); // Encode Header to Base64Url String $encodedHeader = $this->base64url_encode($jsonHeader); From 75f6027bacfec583e1468f112afba930a6d60066 Mon Sep 17 00:00:00 2001 From: alallema Date: Tue, 22 Feb 2022 15:54:32 +0100 Subject: [PATCH 04/14] Adding tests --- src/Delegates/HandlesSystem.php | 6 +- tests/Endpoints/TenantTokenTest.php | 113 ++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/Endpoints/TenantTokenTest.php diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index eb674eec..f5ce70e3 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -39,11 +39,11 @@ public function base64url_encode($data) return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - public function generateTenantToken($searchRules, ?string $expiresAt = null, ?string $apiKey = null): string + public function generateTenantToken($searchRules, ?int $expiresAt = null, ?string $apiKey = null): string { $json = new Json(); - // Standar JWT header for encryption with SHA256/HS256 algorithm + // Standard JWT header for encryption with SHA256/HS256 algorithm $header = [ 'typ' => 'JWT', 'alg' => 'HS256', @@ -57,7 +57,7 @@ public function generateTenantToken($searchRules, ?string $expiresAt = null, ?st $payload['apiKeyPrefix'] = substr($apiKey, 0, 8); $payload['searchRules'] = $searchRules; if ($expiresAt) { - $payload['exp'] = $expiredAt; + $payload['exp'] = $expiresAt; } // Serialize the Header diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php new file mode 100644 index 00000000..e3030272 --- /dev/null +++ b/tests/Endpoints/TenantTokenTest.php @@ -0,0 +1,113 @@ +createEmptyIndex('tenantToken'); + $promise = $index->addDocuments(self::DOCUMENTS); + $index->waitForTask($promise['uid']); + + $response = $this->client->getKeys(); + $this->privateKey = array_reduce($response['results'], function ($carry, $item) { + if (str_contains($item['description'], 'Default Admin API')) { + return $item['key']; + } + }); + $this->privateClient = new Client($this->host, $this->privateKey); + } + + public function testGenerateTenantTokenWithSearchRulesOnly(): void + { + $token = $this->privateClient->generateTenantToken(searchRules: array('*')); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('offset', $response->toArray()); + $this->assertArrayHasKey('limit', $response->toArray()); + $this->assertArrayHasKey('processingTimeMs', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->assertSame(7, $response->getNbHits()); + $this->assertCount(7, $response->getHits()); + } + + public function testGenerateTenantTokenWithApiKey(): void + { + $token = $this->client->generateTenantToken(searchRules: array('*'), apiKey: $this->privateKey); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('offset', $response->toArray()); + $this->assertArrayHasKey('limit', $response->toArray()); + $this->assertArrayHasKey('processingTimeMs', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->assertSame(7, $response->getNbHits()); + $this->assertCount(7, $response->getHits()); + } + + public function testGenerateTenantTokenWithExpiresAt(): void + { + $date = new DateTime(); + $tomorrow = $date->modify('+1 day')->getTimestamp(); + + $token = $this->privateClient->generateTenantToken(searchRules: array('*'), expiresAt: $tomorrow); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('offset', $response->toArray()); + $this->assertArrayHasKey('limit', $response->toArray()); + $this->assertArrayHasKey('processingTimeMs', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->assertSame(7, $response->getNbHits()); + $this->assertCount(7, $response->getHits()); + } + + + public function testGenerateTenantTokenWithoutSearchRules(): void + { + $token = $this->privateClient->generateTenantToken(searchRules: ''); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + + $this->expectException(ApiException::class); + $tokenClient->index('tenantToken')->search(''); + } + + + public function testGenerateTenantTokenWithMasterKey(): void + { + $token = $this->client->generateTenantToken(array('*')); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + + $this->expectException(ApiException::class); + $tokenClient->index('tenantToken')->search(''); + } + + public function testGenerateTenantTokenWithBadExpiresAt(): void + { + $date = new DateTime(); + $yesterday = $date->modify('-2 day')->getTimestamp(); + + $token = $this->privateClient->generateTenantToken(searchRules: array('*'), expiresAt: $yesterday); + $tokenClient = new Client('http://127.0.0.1:7700', $token); + + $this->expectException(ApiException::class); + $tokenClient->index('tenantToken')->search(''); + } +} From 1c4ccbc1ce4a8ac1d9f9357a7e1e8c58b5be6eb8 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 16:16:12 +0100 Subject: [PATCH 05/14] Adding some validations --- src/Delegates/HandlesSystem.php | 17 ++++- src/Exceptions/InvalidArgumentException.php | 10 +++ tests/Endpoints/TenantTokenTest.php | 76 +++++++++++---------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index f5ce70e3..20536213 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -4,6 +4,8 @@ namespace MeiliSearch\Delegates; +use DateTime; +use MeiliSearch\Exceptions\InvalidArgumentException; use MeiliSearch\Http\Serialize\Json; trait HandlesSystem @@ -39,8 +41,19 @@ public function base64url_encode($data) return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - public function generateTenantToken($searchRules, ?int $expiresAt = null, ?string $apiKey = null): string + public function generateTenantToken($searchRules, ?DateTime $expiresAt = null, ?string $apiKey = null): string { + // Validate every fields + if (null == $apiKey && null == $this->apiKey) { + throw InvalidArgumentException::emptyArgument('api key'); + } + if (null == $searchRules) { + throw InvalidArgumentException::emptyArgument('search rules'); + } + if ($expiresAt && new DateTime() > $expiresAt) { + throw InvalidArgumentException::dateIsExpired($expiresAt); + } + $json = new Json(); // Standard JWT header for encryption with SHA256/HS256 algorithm @@ -57,7 +70,7 @@ public function generateTenantToken($searchRules, ?int $expiresAt = null, ?strin $payload['apiKeyPrefix'] = substr($apiKey, 0, 8); $payload['searchRules'] = $searchRules; if ($expiresAt) { - $payload['exp'] = $expiresAt; + $payload['exp'] = $expiresAt->getTimestamp(); } // Serialize the Header diff --git a/src/Exceptions/InvalidArgumentException.php b/src/Exceptions/InvalidArgumentException.php index 5ebd3dad..55b7cc96 100644 --- a/src/Exceptions/InvalidArgumentException.php +++ b/src/Exceptions/InvalidArgumentException.php @@ -4,6 +4,7 @@ namespace MeiliSearch\Exceptions; +use DateTime; use Exception; final class InvalidArgumentException extends Exception @@ -25,4 +26,13 @@ public static function emptyArgument(string $argumentName): self null ); } + + public static function dateIsExpired(DateTime $date): self + { + return new self( + sprintf('DateTime "%s" is expired. The date expiresAt should be in the future.', $date->format('Y-m-d H:i:s')), + 400, + null + ); + } } diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index e3030272..8f16d339 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -4,11 +4,11 @@ namespace Tests\Endpoints; -use DateTimeInterface; -use Tests\TestCase; +use Datetime; use MeiliSearch\Client; use MeiliSearch\Exceptions\ApiException; -use \Datetime; +use MeiliSearch\Exceptions\InvalidArgumentException; +use Tests\TestCase; final class TenantTokenTest extends TestCase { @@ -18,9 +18,7 @@ final class TenantTokenTest extends TestCase protected function setUp(): void { parent::setUp(); - $index = $this->createEmptyIndex('tenantToken'); - $promise = $index->addDocuments(self::DOCUMENTS); - $index->waitForTask($promise['uid']); + $this->createEmptyIndex('tenantToken'); $response = $this->client->getKeys(); $this->privateKey = array_reduce($response['results'], function ($carry, $item) { @@ -33,8 +31,11 @@ protected function setUp(): void public function testGenerateTenantTokenWithSearchRulesOnly(): void { - $token = $this->privateClient->generateTenantToken(searchRules: array('*')); - $tokenClient = new Client('http://127.0.0.1:7700', $token); + $promise = $this->client->index('tenantToken')->addDocuments(self::DOCUMENTS); + $this->client->waitForTask($promise['uid']); + + $token = $this->privateClient->generateTenantToken(searchRules: ['*']); + $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); @@ -46,10 +47,23 @@ public function testGenerateTenantTokenWithSearchRulesOnly(): void $this->assertCount(7, $response->getHits()); } + public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void + { + $this->createEmptyIndex('tenantTokenDuplicate'); + $token = $this->privateClient->generateTenantToken(searchRules: ['tenantToken']); + $tokenClient = new Client($this->host, $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->expectException(ApiException::class); + $response = $tokenClient->index('tenantTokenDuplicate')->search(''); + } + public function testGenerateTenantTokenWithApiKey(): void { - $token = $this->client->generateTenantToken(searchRules: array('*'), apiKey: $this->privateKey); - $tokenClient = new Client('http://127.0.0.1:7700', $token); + $token = $this->client->generateTenantToken(searchRules: ['*'], apiKey: $this->privateKey); + $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); @@ -57,17 +71,15 @@ public function testGenerateTenantTokenWithApiKey(): void $this->assertArrayHasKey('limit', $response->toArray()); $this->assertArrayHasKey('processingTimeMs', $response->toArray()); $this->assertArrayHasKey('query', $response->toArray()); - $this->assertSame(7, $response->getNbHits()); - $this->assertCount(7, $response->getHits()); } public function testGenerateTenantTokenWithExpiresAt(): void { $date = new DateTime(); - $tomorrow = $date->modify('+1 day')->getTimestamp(); + $tomorrow = $date->modify('+1 day'); - $token = $this->privateClient->generateTenantToken(searchRules: array('*'), expiresAt: $tomorrow); - $tokenClient = new Client('http://127.0.0.1:7700', $token); + $token = $this->privateClient->generateTenantToken(searchRules: ['*'], expiresAt: $tomorrow); + $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); @@ -75,39 +87,33 @@ public function testGenerateTenantTokenWithExpiresAt(): void $this->assertArrayHasKey('limit', $response->toArray()); $this->assertArrayHasKey('processingTimeMs', $response->toArray()); $this->assertArrayHasKey('query', $response->toArray()); - $this->assertSame(7, $response->getNbHits()); - $this->assertCount(7, $response->getHits()); } - - public function testGenerateTenantTokenWithoutSearchRules(): void + public function testGenerateTenantTokenWithEmptySearchRules(): void { + $this->expectException(InvalidArgumentException::class); $token = $this->privateClient->generateTenantToken(searchRules: ''); - $tokenClient = new Client('http://127.0.0.1:7700', $token); - - $this->expectException(ApiException::class); - $tokenClient->index('tenantToken')->search(''); } - - public function testGenerateTenantTokenWithMasterKey(): void + public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void { - $token = $this->client->generateTenantToken(array('*')); - $tokenClient = new Client('http://127.0.0.1:7700', $token); - - $this->expectException(ApiException::class); - $tokenClient->index('tenantToken')->search(''); + $this->expectException(InvalidArgumentException::class); + $token = $this->privateClient->generateTenantToken(searchRules: []); } public function testGenerateTenantTokenWithBadExpiresAt(): void { $date = new DateTime(); - $yesterday = $date->modify('-2 day')->getTimestamp(); + $yesterday = $date->modify('-1 day'); - $token = $this->privateClient->generateTenantToken(searchRules: array('*'), expiresAt: $yesterday); - $tokenClient = new Client('http://127.0.0.1:7700', $token); + $this->expectException(InvalidArgumentException::class); + $token = $this->privateClient->generateTenantToken(searchRules: ['*'], expiresAt: $yesterday); + } - $this->expectException(ApiException::class); - $tokenClient->index('tenantToken')->search(''); + public function testGenerateTenantTokenWithNoApiKey(): void + { + $client = new Client($this->host); + $this->expectException(InvalidArgumentException::class); + $token = $client->generateTenantToken(searchRules: ['*']); } } From 8a67f8cc0a7486e3343d7db54ca6123101d1618d Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 28 Feb 2022 13:00:49 +0100 Subject: [PATCH 06/14] Changes due to named parameters --- src/Delegates/HandlesSystem.php | 27 +++++++++++++++++---------- tests/Endpoints/TenantTokenTest.php | 26 +++++++++++++++++--------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index 20536213..d2db7ef6 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -41,17 +41,24 @@ public function base64url_encode($data) return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - public function generateTenantToken($searchRules, ?DateTime $expiresAt = null, ?string $apiKey = null): string + /** + * Generate a new tenant token. + * + * The $options parameter is an array, and the following keys are accepted: + * - apiKey: The API key parent of the token. If you leave it empty the client API Key will be used. + * - expiresAt: A DateTime when the key will expire. Note that if an expiresAt value is included it should be in UTC time. + */ + public function generateTenantToken($searchRules, ?array $options = []): string { // Validate every fields - if (null == $apiKey && null == $this->apiKey) { + if (!\array_key_exists('apiKey', $options) && null == $this->apiKey) { throw InvalidArgumentException::emptyArgument('api key'); } if (null == $searchRules) { throw InvalidArgumentException::emptyArgument('search rules'); } - if ($expiresAt && new DateTime() > $expiresAt) { - throw InvalidArgumentException::dateIsExpired($expiresAt); + if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { + throw InvalidArgumentException::dateIsExpired($options['expiresAt']); } $json = new Json(); @@ -62,15 +69,15 @@ public function generateTenantToken($searchRules, ?DateTime $expiresAt = null, ? 'alg' => 'HS256', ]; - if (null == $apiKey) { - $apiKey = $this->apiKey; + if (!\array_key_exists('apiKey', $options)) { + $options['apiKey'] = $this->apiKey; } // Add the required fields to the payload - $payload['apiKeyPrefix'] = substr($apiKey, 0, 8); + $payload['apiKeyPrefix'] = substr($options['apiKey'], 0, 8); $payload['searchRules'] = $searchRules; - if ($expiresAt) { - $payload['exp'] = $expiresAt->getTimestamp(); + if (\array_key_exists('expiresAt', $options)) { + $payload['exp'] = $options['expiresAt']->getTimestamp(); } // Serialize the Header @@ -86,7 +93,7 @@ public function generateTenantToken($searchRules, ?DateTime $expiresAt = null, ? $encodedPayload = $this->base64url_encode($jsonPayload); // Create Signature Hash - $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $apiKey, true); + $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $options['apiKey'], true); // Encode Signature to Base64Url String $encodedSignature = $this->base64url_encode($signature); diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index 8f16d339..93ef467d 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -34,7 +34,7 @@ public function testGenerateTenantTokenWithSearchRulesOnly(): void $promise = $this->client->index('tenantToken')->addDocuments(self::DOCUMENTS); $this->client->waitForTask($promise['uid']); - $token = $this->privateClient->generateTenantToken(searchRules: ['*']); + $token = $this->privateClient->generateTenantToken(['*']); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); @@ -50,7 +50,7 @@ public function testGenerateTenantTokenWithSearchRulesOnly(): void public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void { $this->createEmptyIndex('tenantTokenDuplicate'); - $token = $this->privateClient->generateTenantToken(searchRules: ['tenantToken']); + $token = $this->privateClient->generateTenantToken(['tenantToken']); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); @@ -62,7 +62,10 @@ public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void public function testGenerateTenantTokenWithApiKey(): void { - $token = $this->client->generateTenantToken(searchRules: ['*'], apiKey: $this->privateKey); + $options = [ + 'apiKey' => $this->privateKey, + ]; + $token = $this->client->generateTenantToken(['*'], $options); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); @@ -77,8 +80,10 @@ public function testGenerateTenantTokenWithExpiresAt(): void { $date = new DateTime(); $tomorrow = $date->modify('+1 day'); - - $token = $this->privateClient->generateTenantToken(searchRules: ['*'], expiresAt: $tomorrow); + $options = [ + 'expiresAt' => $tomorrow, + ]; + $token = $this->privateClient->generateTenantToken(['*'], $options); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); @@ -92,28 +97,31 @@ public function testGenerateTenantTokenWithExpiresAt(): void public function testGenerateTenantTokenWithEmptySearchRules(): void { $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken(searchRules: ''); + $token = $this->privateClient->generateTenantToken(''); } public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void { $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken(searchRules: []); + $token = $this->privateClient->generateTenantToken([]); } public function testGenerateTenantTokenWithBadExpiresAt(): void { $date = new DateTime(); $yesterday = $date->modify('-1 day'); + $options = [ + 'expiresAt' => $yesterday, + ]; $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken(searchRules: ['*'], expiresAt: $yesterday); + $token = $this->privateClient->generateTenantToken(['*'], $options); } public function testGenerateTenantTokenWithNoApiKey(): void { $client = new Client($this->host); $this->expectException(InvalidArgumentException::class); - $token = $client->generateTenantToken(searchRules: ['*']); + $token = $client->generateTenantToken(['*']); } } From 88ea7e975c7f78d4273ad7ad7f3dc3b874b01155 Mon Sep 17 00:00:00 2001 From: alallema Date: Tue, 1 Mar 2022 12:22:48 +0100 Subject: [PATCH 07/14] Create Tenant Token Endpoint --- src/Client.php | 4 +- src/Contracts/Endpoint.php | 4 +- src/Delegates/HandlesSystem.php | 69 +----------------------- src/Endpoints/TenantToken.php | 82 +++++++++++++++++++++++++++++ tests/Endpoints/TenantTokenTest.php | 58 +++++++++++++++++--- 5 files changed, 140 insertions(+), 77 deletions(-) create mode 100644 src/Endpoints/TenantToken.php diff --git a/src/Client.php b/src/Client.php index d330c1c3..ff8cb271 100644 --- a/src/Client.php +++ b/src/Client.php @@ -15,6 +15,7 @@ use MeiliSearch\Endpoints\Keys; use MeiliSearch\Endpoints\Stats; use MeiliSearch\Endpoints\Tasks; +use MeiliSearch\Endpoints\TenantToken; use MeiliSearch\Endpoints\Version; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; @@ -35,6 +36,7 @@ class Client private Stats $stats; private Tasks $tasks; private Dumps $dumps; + private TenantToken $tenantToken; public function __construct( string $url, @@ -42,7 +44,6 @@ public function __construct( ClientInterface $httpClient = null, RequestFactoryInterface $requestFactory = null ) { - $this->apiKey = $apiKey; $this->http = new Http\Client($url, $apiKey, $httpClient, $requestFactory); $this->index = new Indexes($this->http); $this->health = new Health($this->http); @@ -51,5 +52,6 @@ public function __construct( $this->tasks = new Tasks($this->http); $this->keys = new Keys($this->http); $this->dumps = new Dumps($this->http); + $this->tenantToken = new TenantToken($this->http, $apiKey); } } diff --git a/src/Contracts/Endpoint.php b/src/Contracts/Endpoint.php index e3d5d1a2..98b413c0 100644 --- a/src/Contracts/Endpoint.php +++ b/src/Contracts/Endpoint.php @@ -8,10 +8,12 @@ abstract class Endpoint { protected const PATH = ''; protected Http $http; + protected ?string $apiKey; - public function __construct(Http $http) + public function __construct(Http $http, ?string $apiKey = null) { $this->http = $http; + $this->apiKey = $apiKey; } public function show(): ?array diff --git a/src/Delegates/HandlesSystem.php b/src/Delegates/HandlesSystem.php index d2db7ef6..e9e53175 100644 --- a/src/Delegates/HandlesSystem.php +++ b/src/Delegates/HandlesSystem.php @@ -4,10 +4,6 @@ namespace MeiliSearch\Delegates; -use DateTime; -use MeiliSearch\Exceptions\InvalidArgumentException; -use MeiliSearch\Http\Serialize\Json; - trait HandlesSystem { public function health(): ?array @@ -36,71 +32,8 @@ public function stats(): array return $this->stats->show(); } - public function base64url_encode($data) - { - return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); - } - - /** - * Generate a new tenant token. - * - * The $options parameter is an array, and the following keys are accepted: - * - apiKey: The API key parent of the token. If you leave it empty the client API Key will be used. - * - expiresAt: A DateTime when the key will expire. Note that if an expiresAt value is included it should be in UTC time. - */ public function generateTenantToken($searchRules, ?array $options = []): string { - // Validate every fields - if (!\array_key_exists('apiKey', $options) && null == $this->apiKey) { - throw InvalidArgumentException::emptyArgument('api key'); - } - if (null == $searchRules) { - throw InvalidArgumentException::emptyArgument('search rules'); - } - if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { - throw InvalidArgumentException::dateIsExpired($options['expiresAt']); - } - - $json = new Json(); - - // Standard JWT header for encryption with SHA256/HS256 algorithm - $header = [ - 'typ' => 'JWT', - 'alg' => 'HS256', - ]; - - if (!\array_key_exists('apiKey', $options)) { - $options['apiKey'] = $this->apiKey; - } - - // Add the required fields to the payload - $payload['apiKeyPrefix'] = substr($options['apiKey'], 0, 8); - $payload['searchRules'] = $searchRules; - if (\array_key_exists('expiresAt', $options)) { - $payload['exp'] = $options['expiresAt']->getTimestamp(); - } - - // Serialize the Header - $jsonHeader = $json->serialize($header); - - // Serialize the Payload - $jsonPayload = $json->serialize($payload); - - // Encode Header to Base64Url String - $encodedHeader = $this->base64url_encode($jsonHeader); - - // Encode Payload to Base64Url String - $encodedPayload = $this->base64url_encode($jsonPayload); - - // Create Signature Hash - $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $options['apiKey'], true); - - // Encode Signature to Base64Url String - $encodedSignature = $this->base64url_encode($signature); - - // Create JWT - $jwtToken = $encodedHeader.'.'.$encodedPayload.'.'.$encodedSignature; - - return $jwtToken; + return $this->tenantToken->generateTenantToken($searchRules, $options); } } diff --git a/src/Endpoints/TenantToken.php b/src/Endpoints/TenantToken.php new file mode 100644 index 00000000..ddc4268b --- /dev/null +++ b/src/Endpoints/TenantToken.php @@ -0,0 +1,82 @@ +apiKey)) { + throw InvalidArgumentException::emptyArgument('api key'); + } + if (null == $searchRules) { + throw InvalidArgumentException::emptyArgument('search rules'); + } + if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { + throw InvalidArgumentException::dateIsExpired($options['expiresAt']); + } + + $json = new Json(); + + // Standard JWT header for encryption with SHA256/HS256 algorithm + $header = [ + 'typ' => 'JWT', + 'alg' => 'HS256', + ]; + + if (!\array_key_exists('apiKey', $options)) { + $options['apiKey'] = $this->apiKey; + } + + // Add the required fields to the payload + $payload = []; + $payload['apiKeyPrefix'] = substr($options['apiKey'], 0, 8); + $payload['searchRules'] = $searchRules; + if (\array_key_exists('expiresAt', $options)) { + $payload['exp'] = $options['expiresAt']->getTimestamp(); + } + + // Serialize the Header + $jsonHeader = $json->serialize($header); + + // Serialize the Payload + $jsonPayload = $json->serialize($payload); + + // Encode Header to Base64Url String + $encodedHeader = $this->base64url_encode($jsonHeader); + + // Encode Payload to Base64Url String + $encodedPayload = $this->base64url_encode($jsonPayload); + + // Create Signature Hash + $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $options['apiKey'], true); + + // Encode Signature to Base64Url String + $encodedSignature = $this->base64url_encode($signature); + + // Create JWT + $jwtToken = $encodedHeader.'.'.$encodedPayload.'.'.$encodedSignature; + + return $jwtToken; + } +} diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index 93ef467d..795499f7 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -4,6 +4,7 @@ namespace Tests\Endpoints; +/* @phpstan-ignore-next-line */ use Datetime; use MeiliSearch\Client; use MeiliSearch\Exceptions\ApiException; @@ -22,7 +23,7 @@ protected function setUp(): void $response = $this->client->getKeys(); $this->privateKey = array_reduce($response['results'], function ($carry, $item) { - if (str_contains($item['description'], 'Default Admin API')) { + if (array_key_exists('description', $item) && $item['description'] && str_contains($item['description'], 'Default Admin API')) { return $item['key']; } }); @@ -47,6 +48,46 @@ public function testGenerateTenantTokenWithSearchRulesOnly(): void $this->assertCount(7, $response->getHits()); } + public function testGenerateTenantTokenWithSearchRulesAsObject(): void + { + $promise = $this->client->index('tenantToken')->addDocuments(self::DOCUMENTS); + $this->client->waitForTask($promise['uid']); + + $token = $this->privateClient->generateTenantToken((object) ['*' => (object) []]); + $tokenClient = new Client($this->host, $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('offset', $response->toArray()); + $this->assertArrayHasKey('limit', $response->toArray()); + $this->assertArrayHasKey('processingTimeMs', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->assertSame(7, $response->getNbHits()); + $this->assertCount(7, $response->getHits()); + } + + public function testGenerateTenantTokenWithFilter(): void + { + $promise = $this->client->index('tenantToken')->addDocuments(self::DOCUMENTS); + $this->client->waitForTask($promise['uid']); + $promiseFromFilter = $this->client->index('tenantToken')->updateFilterableAttributes([ + 'id', + ]); + $this->client->waitForTask($promiseFromFilter['uid']); + + $token = $this->privateClient->generateTenantToken((object) ['tenantToken' => (object) ['filter' => 'id > 10']]); + $tokenClient = new Client($this->host, $token); + $response = $tokenClient->index('tenantToken')->search(''); + + $this->assertArrayHasKey('hits', $response->toArray()); + $this->assertArrayHasKey('offset', $response->toArray()); + $this->assertArrayHasKey('limit', $response->toArray()); + $this->assertArrayHasKey('processingTimeMs', $response->toArray()); + $this->assertArrayHasKey('query', $response->toArray()); + $this->assertSame(4, $response->getNbHits()); + $this->assertCount(4, $response->getHits()); + } + public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void { $this->createEmptyIndex('tenantTokenDuplicate'); @@ -78,6 +119,7 @@ public function testGenerateTenantTokenWithApiKey(): void public function testGenerateTenantTokenWithExpiresAt(): void { + /* @phpstan-ignore-next-line */ $date = new DateTime(); $tomorrow = $date->modify('+1 day'); $options = [ @@ -94,12 +136,6 @@ public function testGenerateTenantTokenWithExpiresAt(): void $this->assertArrayHasKey('query', $response->toArray()); } - public function testGenerateTenantTokenWithEmptySearchRules(): void - { - $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken(''); - } - public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void { $this->expectException(InvalidArgumentException::class); @@ -108,6 +144,7 @@ public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void public function testGenerateTenantTokenWithBadExpiresAt(): void { + /* @phpstan-ignore-next-line */ $date = new DateTime(); $yesterday = $date->modify('-1 day'); $options = [ @@ -124,4 +161,11 @@ public function testGenerateTenantTokenWithNoApiKey(): void $this->expectException(InvalidArgumentException::class); $token = $client->generateTenantToken(['*']); } + + public function testGenerateTenantTokenWithEmptyApiKey(): void + { + $client = new Client($this->host); + $this->expectException(InvalidArgumentException::class); + $token = $client->generateTenantToken(['*'], ['apiKey' => '']); + } } From 593a0865394c6ee6ea66f8e13edbfd350411ba72 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 2 Mar 2022 19:13:34 +0100 Subject: [PATCH 08/14] Merge Changes Key Class --- tests/Endpoints/KeysAndPermissionsTest.php | 2 ++ tests/Endpoints/TenantTokenTest.php | 6 +++--- tests/TestCase.php | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/Endpoints/KeysAndPermissionsTest.php b/tests/Endpoints/KeysAndPermissionsTest.php index 7ba95113..3d084d6b 100644 --- a/tests/Endpoints/KeysAndPermissionsTest.php +++ b/tests/Endpoints/KeysAndPermissionsTest.php @@ -79,6 +79,7 @@ public function testGetKey(): void $response = $this->client->getKey($key->getKey()); $this->assertNotNull($response->getKey()); + $this->assertNull($response->getDescription()); $this->assertIsArray($response->getActions()); $this->assertIsArray($response->getIndexes()); $this->assertNull($response->getExpiresAt()); @@ -99,6 +100,7 @@ public function testCreateKey(): void $key = $this->client->createKey(self::INFO_KEY); $this->assertNotNull($key->getKey()); + $this->assertNull($key->getDescription()); $this->assertIsArray($key->getActions()); $this->assertSame($key->getActions(), self::INFO_KEY['actions']); $this->assertIsArray($key->getIndexes()); diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index 795499f7..c3d286be 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -22,9 +22,9 @@ protected function setUp(): void $this->createEmptyIndex('tenantToken'); $response = $this->client->getKeys(); - $this->privateKey = array_reduce($response['results'], function ($carry, $item) { - if (array_key_exists('description', $item) && $item['description'] && str_contains($item['description'], 'Default Admin API')) { - return $item['key']; + $this->privateKey = array_reduce($response, function ($carry, $item) { + if ($item->getDescription() && str_contains($item->getDescription(), 'Default Admin API')) { + return $item->getKey(); } }); $this->privateClient = new Client($this->host, $this->privateKey); diff --git a/tests/TestCase.php b/tests/TestCase.php index ac9f4503..8b1182db 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -21,7 +21,6 @@ abstract class TestCase extends BaseTestCase ]; protected const INFO_KEY = [ - 'description' => 'test', 'actions' => ['search'], 'indexes' => ['index'], 'expiresAt' => null, From 420bce9b634a526711dfe64a8c081662403954ca Mon Sep 17 00:00:00 2001 From: alallema Date: Thu, 3 Mar 2022 10:24:37 +0100 Subject: [PATCH 09/14] Changes du to review clean test for efficiency and readability --- tests/Endpoints/TenantTokenTest.php | 38 ++++++++--------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index c3d286be..d2d320ae 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -40,11 +40,6 @@ public function testGenerateTenantTokenWithSearchRulesOnly(): void $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); - $this->assertArrayHasKey('offset', $response->toArray()); - $this->assertArrayHasKey('limit', $response->toArray()); - $this->assertArrayHasKey('processingTimeMs', $response->toArray()); - $this->assertArrayHasKey('query', $response->toArray()); - $this->assertSame(7, $response->getNbHits()); $this->assertCount(7, $response->getHits()); } @@ -58,11 +53,6 @@ public function testGenerateTenantTokenWithSearchRulesAsObject(): void $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); - $this->assertArrayHasKey('offset', $response->toArray()); - $this->assertArrayHasKey('limit', $response->toArray()); - $this->assertArrayHasKey('processingTimeMs', $response->toArray()); - $this->assertArrayHasKey('query', $response->toArray()); - $this->assertSame(7, $response->getNbHits()); $this->assertCount(7, $response->getHits()); } @@ -80,17 +70,13 @@ public function testGenerateTenantTokenWithFilter(): void $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); - $this->assertArrayHasKey('offset', $response->toArray()); - $this->assertArrayHasKey('limit', $response->toArray()); - $this->assertArrayHasKey('processingTimeMs', $response->toArray()); - $this->assertArrayHasKey('query', $response->toArray()); - $this->assertSame(4, $response->getNbHits()); $this->assertCount(4, $response->getHits()); } public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void { $this->createEmptyIndex('tenantTokenDuplicate'); + $token = $this->privateClient->generateTenantToken(['tenantToken']); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); @@ -98,7 +84,7 @@ public function testGenerateTenantTokenWithSearchRulesOnOneIndex(): void $this->assertArrayHasKey('hits', $response->toArray()); $this->assertArrayHasKey('query', $response->toArray()); $this->expectException(ApiException::class); - $response = $tokenClient->index('tenantTokenDuplicate')->search(''); + $tokenClient->index('tenantTokenDuplicate')->search(''); } public function testGenerateTenantTokenWithApiKey(): void @@ -106,15 +92,12 @@ public function testGenerateTenantTokenWithApiKey(): void $options = [ 'apiKey' => $this->privateKey, ]; + $token = $this->client->generateTenantToken(['*'], $options); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); - $this->assertArrayHasKey('offset', $response->toArray()); - $this->assertArrayHasKey('limit', $response->toArray()); - $this->assertArrayHasKey('processingTimeMs', $response->toArray()); - $this->assertArrayHasKey('query', $response->toArray()); } public function testGenerateTenantTokenWithExpiresAt(): void @@ -125,21 +108,18 @@ public function testGenerateTenantTokenWithExpiresAt(): void $options = [ 'expiresAt' => $tomorrow, ]; + $token = $this->privateClient->generateTenantToken(['*'], $options); $tokenClient = new Client($this->host, $token); $response = $tokenClient->index('tenantToken')->search(''); $this->assertArrayHasKey('hits', $response->toArray()); - $this->assertArrayHasKey('offset', $response->toArray()); - $this->assertArrayHasKey('limit', $response->toArray()); - $this->assertArrayHasKey('processingTimeMs', $response->toArray()); - $this->assertArrayHasKey('query', $response->toArray()); } public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void { $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken([]); + $this->privateClient->generateTenantToken([]); } public function testGenerateTenantTokenWithBadExpiresAt(): void @@ -152,20 +132,22 @@ public function testGenerateTenantTokenWithBadExpiresAt(): void ]; $this->expectException(InvalidArgumentException::class); - $token = $this->privateClient->generateTenantToken(['*'], $options); + $this->privateClient->generateTenantToken(['*'], $options); } public function testGenerateTenantTokenWithNoApiKey(): void { $client = new Client($this->host); + $this->expectException(InvalidArgumentException::class); - $token = $client->generateTenantToken(['*']); + $client->generateTenantToken(['*']); } public function testGenerateTenantTokenWithEmptyApiKey(): void { $client = new Client($this->host); + $this->expectException(InvalidArgumentException::class); - $token = $client->generateTenantToken(['*'], ['apiKey' => '']); + $client->generateTenantToken(['*'], ['apiKey' => '']); } } From 62f2cebe9becdbe091e8e87a294287b305ca98ba Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 7 Mar 2022 10:08:20 +0100 Subject: [PATCH 10/14] Modify readability of the tests --- tests/Endpoints/TenantTokenTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Endpoints/TenantTokenTest.php b/tests/Endpoints/TenantTokenTest.php index d2d320ae..99be4ce1 100644 --- a/tests/Endpoints/TenantTokenTest.php +++ b/tests/Endpoints/TenantTokenTest.php @@ -124,6 +124,8 @@ public function testGenerateTenantTokenWithSearchRulesEmptyArray(): void public function testGenerateTenantTokenWithBadExpiresAt(): void { + $this->expectException(InvalidArgumentException::class); + /* @phpstan-ignore-next-line */ $date = new DateTime(); $yesterday = $date->modify('-1 day'); @@ -131,7 +133,6 @@ public function testGenerateTenantTokenWithBadExpiresAt(): void 'expiresAt' => $yesterday, ]; - $this->expectException(InvalidArgumentException::class); $this->privateClient->generateTenantToken(['*'], $options); } From b142cf359ef6aa85c2454ef1f3a4b92428deaa9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie?= Date: Mon, 7 Mar 2022 11:50:54 +0100 Subject: [PATCH 11/14] Update src/Endpoints/TenantToken.php Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com> --- src/Endpoints/TenantToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Endpoints/TenantToken.php b/src/Endpoints/TenantToken.php index ddc4268b..3dfb942a 100644 --- a/src/Endpoints/TenantToken.php +++ b/src/Endpoints/TenantToken.php @@ -25,7 +25,7 @@ private function base64url_encode($data) */ public function generateTenantToken($searchRules, ?array $options = []): string { - // Validate every fields + // Validate every field if ((!\array_key_exists('apiKey', $options) || '' == $options['apiKey']) && \is_null($this->apiKey)) { throw InvalidArgumentException::emptyArgument('api key'); } From 6a11c3b03bd62a46daaf872c10d9a81f71879ce1 Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 7 Mar 2022 12:23:48 +0100 Subject: [PATCH 12/14] Refactoring TenantToken method due to review --- src/Endpoints/TenantToken.php | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Endpoints/TenantToken.php b/src/Endpoints/TenantToken.php index 3dfb942a..963030b3 100644 --- a/src/Endpoints/TenantToken.php +++ b/src/Endpoints/TenantToken.php @@ -16,6 +16,17 @@ private function base64url_encode($data) return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } + private function validateTenantTokenArguments($searchRules, ?array $options = []) { + if (!\array_key_exists('apiKey', $options) || '' == $options['apiKey'] || strlen($options['apiKey']) <= 8) { + throw InvalidArgumentException::emptyArgument('api key'); + } + if (null == $searchRules) { + throw InvalidArgumentException::emptyArgument('search rules'); + } + if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { + throw InvalidArgumentException::dateIsExpired($options['expiresAt']); + } + } /** * Generate a new tenant token. * @@ -25,17 +36,13 @@ private function base64url_encode($data) */ public function generateTenantToken($searchRules, ?array $options = []): string { - // Validate every field - if ((!\array_key_exists('apiKey', $options) || '' == $options['apiKey']) && \is_null($this->apiKey)) { - throw InvalidArgumentException::emptyArgument('api key'); - } - if (null == $searchRules) { - throw InvalidArgumentException::emptyArgument('search rules'); - } - if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { - throw InvalidArgumentException::dateIsExpired($options['expiresAt']); + if (!\array_key_exists('apiKey', $options) || '' == $options['apiKey']) { + $options['apiKey'] = $this->apiKey; } + // Validate every field + $this->validateTenantTokenArguments($searchRules, $options); + $json = new Json(); // Standard JWT header for encryption with SHA256/HS256 algorithm @@ -44,10 +51,6 @@ public function generateTenantToken($searchRules, ?array $options = []): string 'alg' => 'HS256', ]; - if (!\array_key_exists('apiKey', $options)) { - $options['apiKey'] = $this->apiKey; - } - // Add the required fields to the payload $payload = []; $payload['apiKeyPrefix'] = substr($options['apiKey'], 0, 8); From b76d17e081140d9f85cd1f9dafcb31fd801804a4 Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 7 Mar 2022 12:59:53 +0100 Subject: [PATCH 13/14] Add type check for searchRules --- src/Endpoints/TenantToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Endpoints/TenantToken.php b/src/Endpoints/TenantToken.php index 963030b3..a3724538 100644 --- a/src/Endpoints/TenantToken.php +++ b/src/Endpoints/TenantToken.php @@ -20,7 +20,7 @@ private function validateTenantTokenArguments($searchRules, ?array $options = [] if (!\array_key_exists('apiKey', $options) || '' == $options['apiKey'] || strlen($options['apiKey']) <= 8) { throw InvalidArgumentException::emptyArgument('api key'); } - if (null == $searchRules) { + if ((!is_array($searchRules) && !is_object($searchRules)) || null == $searchRules) { throw InvalidArgumentException::emptyArgument('search rules'); } if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { From 68cdec3e951ac62e1ff6bb431fc14e5d860759c1 Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 7 Mar 2022 13:38:04 +0100 Subject: [PATCH 14/14] Fix lint --- src/Endpoints/TenantToken.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Endpoints/TenantToken.php b/src/Endpoints/TenantToken.php index a3724538..4126f72a 100644 --- a/src/Endpoints/TenantToken.php +++ b/src/Endpoints/TenantToken.php @@ -16,17 +16,19 @@ private function base64url_encode($data) return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - private function validateTenantTokenArguments($searchRules, ?array $options = []) { - if (!\array_key_exists('apiKey', $options) || '' == $options['apiKey'] || strlen($options['apiKey']) <= 8) { + private function validateTenantTokenArguments($searchRules, ?array $options = []): void + { + if (!\array_key_exists('apiKey', $options) || '' == $options['apiKey'] || \strlen($options['apiKey']) <= 8) { throw InvalidArgumentException::emptyArgument('api key'); } - if ((!is_array($searchRules) && !is_object($searchRules)) || null == $searchRules) { + if ((!\is_array($searchRules) && !\is_object($searchRules)) || null == $searchRules) { throw InvalidArgumentException::emptyArgument('search rules'); } if (\array_key_exists('expiresAt', $options) && new DateTime() > $options['expiresAt']) { throw InvalidArgumentException::dateIsExpired($options['expiresAt']); } } + /** * Generate a new tenant token. *