From 46fa00cc7dc63f1148da98b897a08e05ff0bf100 Mon Sep 17 00:00:00 2001 From: "amelie.haladjian" Date: Mon, 4 Nov 2024 15:46:38 +0100 Subject: [PATCH] feat(api tester): enable specific user authentication for given endpoint --- src/Authenticator/Authenticator.php | 1 + src/Config/Auth.php | 12 ++++ src/Config/Filters.php | 58 +++++++++++++++++++ src/Definition/Example/OperationExample.php | 46 ++++++--------- src/Definition/Token.php | 9 +++ src/Test/Suite.php | 64 +-------------------- 6 files changed, 98 insertions(+), 92 deletions(-) diff --git a/src/Authenticator/Authenticator.php b/src/Authenticator/Authenticator.php index 1b190f2..2a98a8f 100644 --- a/src/Authenticator/Authenticator.php +++ b/src/Authenticator/Authenticator.php @@ -47,6 +47,7 @@ public function authenticate(Auth $config, Api $api, Requester $requester): Toke $body['access_token'], explode(' ', $config->getBody()['scope'] ?? ''), $body['refresh_token'] ?? null, + $config->getFilters(), $body['token_type'] ?? null, $body['expires_in'] ?? null, ); diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 7d4f08a..cd97a89 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -16,6 +16,8 @@ final class Auth */ private array $body = []; + private ?Filters $filters = null; + public function __construct( private readonly string $name ) { @@ -57,4 +59,14 @@ public function setBody(array $body): void { $this->body = $body; } + + public function getFilters(): ?Filters + { + return $this->filters; + } + + public function setFilters(Filters $filters): void + { + $this->filters = $filters; + } } diff --git a/src/Config/Filters.php b/src/Config/Filters.php index 7ac0ae5..d68abba 100644 --- a/src/Config/Filters.php +++ b/src/Config/Filters.php @@ -4,6 +4,8 @@ namespace APITester\Config; +use APITester\Util\Filterable; +use Symfony\Component\Yaml\Tag\TaggedValue; use Symfony\Component\Yaml\Yaml; final class Filters @@ -116,4 +118,60 @@ private function getBaseLineData(): array /** @var array{'exclude': ?array>} */ return Yaml::parseFile($this->getBaseline()); } + + /** + * @param array> $includeFilters + * @param array> $excludeFilters + */ + public function includes(Filterable $object): bool + { + $include = true; + foreach ($this->getInclude() as $item) { + $include = true; + foreach ($item as $key => $value) { + [$operator, $value] = $this->handleTags($value); + if (!$object->has($key, $value, $operator)) { + $include = false; + continue 2; + } + } + break; + } + + if (!$include) { + return false; + } + + foreach ($this->getExclude() as $item) { + foreach ($item as $key => $value) { + [$operator, $value] = $this->handleTags($value); + if (!$object->has($key, $value, $operator)) { + continue 2; + } + } + $include = false; + break; + } + + return $include; + } + + + + /** + * @return array{0: string, 1: string|int} + */ + private function handleTags(string|int|TaggedValue $value): array + { + $operator = '='; + + if ($value instanceof TaggedValue) { + if ($value->getTag() === 'NOT') { + $operator = '!='; + } + $value = (string) $value->getValue(); + } + + return [$operator, $value]; + } } diff --git a/src/Definition/Example/OperationExample.php b/src/Definition/Example/OperationExample.php index b90c273..6f76998 100644 --- a/src/Definition/Example/OperationExample.php +++ b/src/Definition/Example/OperationExample.php @@ -96,6 +96,7 @@ public function setParameter( foreach ($value as $attribute => $attributeValue) { $this->{$paramProp}[$name][$attribute] = (string) $attributeValue; } + return $this; } @@ -204,13 +205,11 @@ public function getPathParameters(): array if ($this->parent !== null) { $definitionParamsCount = $this->parent ->getPathParameters() - ->count() - ; + ->count(); if ($this->forceRandom || ($this->autoComplete && \count($this->pathParameters) < $definitionParamsCount)) { $randomPathParams = $this->parent ->getPathParameters() - ->getRandomExamples() - ; + ->getRandomExamples(); $this->pathParameters = array_merge($randomPathParams, $this->pathParameters); } } @@ -236,15 +235,13 @@ public function getQueryParameters(): array if ($this->parent !== null) { $definitionParamsCount = $this->parent ->getQueryParameters() - ->count() - ; + ->count(); if ($this->forceRandom || ($this->autoComplete && \count( - $this->queryParameters - ) < $definitionParamsCount)) { + $this->queryParameters + ) < $definitionParamsCount)) { $randomQueryParams = $this->parent ->getQueryParameters() - ->getRandomExamples() - ; + ->getRandomExamples(); $this->queryParameters = array_merge($randomQueryParams, $this->queryParameters); } } @@ -272,14 +269,13 @@ public function getHeaders(): array if ($this->getBody() !== null && !isset($this->headers['content-type'])) { $this->headers['content-type'] = $this ->getBody() - ->getMediaType() - ; + ->getMediaType(); } if ($this->parent !== null) { $definitionHeadersCount = $this->parent - ->getHeaders() - ->count() + 1 // content-type + ->getHeaders() + ->count() + 1 // content-type ; if ($this->parent->getSecurities()->count() > 0) { @@ -293,8 +289,7 @@ public function getHeaders(): array 'content-type', 'authorization', 'range', - ]) - ; + ]); $this->headers = array_merge($randomHeaders, $this->headers); } } @@ -380,19 +375,15 @@ public function setAuthenticationHeaders(Tokens $tokens, bool $ignoreScope = fal $scopes = $security->getScopes() ->where('name', '!=', 'current_user') ->select('name') - ->toArray() - ; + ->toArray(); if ($ignoreScope) { /** @var Token|null $token */ $token = $tokens->first(); } else { /** @var Token|null $token */ - $token = $tokens->where( - 'scopes', - 'includes', - $scopes - )->first(); + $token = $tokens->filter(fn (Token $token) => $token->getFilters()?->includes($operation) ?? false) + ->first() ?? $tokens->where('scopes', 'includes', $scopes)->first(); } if ($token !== null) { @@ -475,12 +466,10 @@ public function getPath(): string $example = null; if ($this->parent !== null && $this->forceRandom) { $example = $this->parent - ->getRandomExample() - ; + ->getRandomExample(); } elseif ($this->parent !== null && $this->autoComplete) { $example = $this->parent - ->getExample() - ; + ->getExample(); } if ($example !== null && (\count($pathParameters) === 0 || $this->forceRandom)) { @@ -522,8 +511,7 @@ public function getStringBody(): string } return $this->getBody() - ->getStringContent() - ; + ->getStringContent(); } private function getParametersProp(string $type): string diff --git a/src/Definition/Token.php b/src/Definition/Token.php index 046a16a..baabf89 100644 --- a/src/Definition/Token.php +++ b/src/Definition/Token.php @@ -4,6 +4,9 @@ namespace APITester\Definition; +use APITester\Config\Filters; +use APITester\Util\Filterable; + final class Token { private readonly string $type; @@ -19,6 +22,7 @@ public function __construct( private readonly string $accessToken, private readonly array $scopes = [], private readonly ?string $refreshToken = null, + private readonly ?Filters $filters = null, ?string $type = null, ?int $expiresIn = null ) { @@ -26,6 +30,11 @@ public function __construct( $this->expiresIn = $expiresIn ?? 3600; } + public function getFilters(): ?Filters + { + return $this->filters; + } + public function getAccessToken(): string { return $this->accessToken; diff --git a/src/Test/Suite.php b/src/Test/Suite.php index 97e6edd..998882f 100644 --- a/src/Test/Suite.php +++ b/src/Test/Suite.php @@ -11,14 +11,12 @@ use APITester\Preparator\Exception\PreparatorLoadingException; use APITester\Preparator\TestCasesPreparator; use APITester\Requester\Requester; -use APITester\Util\Filterable; use APITester\Util\Traits\TimeBoundTrait; use Illuminate\Support\Collection; use PHPUnit\Framework\TestResult; use PHPUnit\Framework\TestSuite; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Symfony\Component\Yaml\Tag\TaggedValue; /** * @internal @@ -87,43 +85,6 @@ public function setLogger(LoggerInterface $logger): void $this->logger = $logger; } - /** - * @param array> $includeFilters - * @param array> $excludeFilters - */ - public function includes(Filterable $object, array $includeFilters = [], array $excludeFilters = []): bool - { - $include = true; - foreach ($includeFilters as $item) { - $include = true; - foreach ($item as $key => $value) { - [$operator, $value] = $this->handleTags($value); - if (!$object->has($key, $value, $operator)) { - $include = false; - continue 2; - } - } - break; - } - - if (!$include) { - return false; - } - - foreach ($excludeFilters as $item) { - foreach ($item as $key => $value) { - [$operator, $value] = $this->handleTags($value); - if (!$object->has($key, $value, $operator)) { - continue 2; - } - } - $include = false; - break; - } - - return $include; - } - /** * @param array> $filter * @@ -233,13 +194,7 @@ private function prepareTestCases(): void private function filterOperation(Operations $operations): Operations { - return $operations->filter( - fn (Operation $operation) => $this->includes( - $operation, - $this->filters->getInclude(), - $this->filters->getExclude(), - ) - ); + return $operations->filter(fn (Operation $operation) => $this->filters->includes($operation)); } /** @@ -301,21 +256,4 @@ private function indexInPart(?string $part, int $index, int $total): bool return false; } - - /** - * @return array{0: string, 1: string|int} - */ - private function handleTags(string|int|\Symfony\Component\Yaml\Tag\TaggedValue $value): array - { - $operator = '='; - - if ($value instanceof TaggedValue) { - if ($value->getTag() === 'NOT') { - $operator = '!='; - } - $value = (string) $value->getValue(); - } - - return [$operator, $value]; - } }