From 36238c6b7b3ce368a25f0c34fa66e0b65b37b9cf Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 12 Jun 2024 16:09:09 +0200 Subject: [PATCH 1/6] IBX-8356: Reworked Ibexa\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface usages to comply with Symfony-based authentication --- .../graphql/PlatformMutation.types.yaml | 18 --- .../Resources/config/services/resolvers.yaml | 6 - .../Resources/config/services/services.yaml | 9 ++ src/lib/Mutation/Authentication.php | 76 ---------- src/lib/Security/JWTAuthenticator.php | 141 ++++++++++++++++++ .../JWTTokenMutationFormatEventSubscriber.php | 50 +++++++ src/lib/Security/JWTUser.php | 56 ------- .../NonAdminGraphQLRequestMatcher.php | 17 ++- 8 files changed, 210 insertions(+), 163 deletions(-) delete mode 100644 src/lib/Mutation/Authentication.php create mode 100644 src/lib/Security/JWTAuthenticator.php create mode 100644 src/lib/Security/JWTTokenMutationFormatEventSubscriber.php delete mode 100644 src/lib/Security/JWTUser.php diff --git a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml index a9ee319..45a023f 100644 --- a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml +++ b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml @@ -24,14 +24,6 @@ PlatformMutation: language: type: RepositoryLanguage! description: "The language the content items must be created in" - createToken: - type: CreatedTokenPayload - resolve: '@=mutation("CreateToken", args)' - args: - username: - type: String! - password: - type: String! UploadedFilesPayload: type: object @@ -52,13 +44,3 @@ DeleteContentPayload: id: type: ID description: "Global ID" - -CreatedTokenPayload: - type: object - config: - fields: - token: - type: String - message: - type: String - description: "The reason why authentication has failed, if it has" diff --git a/src/bundle/Resources/config/services/resolvers.yaml b/src/bundle/Resources/config/services/resolvers.yaml index 8b55469..fde4e23 100644 --- a/src/bundle/Resources/config/services/resolvers.yaml +++ b/src/bundle/Resources/config/services/resolvers.yaml @@ -62,12 +62,6 @@ services: tags: - { name: overblog_graphql.resolver, alias: "Thumbnail", method: "resolveThumbnail" } - Ibexa\GraphQL\Mutation\Authentication: - arguments: - $authenticator: '@?ibexa.rest.session_authenticator' - tags: - - { name: overblog_graphql.mutation, alias: "CreateToken", method: "createToken" } - Ibexa\GraphQL\Mutation\UploadFiles: arguments: $repository: '@ibexa.siteaccessaware.repository' diff --git a/src/bundle/Resources/config/services/services.yaml b/src/bundle/Resources/config/services/services.yaml index 5e2dd63..048c9bb 100644 --- a/src/bundle/Resources/config/services/services.yaml +++ b/src/bundle/Resources/config/services/services.yaml @@ -50,3 +50,12 @@ services: $contentLoader: '@Ibexa\GraphQL\DataLoader\ContentLoader' tags: - { name: ibexa.field_type.image_asset.mapper.strategy, priority: 0 } + + Ibexa\GraphQL\Security\JWTAuthenticator: + arguments: + $userProvider: '@ibexa.security.user_provider' + + Ibexa\GraphQL\Security\JWTTokenMutationFormatEventSubscriber: + tags: + - name: kernel.event_subscriber + dispatcher: security.event_dispatcher.ibexa_jwt_graphql diff --git a/src/lib/Mutation/Authentication.php b/src/lib/Mutation/Authentication.php deleted file mode 100644 index 3b65a94..0000000 --- a/src/lib/Mutation/Authentication.php +++ /dev/null @@ -1,76 +0,0 @@ -tokenManager = $tokenManager; - $this->requestStack = $requestStack; - $this->authenticator = $authenticator; - } - - /** - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - */ - public function createToken($args): array - { - $username = $args['username']; - $password = $args['password']; - - $request = $this->requestStack->getCurrentRequest(); - $request->attributes->set('username', $username); - $request->attributes->set('password', (string) $password); - - try { - $user = $this->getAuthenticator()->authenticate($request)->getUser(); - - $token = $this->tokenManager->create( - new JWTUser($user, $username) - ); - - return ['token' => $token]; - } catch (AuthenticationException $e) { - return ['message' => 'Wrong username or password', 'token' => null]; - } - } - - private function getAuthenticator(): AuthenticatorInterface - { - if (null === $this->authenticator) { - throw new \RuntimeException( - sprintf( - "No %s instance injected. Ensure 'ezpublish_rest_session' is configured under your firewall", - AuthenticatorInterface::class - ) - ); - } - - return $this->authenticator; - } -} diff --git a/src/lib/Security/JWTAuthenticator.php b/src/lib/Security/JWTAuthenticator.php new file mode 100644 index 0000000..44fe9f3 --- /dev/null +++ b/src/lib/Security/JWTAuthenticator.php @@ -0,0 +1,141 @@ +getContent(), true); + if (!isset($payload['query'])) { + return false; + } + + try { + $credentials = $this->extractCredentials($payload['query']); + } catch (Exception) { + return false; + } + + if (isset($credentials['username'], $credentials['password'])) { + $this->username = $credentials['username']; + $this->password = $credentials['password']; + + return true; + } + + return false; + } + + public function authenticate(Request $request): Passport + { + $passport = new Passport( + new UserBadge($this->username, [$this->userProvider, 'loadUserByUsername']), + new PasswordCredentials($this->password) + ); + + $user = $passport->getUser(); + if ($user instanceof IbexaUser) { + $this->permissionResolver->setCurrentUserReference($user->getAPIUser()); + } + + $passport->setAttribute('token', $this->tokenManager->create($user)); + + return $passport; + } + + /** + * @throws \JsonException + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return new Response( + json_encode( + [ + 'token' => $this->tokenManager->create($token->getUser()), + 'message' => null, + ], + JSON_THROW_ON_ERROR + ) + ); + } + + /** + * @throws \JsonException + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return new Response( + json_encode( + [ + 'token' => null, + 'message' => $exception->getMessageKey(), + ], + JSON_THROW_ON_ERROR + ), + Response::HTTP_FORBIDDEN + ); + } + + public function isInteractive(): bool + { + return true; + } + + /** + * @return array + * + * @throws \Exception + */ + private function extractCredentials(string $graphqlQuery): array + { + $parsed = Parser::parse($graphqlQuery); + $credentials = []; + Visitor::visit( + $parsed, + [ + NodeKind::ARGUMENT => static function (ArgumentNode $node) use (&$credentials): void { + $credentials[$node->name->value] = (string)$node->value->value; + }, + ] + ); + + return $credentials; + } +} diff --git a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php new file mode 100644 index 0000000..e71153f --- /dev/null +++ b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php @@ -0,0 +1,50 @@ + ['onAuthorizationFinishes', 10], + LoginFailureEvent::class => ['onAuthorizationFinishes', 10], + ]; + } + + /** + * @throws \JsonException + */ + public function onAuthorizationFinishes(LoginSuccessEvent|LoginFailureEvent $event): void + { + $response = $event->getResponse(); + $response->setContent( + $this->formatMutationResponseData($response->getContent()) + ); + } + + /** + * @throws \JsonException + */ + private function formatMutationResponseData(mixed $data): string + { + return json_encode([ + 'data' => [ + 'CreateToken' => json_decode($data, true, 512, JSON_THROW_ON_ERROR), + ], + ]); + } +} diff --git a/src/lib/Security/JWTUser.php b/src/lib/Security/JWTUser.php deleted file mode 100644 index a93fe62..0000000 --- a/src/lib/Security/JWTUser.php +++ /dev/null @@ -1,56 +0,0 @@ -wrappedUser = $wrappedUser; - $this->userIdentifier = $userIdentifier; - } - - public function getPassword(): ?string - { - return $this->wrappedUser->getPassword(); - } - - public function eraseCredentials(): void - { - $this->wrappedUser->eraseCredentials(); - } - - public function getRoles(): array - { - return $this->wrappedUser->getRoles(); - } - - public function getSalt(): ?string - { - return $this->wrappedUser->getSalt(); - } - - public function getUsername(): string - { - return $this->userIdentifier ?? $this->wrappedUser->getUsername(); - } - - public function getWrappedUser(): UserInterface - { - return $this->wrappedUser; - } -} diff --git a/src/lib/Security/NonAdminGraphQLRequestMatcher.php b/src/lib/Security/NonAdminGraphQLRequestMatcher.php index 1d88950..f90de2f 100644 --- a/src/lib/Security/NonAdminGraphQLRequestMatcher.php +++ b/src/lib/Security/NonAdminGraphQLRequestMatcher.php @@ -16,16 +16,19 @@ * Security request matcher that excludes admin+graphql requests. * Needed because the admin uses GraphQL without a JWT. */ -class NonAdminGraphQLRequestMatcher implements RequestMatcherInterface +final readonly class NonAdminGraphQLRequestMatcher implements RequestMatcherInterface { - /** @var string[][] */ - private $siteAccessGroups; - - public function __construct(array $siteAccessGroups) - { - $this->siteAccessGroups = $siteAccessGroups; + /** + * @param string[][] $siteAccessGroups + */ + public function __construct( + private array $siteAccessGroups + ) { } + /** + * @throws \Ibexa\AdminUi\Exception\InvalidArgumentException + */ public function matches(Request $request): bool { return From 3884487423fac6e41fa2dc82cc632ad422d5d409 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 19 Jun 2024 12:06:19 +0200 Subject: [PATCH 2/6] fixed phpstan --- phpstan-baseline.neon | 49 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dd907d4..d540be5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -325,31 +325,6 @@ parameters: count: 1 path: src/lib/Mapper/ContentImageAssetMapperStrategy.php - - - message: "#^Cannot access property \\$attributes on Symfony\\\\Component\\\\HttpFoundation\\\\Request\\|null\\.$#" - count: 2 - path: src/lib/Mutation/Authentication.php - - - - message: "#^Method Ibexa\\\\GraphQL\\\\Mutation\\\\Authentication\\:\\:createToken\\(\\) has parameter \\$args with no type specified\\.$#" - count: 1 - path: src/lib/Mutation/Authentication.php - - - - message: "#^Method Ibexa\\\\GraphQL\\\\Mutation\\\\Authentication\\:\\:createToken\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/Mutation/Authentication.php - - - - message: "#^Parameter \\#1 \\$request of method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AuthenticatorInterface\\:\\:authenticate\\(\\) expects Symfony\\\\Component\\\\HttpFoundation\\\\Request, Symfony\\\\Component\\\\HttpFoundation\\\\Request\\|null given\\.$#" - count: 1 - path: src/lib/Mutation/Authentication.php - - - - message: "#^Parameter \\#1 \\$wrappedUser of class Ibexa\\\\GraphQL\\\\Security\\\\JWTUser constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" - count: 1 - path: src/lib/Mutation/Authentication.php - - message: "#^Method Ibexa\\\\GraphQL\\\\Mutation\\\\InputHandler\\\\FieldType\\\\BinaryFile\\:\\:toFieldValue\\(\\) has parameter \\$input with no type specified\\.$#" count: 1 @@ -2331,9 +2306,29 @@ parameters: path: src/lib/Schema/Worker.php - - message: "#^Method Ibexa\\\\GraphQL\\\\Security\\\\NonAdminGraphQLRequestMatcher\\:\\:__construct\\(\\) has parameter \\$siteAccessGroups with no value type specified in iterable type array\\.$#" + message: "#^Access to an undefined property GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode\\:\\:\\$value\\.$#" + count: 1 + path: src/lib/Security/JWTAuthenticator.php + + - + message: "#^Parameter \\#1 \\$user of method Lexik\\\\Bundle\\\\JWTAuthenticationBundle\\\\Services\\\\JWTTokenManagerInterface\\:\\:create\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" + count: 1 + path: src/lib/Security/JWTAuthenticator.php + + - + message: "#^Cannot call method getContent\\(\\) on Symfony\\\\Component\\\\HttpFoundation\\\\Response\\|null\\.$#" + count: 1 + path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php + + - + message: "#^Cannot call method setContent\\(\\) on Symfony\\\\Component\\\\HttpFoundation\\\\Response\\|null\\.$#" + count: 1 + path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php + + - + message: "#^Method Ibexa\\\\GraphQL\\\\Security\\\\JWTTokenMutationFormatEventSubscriber\\:\\:formatMutationResponseData\\(\\) should return string but returns string\\|false\\.$#" count: 1 - path: src/lib/Security/NonAdminGraphQLRequestMatcher.php + path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php - message: "#^Cannot cast object to string\\.$#" From f654f6ffec83ff23bd1710e99bbedcfff6cfa585 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 19 Jun 2024 12:17:33 +0200 Subject: [PATCH 3/6] improved code according to PHPStan reports --- phpstan-baseline.neon | 25 ------------------- src/lib/Security/JWTAuthenticator.php | 12 +++++++-- .../JWTTokenMutationFormatEventSubscriber.php | 8 +++++- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d540be5..ed36eed 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2305,31 +2305,6 @@ parameters: count: 1 path: src/lib/Schema/Worker.php - - - message: "#^Access to an undefined property GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode\\:\\:\\$value\\.$#" - count: 1 - path: src/lib/Security/JWTAuthenticator.php - - - - message: "#^Parameter \\#1 \\$user of method Lexik\\\\Bundle\\\\JWTAuthenticationBundle\\\\Services\\\\JWTTokenManagerInterface\\:\\:create\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" - count: 1 - path: src/lib/Security/JWTAuthenticator.php - - - - message: "#^Cannot call method getContent\\(\\) on Symfony\\\\Component\\\\HttpFoundation\\\\Response\\|null\\.$#" - count: 1 - path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php - - - - message: "#^Cannot call method setContent\\(\\) on Symfony\\\\Component\\\\HttpFoundation\\\\Response\\|null\\.$#" - count: 1 - path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php - - - - message: "#^Method Ibexa\\\\GraphQL\\\\Security\\\\JWTTokenMutationFormatEventSubscriber\\:\\:formatMutationResponseData\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/lib/Security/JWTTokenMutationFormatEventSubscriber.php - - message: "#^Cannot cast object to string\\.$#" count: 1 diff --git a/src/lib/Security/JWTAuthenticator.php b/src/lib/Security/JWTAuthenticator.php index 44fe9f3..6714986 100644 --- a/src/lib/Security/JWTAuthenticator.php +++ b/src/lib/Security/JWTAuthenticator.php @@ -85,10 +85,15 @@ public function authenticate(Request $request): Passport */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { + $user = $token->getUser(); + if ($user === null) { + throw new AuthenticationException('No authenticated user found.', 401); + } + return new Response( json_encode( [ - 'token' => $this->tokenManager->create($token->getUser()), + 'token' => $this->tokenManager->create($user), 'message' => null, ], JSON_THROW_ON_ERROR @@ -131,7 +136,10 @@ private function extractCredentials(string $graphqlQuery): array $parsed, [ NodeKind::ARGUMENT => static function (ArgumentNode $node) use (&$credentials): void { - $credentials[$node->name->value] = (string)$node->value->value; + /** @var \GraphQL\Language\AST\StringValueNode $nodeValue */ + $nodeValue = $node->value; + + $credentials[$node->name->value] = $nodeValue->value; }, ] ); diff --git a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php index e71153f..e55d4c4 100644 --- a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php +++ b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php @@ -31,6 +31,10 @@ public static function getSubscribedEvents(): array public function onAuthorizationFinishes(LoginSuccessEvent|LoginFailureEvent $event): void { $response = $event->getResponse(); + if ($response === null) { + return; + } + $response->setContent( $this->formatMutationResponseData($response->getContent()) ); @@ -41,10 +45,12 @@ public function onAuthorizationFinishes(LoginSuccessEvent|LoginFailureEvent $eve */ private function formatMutationResponseData(mixed $data): string { - return json_encode([ + $formatted = json_encode([ 'data' => [ 'CreateToken' => json_decode($data, true, 512, JSON_THROW_ON_ERROR), ], ]); + + return $formatted === false ? '' : $formatted; } } From 51e57fe7c024c3d286d355b0ed398601fe06f1bb Mon Sep 17 00:00:00 2001 From: konradoboza Date: Tue, 25 Jun 2024 15:16:54 +0200 Subject: [PATCH 4/6] restored mutation and moved authorization elsewhere --- .../graphql/PlatformMutation.types.yaml | 18 +++ .../Resources/config/services/resolvers.yaml | 4 + .../Resources/config/services/services.yaml | 9 -- src/lib/Mutation/AuthenticationMutation.php | 53 +++++++ src/lib/Security/JWTAuthenticator.php | 149 ------------------ .../JWTTokenMutationFormatEventSubscriber.php | 56 ------- 6 files changed, 75 insertions(+), 214 deletions(-) create mode 100644 src/lib/Mutation/AuthenticationMutation.php delete mode 100644 src/lib/Security/JWTAuthenticator.php delete mode 100644 src/lib/Security/JWTTokenMutationFormatEventSubscriber.php diff --git a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml index 45a023f..a9ee319 100644 --- a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml +++ b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml @@ -24,6 +24,14 @@ PlatformMutation: language: type: RepositoryLanguage! description: "The language the content items must be created in" + createToken: + type: CreatedTokenPayload + resolve: '@=mutation("CreateToken", args)' + args: + username: + type: String! + password: + type: String! UploadedFilesPayload: type: object @@ -44,3 +52,13 @@ DeleteContentPayload: id: type: ID description: "Global ID" + +CreatedTokenPayload: + type: object + config: + fields: + token: + type: String + message: + type: String + description: "The reason why authentication has failed, if it has" diff --git a/src/bundle/Resources/config/services/resolvers.yaml b/src/bundle/Resources/config/services/resolvers.yaml index fde4e23..d25fffc 100644 --- a/src/bundle/Resources/config/services/resolvers.yaml +++ b/src/bundle/Resources/config/services/resolvers.yaml @@ -62,6 +62,10 @@ services: tags: - { name: overblog_graphql.resolver, alias: "Thumbnail", method: "resolveThumbnail" } + Ibexa\GraphQL\Mutation\AuthenticationMutation: + tags: + - { name: overblog_graphql.mutation, alias: "CreateToken", method: "createToken" } + Ibexa\GraphQL\Mutation\UploadFiles: arguments: $repository: '@ibexa.siteaccessaware.repository' diff --git a/src/bundle/Resources/config/services/services.yaml b/src/bundle/Resources/config/services/services.yaml index 048c9bb..5e2dd63 100644 --- a/src/bundle/Resources/config/services/services.yaml +++ b/src/bundle/Resources/config/services/services.yaml @@ -50,12 +50,3 @@ services: $contentLoader: '@Ibexa\GraphQL\DataLoader\ContentLoader' tags: - { name: ibexa.field_type.image_asset.mapper.strategy, priority: 0 } - - Ibexa\GraphQL\Security\JWTAuthenticator: - arguments: - $userProvider: '@ibexa.security.user_provider' - - Ibexa\GraphQL\Security\JWTTokenMutationFormatEventSubscriber: - tags: - - name: kernel.event_subscriber - dispatcher: security.event_dispatcher.ibexa_jwt_graphql diff --git a/src/lib/Mutation/AuthenticationMutation.php b/src/lib/Mutation/AuthenticationMutation.php new file mode 100644 index 0000000..9174089 --- /dev/null +++ b/src/lib/Mutation/AuthenticationMutation.php @@ -0,0 +1,53 @@ + + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + public function createToken(Argument $args): array + { + if (!isset($args['username'], $args['password'])) { + return [ + 'message' => 'Missing username or password', + 'token' => null, + ]; + } + + try { + $user = $this->userService->loadUserByLogin($args['username']); + $this->userService->checkUserCredentials($user, $args['password']); + } catch (NotFoundException) { + return [ + 'message' => 'Wrong username or password', + 'token' => null, + ]; + } + + return [ + 'token' => $this->tokenManager->create(new User($user)), + ]; + } +} diff --git a/src/lib/Security/JWTAuthenticator.php b/src/lib/Security/JWTAuthenticator.php deleted file mode 100644 index 6714986..0000000 --- a/src/lib/Security/JWTAuthenticator.php +++ /dev/null @@ -1,149 +0,0 @@ -getContent(), true); - if (!isset($payload['query'])) { - return false; - } - - try { - $credentials = $this->extractCredentials($payload['query']); - } catch (Exception) { - return false; - } - - if (isset($credentials['username'], $credentials['password'])) { - $this->username = $credentials['username']; - $this->password = $credentials['password']; - - return true; - } - - return false; - } - - public function authenticate(Request $request): Passport - { - $passport = new Passport( - new UserBadge($this->username, [$this->userProvider, 'loadUserByUsername']), - new PasswordCredentials($this->password) - ); - - $user = $passport->getUser(); - if ($user instanceof IbexaUser) { - $this->permissionResolver->setCurrentUserReference($user->getAPIUser()); - } - - $passport->setAttribute('token', $this->tokenManager->create($user)); - - return $passport; - } - - /** - * @throws \JsonException - */ - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response - { - $user = $token->getUser(); - if ($user === null) { - throw new AuthenticationException('No authenticated user found.', 401); - } - - return new Response( - json_encode( - [ - 'token' => $this->tokenManager->create($user), - 'message' => null, - ], - JSON_THROW_ON_ERROR - ) - ); - } - - /** - * @throws \JsonException - */ - public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response - { - return new Response( - json_encode( - [ - 'token' => null, - 'message' => $exception->getMessageKey(), - ], - JSON_THROW_ON_ERROR - ), - Response::HTTP_FORBIDDEN - ); - } - - public function isInteractive(): bool - { - return true; - } - - /** - * @return array - * - * @throws \Exception - */ - private function extractCredentials(string $graphqlQuery): array - { - $parsed = Parser::parse($graphqlQuery); - $credentials = []; - Visitor::visit( - $parsed, - [ - NodeKind::ARGUMENT => static function (ArgumentNode $node) use (&$credentials): void { - /** @var \GraphQL\Language\AST\StringValueNode $nodeValue */ - $nodeValue = $node->value; - - $credentials[$node->name->value] = $nodeValue->value; - }, - ] - ); - - return $credentials; - } -} diff --git a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php deleted file mode 100644 index e55d4c4..0000000 --- a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php +++ /dev/null @@ -1,56 +0,0 @@ - ['onAuthorizationFinishes', 10], - LoginFailureEvent::class => ['onAuthorizationFinishes', 10], - ]; - } - - /** - * @throws \JsonException - */ - public function onAuthorizationFinishes(LoginSuccessEvent|LoginFailureEvent $event): void - { - $response = $event->getResponse(); - if ($response === null) { - return; - } - - $response->setContent( - $this->formatMutationResponseData($response->getContent()) - ); - } - - /** - * @throws \JsonException - */ - private function formatMutationResponseData(mixed $data): string - { - $formatted = json_encode([ - 'data' => [ - 'CreateToken' => json_decode($data, true, 512, JSON_THROW_ON_ERROR), - ], - ]); - - return $formatted === false ? '' : $formatted; - } -} From 18233e63bb0c9f5e42224c2eec79fb87cc89684e Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 26 Jun 2024 10:26:48 +0200 Subject: [PATCH 5/6] fixed lack of proper password validation --- src/lib/Mutation/AuthenticationMutation.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/Mutation/AuthenticationMutation.php b/src/lib/Mutation/AuthenticationMutation.php index 9174089..94e7878 100644 --- a/src/lib/Mutation/AuthenticationMutation.php +++ b/src/lib/Mutation/AuthenticationMutation.php @@ -38,10 +38,16 @@ public function createToken(Argument $args): array try { $user = $this->userService->loadUserByLogin($args['username']); - $this->userService->checkUserCredentials($user, $args['password']); } catch (NotFoundException) { return [ - 'message' => 'Wrong username or password', + 'message' => 'Wrong username', + 'token' => null, + ]; + } + + if (!$this->userService->checkUserCredentials($user, $args['password'])) { + return [ + 'message' => 'Wrong password', 'token' => null, ]; } From 83413ce5032b4f61ac4003d239ada87dfa826182 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 26 Jun 2024 13:06:38 +0200 Subject: [PATCH 6/6] cr remark --- src/lib/Mutation/AuthenticationMutation.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib/Mutation/AuthenticationMutation.php b/src/lib/Mutation/AuthenticationMutation.php index 94e7878..eabb351 100644 --- a/src/lib/Mutation/AuthenticationMutation.php +++ b/src/lib/Mutation/AuthenticationMutation.php @@ -39,21 +39,26 @@ public function createToken(Argument $args): array try { $user = $this->userService->loadUserByLogin($args['username']); } catch (NotFoundException) { - return [ - 'message' => 'Wrong username', - 'token' => null, - ]; + return $this->getWrongCredentialsErrorMessage(); } if (!$this->userService->checkUserCredentials($user, $args['password'])) { - return [ - 'message' => 'Wrong password', - 'token' => null, - ]; + return $this->getWrongCredentialsErrorMessage(); } return [ 'token' => $this->tokenManager->create(new User($user)), ]; } + + /** + * @return array + */ + private function getWrongCredentialsErrorMessage(): array + { + return [ + 'message' => 'Wrong username or password', + 'token' => null, + ]; + } }