diff --git a/DependencyInjection/Security/Factory/Message/LtiPlatformMessageSecurityFactory.php b/DependencyInjection/Security/Factory/Message/LtiPlatformMessageSecurityFactory.php index adf927c..242e2a2 100644 --- a/DependencyInjection/Security/Factory/Message/LtiPlatformMessageSecurityFactory.php +++ b/DependencyInjection/Security/Factory/Message/LtiPlatformMessageSecurityFactory.php @@ -24,7 +24,11 @@ use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Provider\Message\LtiPlatformMessageAuthenticationProvider; use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiPlatformMessageAuthenticationListener; +use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiPlatformMessageAuthenticator; +use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticator; use OAT\Library\Lti1p3Core\Message\Launch\Validator\Platform\PlatformLaunchValidatorInterface; +use OAT\Library\Lti1p3Core\Security\OAuth2\Validator\RequestAccessTokenValidatorInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; use Symfony\Component\Config\Definition\Builder\NodeDefinition; @@ -57,23 +61,22 @@ public function createAuthenticator( array $config, string $userProviderId ): array|string { - $providerId = sprintf('security.authentication.provider.%s.%s', $this->getKey(), $firewallName); - $providerDefinition = new Definition(LtiPlatformMessageAuthenticationProvider::class); - $providerDefinition + $authenticatorId = sprintf('security.authenticator.%s.%s', $this->getKey(), $firewallName); + $authenticatorDefinition = new Definition(LtiPlatformMessageAuthenticator::class); + $authenticatorDefinition ->setShared(false) ->setArguments( [ + new Reference('security.firewall.map'), + new Reference(HttpMessageFactoryInterface::class), new Reference(PlatformLaunchValidatorInterface::class), $firewallName, $config['types'] ?? [] ] ); - $container->setDefinition($providerId, $providerDefinition); + $container->setDefinition($authenticatorId, $authenticatorDefinition); - $listenerId = sprintf('security.authentication.listener.%s.%s', $this->getKey(), $firewallName); - $container->setDefinition($listenerId, new ChildDefinition(LtiPlatformMessageAuthenticationListener::class)); - - return [$providerId, $listenerId]; + return $authenticatorId; } public function addConfiguration(NodeDefinition $node): void diff --git a/DependencyInjection/Security/Factory/Message/LtiToolMessageSecurityFactory.php b/DependencyInjection/Security/Factory/Message/LtiToolMessageSecurityFactory.php index c48d683..38e478f 100644 --- a/DependencyInjection/Security/Factory/Message/LtiToolMessageSecurityFactory.php +++ b/DependencyInjection/Security/Factory/Message/LtiToolMessageSecurityFactory.php @@ -23,8 +23,13 @@ namespace OAT\Bundle\Lti1p3Bundle\DependencyInjection\Security\Factory\Message; use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Provider\Message\LtiToolMessageAuthenticationProvider; +use OAT\Bundle\Lti1p3Bundle\Security\Exception\LtiToolMessageExceptionHandlerInterface; use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticationListener; +use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticator; +use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Service\LtiServiceAuthenticator; use OAT\Library\Lti1p3Core\Message\Launch\Validator\Tool\ToolLaunchValidatorInterface; +use OAT\Library\Lti1p3Core\Security\OAuth2\Validator\RequestAccessTokenValidatorInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; use Symfony\Component\Config\Definition\Builder\NodeDefinition; @@ -51,23 +56,23 @@ public function createAuthenticator( array $config, string $userProviderId ): array|string { - $providerId = sprintf('security.authentication.provider.%s.%s', $this->getKey(), $firewallName); - $providerDefinition = new Definition(LtiToolMessageAuthenticationProvider::class); - $providerDefinition + $authenticatorId = sprintf('security.authenticator.%s.%s', $this->getKey(), $firewallName); + $authenticatorDefinition = new Definition(LtiToolMessageAuthenticator::class); + $authenticatorDefinition ->setShared(false) ->setArguments( [ + new Reference('security.firewall.map'), + new Reference(HttpMessageFactoryInterface::class), + new Reference(LtiToolMessageExceptionHandlerInterface::class), new Reference(ToolLaunchValidatorInterface::class), $firewallName, $config['types'] ?? [] ] ); - $container->setDefinition($providerId, $providerDefinition); + $container->setDefinition($authenticatorId, $authenticatorDefinition); - $listenerId = sprintf('security.authentication.listener.%s.%s', $this->getKey(), $firewallName); - $container->setDefinition($listenerId, new ChildDefinition(LtiToolMessageAuthenticationListener::class)); - - return [$providerId, $listenerId]; + return $authenticatorId; } public function addConfiguration(NodeDefinition $node): void diff --git a/DependencyInjection/Security/Factory/Service/LtiServiceSecurityFactory.php b/DependencyInjection/Security/Factory/Service/LtiServiceSecurityFactory.php index 9ea6baa..1f67769 100644 --- a/DependencyInjection/Security/Factory/Service/LtiServiceSecurityFactory.php +++ b/DependencyInjection/Security/Factory/Service/LtiServiceSecurityFactory.php @@ -22,13 +22,13 @@ namespace OAT\Bundle\Lti1p3Bundle\DependencyInjection\Security\Factory\Service; -use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Provider\Service\LtiServiceAuthenticationProvider; use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Service\LtiServiceAuthenticationListener; +use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Service\LtiServiceAuthenticator; use OAT\Library\Lti1p3Core\Security\OAuth2\Validator\RequestAccessTokenValidatorInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; use Symfony\Component\Config\Definition\Builder\NodeDefinition; -use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; @@ -51,23 +51,22 @@ public function createAuthenticator( array $config, string $userProviderId ): array|string { - $providerId = sprintf('security.authentication.provider.%s.%s', $this->getKey(), $firewallName); - $providerDefinition = new Definition(LtiServiceAuthenticationProvider::class); - $providerDefinition + $authenticatorId = sprintf('security.authenticator.%s.%s', $this->getKey(), $firewallName); + $authenticatorDefinition = new Definition(LtiServiceAuthenticator::class); + $authenticatorDefinition ->setShared(false) ->setArguments( [ + new Reference('security.firewall.map'), + new Reference(HttpMessageFactoryInterface::class), new Reference(RequestAccessTokenValidatorInterface::class), $firewallName, $config['scopes'] ?? [] ] ); - $container->setDefinition($providerId, $providerDefinition); + $container->setDefinition($authenticatorId, $authenticatorDefinition); - $listenerId = sprintf('security.authentication.listener.%s.%s', $this->getKey(), $firewallName); - $container->setDefinition($listenerId, new ChildDefinition(LtiServiceAuthenticationListener::class)); - - return [$providerId, $listenerId]; + return $authenticatorId; } public function addConfiguration(NodeDefinition $node): void diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml index 11ed26e..d93d55b 100644 --- a/Resources/config/services.yaml +++ b/Resources/config/services.yaml @@ -181,27 +181,27 @@ services: OAT\Bundle\Lti1p3Bundle\Security\Exception\LtiToolMessageExceptionHandlerInterface: alias: OAT\Bundle\Lti1p3Bundle\Security\Exception\LtiToolMessageExceptionHandler - OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiPlatformMessageAuthenticationListener: - arguments: - - '@security.token_storage' - - '@security.authentication.manager' - - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' - - '@security.firewall.map' - - OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticationListener: - arguments: - - '@security.token_storage' - - '@security.authentication.manager' - - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' - - '@OAT\Bundle\Lti1p3Bundle\Security\Exception\LtiToolMessageExceptionHandlerInterface' - - '@security.firewall.map' - - OAT\Bundle\Lti1p3Bundle\Security\Firewall\Service\LtiServiceAuthenticationListener: - arguments: - - '@security.token_storage' - - '@security.authentication.manager' - - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' - - '@security.firewall.map' +# OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiPlatformMessageAuthenticationListener: +# arguments: +# - '@security.token_storage' +## - '@security.authenticator.manager.main' +# - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' +# - '@security.firewall.map' +# +# OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticationListener: +# arguments: +# - '@security.token_storage' +## - '@security.authenticator.manager.main' +# - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' +# - '@OAT\Bundle\Lti1p3Bundle\Security\Exception\LtiToolMessageExceptionHandlerInterface' +# - '@security.firewall.map' +# +# OAT\Bundle\Lti1p3Bundle\Security\Firewall\Service\LtiServiceAuthenticationListener: +# arguments: +# - '@security.token_storage' +## - '@security.authenticator' +# - '@Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface' +# - '@security.firewall.map' OAT\Library\Lti1p3Core\Security\User\UserAuthenticatorInterface: ~ diff --git a/Security/Authentication/Provider/Message/LtiPlatformMessageAuthenticationProvider.php b/Security/Authentication/Provider/Message/LtiPlatformMessageAuthenticationProvider.php deleted file mode 100644 index 36f2b7d..0000000 --- a/Security/Authentication/Provider/Message/LtiPlatformMessageAuthenticationProvider.php +++ /dev/null @@ -1,85 +0,0 @@ -validator = $validator; - $this->firewallName = $firewallName; - $this->types = $types; - } - - public function supports(TokenInterface $token): bool - { - $firewallName = $token->hasAttribute('firewall_config') ? $token->getAttribute('firewall_config')->getName() : null; - - return $token instanceof LtiPlatformMessageSecurityToken && $firewallName === $this->firewallName; - } - - public function authenticate(TokenInterface $token): TokenInterface - { - try { - $validationResult = $this->validator->validateToolOriginatingLaunch($token->getAttribute('request')); - - if ($validationResult->hasError()) { - throw new LtiException($validationResult->getError()); - } - - $messageType = $validationResult->getPayload()->getMessageType(); - - if (!empty($this->types) && !in_array($messageType, $this->types)) { - throw new BadRequestHttpException(sprintf('Invalid LTI message type %s', $messageType)); - } - - return new LtiPlatformMessageSecurityToken($validationResult); - } catch (BadRequestHttpException $exception) { - throw $exception; - } catch (Throwable $exception) { - throw new AuthenticationException( - sprintf('LTI platform message request authentication failed: %s', $exception->getMessage()), - (int) $exception->getCode(), - $exception - ); - } - } -} diff --git a/Security/Authentication/Provider/Message/LtiToolMessageAuthenticationProvider.php b/Security/Authentication/Provider/Message/LtiToolMessageAuthenticationProvider.php deleted file mode 100644 index eb18463..0000000 --- a/Security/Authentication/Provider/Message/LtiToolMessageAuthenticationProvider.php +++ /dev/null @@ -1,85 +0,0 @@ -validator = $validator; - $this->firewallName = $firewallName; - $this->types = $types; - } - - public function supports(TokenInterface $token): bool - { - $firewallName = $token->hasAttribute('firewall_config') ? $token->getAttribute('firewall_config')->getName() : null; - - return $token instanceof LtiToolMessageSecurityToken && $firewallName === $this->firewallName; - } - - public function authenticate(TokenInterface $token): TokenInterface - { - try { - $validationResult = $this->validator->validatePlatformOriginatingLaunch($token->getAttribute('request')); - - if ($validationResult->hasError()) { - throw new LtiException($validationResult->getError()); - } - - $messageType = $validationResult->getPayload()->getMessageType(); - - if (!empty($this->types) && !in_array($messageType, $this->types)) { - throw new BadRequestHttpException(sprintf('Invalid LTI message type %s', $messageType)); - } - - return new LtiToolMessageSecurityToken($validationResult); - } catch (BadRequestHttpException $exception) { - throw $exception; - } catch (Throwable $exception) { - throw new AuthenticationException( - sprintf('LTI tool message request authentication failed: %s', $exception->getMessage()), - (int) $exception->getCode(), - $exception - ); - } - } -} diff --git a/Security/Authentication/Provider/Service/LtiServiceAuthenticationProvider.php b/Security/Authentication/Provider/Service/LtiServiceAuthenticationProvider.php deleted file mode 100644 index 6da76e5..0000000 --- a/Security/Authentication/Provider/Service/LtiServiceAuthenticationProvider.php +++ /dev/null @@ -1,79 +0,0 @@ -validator = $validator; - $this->firewallName = $firewallName; - $this->scopes = $scopes; - } - - public function supports(TokenInterface $token): bool - { - $firewallName = $token->hasAttribute('firewall_config') ? $token->getAttribute('firewall_config')->getName() : null; - - return $token instanceof LtiServiceSecurityToken && $firewallName === $this->firewallName; - } - - public function authenticate(TokenInterface $token): TokenInterface - { - try { - $validationResult = $this->validator->validate( - $token->getAttribute('request'), - $this->scopes - ); - - if ($validationResult->hasError()) { - throw new LtiException($validationResult->getError()); - } - - return new LtiServiceSecurityToken($validationResult); - } catch (Throwable $exception) { - throw new AuthenticationException( - sprintf('LTI service request authentication failed: %s', $exception->getMessage()), - (int) $exception->getCode(), - $exception - ); - } - } -} diff --git a/Security/Authentication/Token/Message/AbstractLtiMessageSecurityToken.php b/Security/Authentication/Token/Message/AbstractLtiMessageSecurityToken.php index 896711d..5644825 100644 --- a/Security/Authentication/Token/Message/AbstractLtiMessageSecurityToken.php +++ b/Security/Authentication/Token/Message/AbstractLtiMessageSecurityToken.php @@ -49,16 +49,12 @@ public function getValidationResult(): ?LaunchValidationResultInterface public function getRegistration(): ?RegistrationInterface { - return $this->validationResult - ? $this->validationResult->getRegistration() - : null; + return $this->validationResult?->getRegistration(); } public function getPayload(): ?LtiMessagePayloadInterface { - return $this->validationResult - ? $this->validationResult->getPayload() - : null; + return $this->validationResult?->getPayload(); } public function getCredentials(): string diff --git a/Security/Authentication/Token/Message/LtiPlatformMessageSecurityToken.php b/Security/Authentication/Token/Message/LtiPlatformMessageSecurityToken.php index fc54b57..1a21888 100644 --- a/Security/Authentication/Token/Message/LtiPlatformMessageSecurityToken.php +++ b/Security/Authentication/Token/Message/LtiPlatformMessageSecurityToken.php @@ -23,6 +23,7 @@ namespace OAT\Bundle\Lti1p3Bundle\Security\Authentication\Token\Message; use OAT\Library\Lti1p3Core\Message\Launch\Validator\Result\LaunchValidationResultInterface; +use Symfony\Component\Security\Core\User\InMemoryUser; class LtiPlatformMessageSecurityToken extends AbstractLtiMessageSecurityToken { @@ -33,9 +34,8 @@ protected function applyValidationResult(?LaunchValidationResultInterface $valid $this->roleNames = []; if (null !== $this->validationResult) { - $this->setAuthenticated(!$this->validationResult->hasError()); - } else { - $this->setAuthenticated(false); + $user = new InMemoryUser('lti platform', null); + $this->setUser($user); } } } diff --git a/Security/Authentication/Token/Message/LtiToolMessageSecurityToken.php b/Security/Authentication/Token/Message/LtiToolMessageSecurityToken.php index efc4cdd..8c032ff 100644 --- a/Security/Authentication/Token/Message/LtiToolMessageSecurityToken.php +++ b/Security/Authentication/Token/Message/LtiToolMessageSecurityToken.php @@ -24,14 +24,13 @@ use OAT\Library\Lti1p3Core\Message\Launch\Validator\Result\LaunchValidationResultInterface; use OAT\Library\Lti1p3Core\Message\Payload\MessagePayloadInterface; +use Symfony\Component\Security\Core\User\InMemoryUser; class LtiToolMessageSecurityToken extends AbstractLtiMessageSecurityToken { public function getState(): ?MessagePayloadInterface { - return $this->validationResult - ? $this->validationResult->getState() - : null; + return $this->validationResult?->getState(); } protected function applyValidationResult(?LaunchValidationResultInterface $validationResult = null): void @@ -41,21 +40,18 @@ protected function applyValidationResult(?LaunchValidationResultInterface $valid if (null !== $this->validationResult) { $payload = $this->validationResult->getPayload(); - if (null !== $payload) { + if (null !== $payload && !$this->validationResult->hasError()) { $userIdentity = $payload->getUserIdentity(); if (null !== $userIdentity) { - $this->setUser($userIdentity->getIdentifier()); + $user = new InMemoryUser($userIdentity->getIdentifier(), null); + $this->setUser($user); } $this->roleNames = $payload->getRoles(); } - - $this->setAuthenticated(!$this->validationResult->hasError()); } else { $this->roleNames = []; - - $this->setAuthenticated(false); } } } diff --git a/Security/Authentication/Token/Service/LtiServiceSecurityToken.php b/Security/Authentication/Token/Service/LtiServiceSecurityToken.php index 7b36fd6..5fae2cd 100644 --- a/Security/Authentication/Token/Service/LtiServiceSecurityToken.php +++ b/Security/Authentication/Token/Service/LtiServiceSecurityToken.php @@ -26,6 +26,8 @@ use OAT\Library\Lti1p3Core\Security\Jwt\TokenInterface; use OAT\Library\Lti1p3Core\Security\OAuth2\Validator\Result\RequestAccessTokenValidationResultInterface; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\UserInterface; class LtiServiceSecurityToken extends AbstractToken { @@ -90,17 +92,18 @@ private function applyValidationResult(?RequestAccessTokenValidationResultInterf $registration = $this->validationResult->getRegistration(); - if (null !== $registration) { - $this->setUser($registration->getTool()->getName()); + if (!$this->validationResult->hasError()) { + if (null !== $registration) { + $user = new InMemoryUser($registration->getTool()->getName(), null); + } else { + $user = new InMemoryUser('lti tool', null); + } + $this->setUser($user); } $this->roleNames = $this->validationResult->getScopes(); - - $this->setAuthenticated(!$this->validationResult->hasError()); } else { $this->roleNames = []; - - $this->setAuthenticated(false); } } } diff --git a/Security/Firewall/Message/LtiPlatformMessageAuthenticationListener.php b/Security/Firewall/Message/LtiPlatformMessageAuthenticationListener.php deleted file mode 100644 index 96c0287..0000000 --- a/Security/Firewall/Message/LtiPlatformMessageAuthenticationListener.php +++ /dev/null @@ -1,85 +0,0 @@ -storage = $tokenStorage; - $this->manager = $authenticationManager; - $this->factory = $factory; - $this->firewallMap = $firewallMap; - } - - public function supports(Request $request): ?bool - { - return null !== $this->getJwtFromRequest($request); - } - - public function authenticate(RequestEvent $event): void - { - $request = $event->getRequest(); - - $token = new LtiPlatformMessageSecurityToken(); - $token->setAttribute('request', $this->factory->createRequest($request)); - $token->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); - - $this->storage->setToken($this->manager->authenticate($token)); - } - - private function getJwtFromRequest(Request $request): ?string - { - $jwtFromQuery = $request->query->get('JWT'); - if (null !== $jwtFromQuery) { - return $jwtFromQuery; - } - - return $request->request->get('JWT'); - } -} diff --git a/Security/Firewall/Message/LtiPlatformMessageAuthenticator.php b/Security/Firewall/Message/LtiPlatformMessageAuthenticator.php new file mode 100644 index 0000000..ca375f9 --- /dev/null +++ b/Security/Firewall/Message/LtiPlatformMessageAuthenticator.php @@ -0,0 +1,144 @@ +factory = $factory; + $this->firewallMap = $firewallMap; + $this->validator = $validator; + $this->firewallName = $firewallName; + $this->types = $types; + } + + public function supports(Request $request): ?bool + { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + + return null !== $this->getJwtFromRequest($request) && $firewallConfig?->getName() === $this->firewallName; + } + + public function authenticate(Request $request): Passport + { + $username = 'lti-platform'; + + $passport = new SelfValidatingPassport(new UserBadge($username), [ + new PreAuthenticatedUserBadge() + ]); + + $passport->setAttribute('request', $this->factory->createRequest($request)); + $passport->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); + + return $passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + try { + $validationResult = $this->validator->validateToolOriginatingLaunch($passport->getAttribute('request')); + + if ($validationResult->hasError()) { + throw new LtiException($validationResult->getError()); + } + + $messageType = $validationResult->getPayload()->getMessageType(); + + if (!empty($this->types) && !in_array($messageType, $this->types)) { + throw new BadRequestHttpException(sprintf('Invalid LTI message type %s', $messageType)); + } + + $token = new LtiPlatformMessageSecurityToken($validationResult); + $token->setAttribute('request', $passport->getAttribute('request')); + $token->setAttribute('firewall_config', $passport->getAttribute('firewall_config')); + + return $token; + } catch (BadRequestHttpException $exception) { + throw $exception; + } catch (\Throwable $exception) { + throw new AuthenticationException( + sprintf('LTI platform message request authentication failed: %s', $exception->getMessage()), + (int) $exception->getCode(), + $exception + ); + } + } + + private function getJwtFromRequest(Request $request): ?string + { + $jwtFromQuery = $request->query->get('JWT'); + if (null !== $jwtFromQuery) { + return $jwtFromQuery; + } + + return $request->request->get('JWT'); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return new JsonResponse([ + 'error' => [ + 'message' => strtr($exception->getMessage(), $exception->getMessageData()), + ], + ], Response::HTTP_UNAUTHORIZED); + } +} diff --git a/Security/Firewall/Message/LtiToolMessageAuthenticationListener.php b/Security/Firewall/Message/LtiToolMessageAuthenticationListener.php deleted file mode 100644 index 0d665cc..0000000 --- a/Security/Firewall/Message/LtiToolMessageAuthenticationListener.php +++ /dev/null @@ -1,96 +0,0 @@ -storage = $tokenStorage; - $this->manager = $authenticationManager; - $this->factory = $factory; - $this->handler = $handler; - $this->firewallMap = $firewallMap; - } - - public function supports(Request $request): ?bool - { - return null !== $this->getIdTokenFromRequest($request); - } - - public function authenticate(RequestEvent $event): void - { - $request = $event->getRequest(); - - $token = new LtiToolMessageSecurityToken(); - $token->setAttribute('request', $this->factory->createRequest($request)); - $token->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); - - try { - $this->storage->setToken($this->manager->authenticate($token)); - } catch (Throwable $exception) { - $event->setResponse($this->handler->handle($exception, $request)); - } - } - - private function getIdTokenFromRequest(Request $request): ?string - { - $idTokenFromQuery = $request->query->get('id_token'); - if (null !== $idTokenFromQuery) { - return $idTokenFromQuery; - } - - return $request->request->get('id_token'); - } -} diff --git a/Security/Firewall/Message/LtiToolMessageAuthenticator.php b/Security/Firewall/Message/LtiToolMessageAuthenticator.php new file mode 100644 index 0000000..5c1eaad --- /dev/null +++ b/Security/Firewall/Message/LtiToolMessageAuthenticator.php @@ -0,0 +1,156 @@ +factory = $factory; + $this->handler = $handler; + $this->firewallMap = $firewallMap; + $this->validator = $validator; + $this->firewallName = $firewallName; + $this->types = $types; + } + + public function supports(Request $request): ?bool + { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + + return null !== $this->getIdTokenFromRequest($request) && $firewallConfig?->getName() === $this->firewallName; + } + + public function authenticate(Request $request): Passport + { +// $request = $event->getRequest(); + +// $token = new LtiToolMessageSecurityToken(); +// $token->setAttribute('request', $this->factory->createRequest($request)); +// $token->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); + + $username = 'lti-tool'; + + $passport = new SelfValidatingPassport(new UserBadge($username), [ + new PreAuthenticatedUserBadge() + ]); + + $passport->setAttribute('request', $this->factory->createRequest($request)); + $passport->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); + + return $passport; + } + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + try { + $validationResult = $this->validator->validatePlatformOriginatingLaunch($passport->getAttribute('request')); + + if ($validationResult->hasError()) { + throw new LtiException($validationResult->getError()); + } + + $messageType = $validationResult->getPayload()->getMessageType(); + + if (!empty($this->types) && !in_array($messageType, $this->types)) { + throw new BadRequestHttpException(sprintf('Invalid LTI message type %s', $messageType)); + } + + $token = new LtiToolMessageSecurityToken($validationResult); + $token->setAttribute('request', $passport->getAttribute('request')); + $token->setAttribute('firewall_config', $passport->getAttribute('firewall_config')); + + return $token; + } catch (BadRequestHttpException $exception) { + throw $exception; + } catch (\Throwable $exception) { + throw new AuthenticationException( + sprintf('LTI tool message request authentication failed: %s', $exception->getMessage()), + (int) $exception->getCode(), + $exception + ); + } + } + + private function getIdTokenFromRequest(Request $request): ?string + { + $idTokenFromQuery = $request->query->get('id_token'); + if (null !== $idTokenFromQuery) { + return $idTokenFromQuery; + } + + return $request->request->get('id_token'); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return $this->handler->handle($exception, $request); +// return new JsonResponse([ +// 'error' => [ +// 'message' => strtr($exception->getMessage(), $exception->getMessageData()), +// ], +// ], Response::HTTP_UNAUTHORIZED); + } +} diff --git a/Security/Firewall/Service/LtiServiceAuthenticationListener.php b/Security/Firewall/Service/LtiServiceAuthenticationListener.php deleted file mode 100644 index 84e11e6..0000000 --- a/Security/Firewall/Service/LtiServiceAuthenticationListener.php +++ /dev/null @@ -1,75 +0,0 @@ -storage = $tokenStorage; - $this->manager = $authenticationManager; - $this->factory = $factory; - $this->firewallMap = $firewallMap; - } - - public function supports(Request $request): ?bool - { - return $request->headers->has('Authorization'); - } - - public function authenticate(RequestEvent $event): void - { - $request = $event->getRequest(); - - $token = new LtiServiceSecurityToken(); - $token->setAttribute('request', $this->factory->createRequest($request)); - $token->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); - - $this->storage->setToken($this->manager->authenticate($token)); - } -} diff --git a/Security/Firewall/Service/LtiServiceAuthenticator.php b/Security/Firewall/Service/LtiServiceAuthenticator.php new file mode 100644 index 0000000..1a7ba9a --- /dev/null +++ b/Security/Firewall/Service/LtiServiceAuthenticator.php @@ -0,0 +1,132 @@ +factory = $factory; + $this->firewallMap = $firewallMap; + $this->validator = $validator; + $this->firewallName = $firewallName; + $this->scopes = $scopes; + } + + public function supports(Request $request): ?bool + { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + + return $request->headers->has('Authorization') && $firewallConfig?->getName() === $this->firewallName; + } + + public function authenticate(Request $request): Passport + { + if (!$request->headers->has('Authorization')) { + throw new AuthenticationException('Authorization header is missing'); + } + + $username = 'lti-service'; + + $passport = new SelfValidatingPassport(new UserBadge($username), [ + new PreAuthenticatedUserBadge() + ]); + + $passport->setAttribute('request', $this->factory->createRequest($request)); + $passport->setAttribute('firewall_config', $this->firewallMap->getFirewallConfig($request)); + + return $passport; + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + try { + $validationResult = $this->validator->validate( + $passport->getAttribute('request'), + $this->scopes + ); + + if ($validationResult->hasError()) { + throw new LtiException($validationResult->getError()); + } + + $token = new LtiServiceSecurityToken($validationResult); + $token->setAttribute('request', $passport->getAttribute('request')); + $token->setAttribute('firewall_config', $passport->getAttribute('firewall_config')); + + return $token; + } catch (\Throwable $exception) { + throw new AuthenticationException( + sprintf('LTI service request authentication failed: %s', $exception->getMessage()), + (int) $exception->getCode(), + $exception + ); + } + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return new JsonResponse([ + 'error' => [ + 'message' => strtr($exception->getMessage(), $exception->getMessageData()), + ], + ], Response::HTTP_UNAUTHORIZED); + } +} diff --git a/Service/Server/Handler/LtiServiceServerHttpFoundationRequestHandler.php b/Service/Server/Handler/LtiServiceServerHttpFoundationRequestHandler.php index c1f5d46..63934aa 100644 --- a/Service/Server/Handler/LtiServiceServerHttpFoundationRequestHandler.php +++ b/Service/Server/Handler/LtiServiceServerHttpFoundationRequestHandler.php @@ -31,6 +31,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\Security; use Throwable; @@ -102,6 +103,10 @@ public function __invoke(Request $request): Response /** @var LtiServiceSecurityToken $token */ $token = $this->security->getToken(); + if ($token === null) { + throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); + } + $handlerResponse = $this->handler->handleValidatedServiceRequest( $token->getValidationResult(), $this->psr7Factory->createRequest($request), diff --git a/Tests/Functional/Action/Platform/Message/OidcAuthenticationActionTest.php b/Tests/Functional/Action/Platform/Message/OidcAuthenticationActionTest.php index f3e8da7..41215e4 100644 --- a/Tests/Functional/Action/Platform/Message/OidcAuthenticationActionTest.php +++ b/Tests/Functional/Action/Platform/Message/OidcAuthenticationActionTest.php @@ -53,7 +53,7 @@ protected function setUp(): void $this->resetTestLogger(); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); } @@ -68,7 +68,7 @@ protected function tearDown(): void public function testValidOidcAuthenticationWithPostMethod(): void { /** @var LtiMessageInterface $message */ - $message = static::$container->get(LtiResourceLinkLaunchRequestBuilder::class)->buildLtiResourceLinkLaunchRequest( + $message = static::getContainer()->get(LtiResourceLinkLaunchRequestBuilder::class)->buildLtiResourceLinkLaunchRequest( new LtiResourceLink('resourceLinkIdentifier'), $this->registration, 'loginHint' @@ -100,7 +100,7 @@ public function testValidOidcAuthenticationWithPostMethod(): void public function testValidOidcAuthenticationWithGetMethod(): void { /** @var LtiMessageInterface $message */ - $message = static::$container->get(LtiResourceLinkLaunchRequestBuilder::class)->buildLtiResourceLinkLaunchRequest( + $message = static::getContainer()->get(LtiResourceLinkLaunchRequestBuilder::class)->buildLtiResourceLinkLaunchRequest( new LtiResourceLink('resourceLinkIdentifier'), $this->registration, 'loginHint' diff --git a/Tests/Functional/Action/Platform/Service/OAuth2AccessTokenCreationActionTest.php b/Tests/Functional/Action/Platform/Service/OAuth2AccessTokenCreationActionTest.php index fcb1277..56e5b2b 100644 --- a/Tests/Functional/Action/Platform/Service/OAuth2AccessTokenCreationActionTest.php +++ b/Tests/Functional/Action/Platform/Service/OAuth2AccessTokenCreationActionTest.php @@ -55,11 +55,11 @@ protected function setUp(): void $this->resetTestLogger(); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); - $this->keyChain = static::$container + $this->keyChain = static::getContainer() ->get(KeyChainRepositoryInterface::class) ->find('kid1'); } diff --git a/Tests/Functional/Action/Tool/Message/OidcInitiationActionTest.php b/Tests/Functional/Action/Tool/Message/OidcInitiationActionTest.php index 77c460b..0dd50ff 100644 --- a/Tests/Functional/Action/Tool/Message/OidcInitiationActionTest.php +++ b/Tests/Functional/Action/Tool/Message/OidcInitiationActionTest.php @@ -48,7 +48,7 @@ protected function setUp(): void $this->resetTestLogger(); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); } diff --git a/Tests/Functional/Flow/Message/LtiPlatformOriginatingMessageFlowTest.php b/Tests/Functional/Flow/Message/LtiPlatformOriginatingMessageFlowTest.php index 13b2a13..0291b9c 100644 --- a/Tests/Functional/Flow/Message/LtiPlatformOriginatingMessageFlowTest.php +++ b/Tests/Functional/Flow/Message/LtiPlatformOriginatingMessageFlowTest.php @@ -58,9 +58,9 @@ class LtiPlatformOriginatingMessageFlowTest extends WebTestCase protected function setUp(): void { $this->client = static::createClient(); - $this->builder = static::$container->get(LtiResourceLinkLaunchRequestBuilder::class); + $this->builder = static::getContainer()->get(LtiResourceLinkLaunchRequestBuilder::class); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); } @@ -239,7 +239,7 @@ public function testItFailsWithExpiredIdToken(): void // Step 4 - Tool message validation /** @var MessagePayloadBuilderInterface $messagePayloadBuilder */ - $messagePayloadBuilder = static::$container->get(MessagePayloadBuilderInterface::class); + $messagePayloadBuilder = static::getContainer()->get(MessagePayloadBuilderInterface::class); Carbon::setTestNow(Carbon::now()->subSeconds(LtiMessagePayloadInterface::TTL + 1)); @@ -323,7 +323,7 @@ public function testItFailsWithExpiredIdTokenAndRedirection(): void // Step 4 - Tool message validation /** @var MessagePayloadBuilderInterface $messagePayloadBuilder */ - $messagePayloadBuilder = static::$container->get(MessagePayloadBuilderInterface::class); + $messagePayloadBuilder = static::getContainer()->get(MessagePayloadBuilderInterface::class); Carbon::setTestNow(Carbon::now()->subSeconds(LtiMessagePayloadInterface::TTL + 1)); diff --git a/Tests/Functional/Flow/Message/LtiToolOriginatingMessageFlowTest.php b/Tests/Functional/Flow/Message/LtiToolOriginatingMessageFlowTest.php index 1907e23..227a9ed 100644 --- a/Tests/Functional/Flow/Message/LtiToolOriginatingMessageFlowTest.php +++ b/Tests/Functional/Flow/Message/LtiToolOriginatingMessageFlowTest.php @@ -54,9 +54,9 @@ class LtiToolOriginatingMessageFlowTest extends WebTestCase protected function setUp(): void { $this->client = static::createClient(); - $this->builder = static::$container->get(ToolOriginatingLaunchBuilder::class); + $this->builder = static::getContainer()->get(ToolOriginatingLaunchBuilder::class); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); } diff --git a/Tests/Functional/Flow/Service/LtiServiceFlowTest.php b/Tests/Functional/Flow/Service/LtiServiceFlowTest.php index 08c3810..7da280f 100644 --- a/Tests/Functional/Flow/Service/LtiServiceFlowTest.php +++ b/Tests/Functional/Flow/Service/LtiServiceFlowTest.php @@ -47,7 +47,7 @@ protected function setUp(): void { $this->client = static::createClient(); - $this->registration = static::$container + $this->registration = static::getContainer() ->get(RegistrationRepositoryInterface::class) ->find('testRegistration'); } @@ -214,7 +214,7 @@ public function testItReturnsUnauthorizedResponseWithInvalidScopes(): void public function testItReturnsUnauthorizedResponseWithoutBearer(): void { - $this->client->request(Request::METHOD_GET, '/test/service'); + $this->client->request(Request::METHOD_GET, '/test/service', [], [], ['HTTP_ACCEPT' => 'application/json']); $response = $this->client->getResponse(); $this->assertInstanceOf(Response::class, $response); diff --git a/Tests/Integration/DependencyInjection/Builder/KeyChainRepositoryBuilderTest.php b/Tests/Integration/DependencyInjection/Builder/KeyChainRepositoryBuilderTest.php index 360c33e..d4cf67a 100644 --- a/Tests/Integration/DependencyInjection/Builder/KeyChainRepositoryBuilderTest.php +++ b/Tests/Integration/DependencyInjection/Builder/KeyChainRepositoryBuilderTest.php @@ -40,7 +40,7 @@ protected function setUp(): void static::bootKernel(); - $this->repository = static::$container->get(KeyChainRepositoryInterface::class); + $this->repository = static::getContainer()->get(KeyChainRepositoryInterface::class); } public function testBuildRepositoryClass(): void diff --git a/Tests/Integration/DependencyInjection/Builder/RegistrationRepositoryBuilderPassTest.php b/Tests/Integration/DependencyInjection/Builder/RegistrationRepositoryBuilderPassTest.php index ea4602a..5fca3a7 100644 --- a/Tests/Integration/DependencyInjection/Builder/RegistrationRepositoryBuilderPassTest.php +++ b/Tests/Integration/DependencyInjection/Builder/RegistrationRepositoryBuilderPassTest.php @@ -58,7 +58,7 @@ public function testBuiltContainerRepositoryCanFindRegistration(): void { static::bootKernel(); - $repository = static::$container->get(RegistrationRepositoryInterface::class); + $repository = static::getContainer()->get(RegistrationRepositoryInterface::class); $this->assertInstanceOf(RegistrationRepository::class, $repository); diff --git a/Tests/Integration/DependencyInjection/Compiler/ConfigurationPassTest.php b/Tests/Integration/DependencyInjection/Compiler/ConfigurationPassTest.php index a21db9b..41e97d1 100644 --- a/Tests/Integration/DependencyInjection/Compiler/ConfigurationPassTest.php +++ b/Tests/Integration/DependencyInjection/Compiler/ConfigurationPassTest.php @@ -39,7 +39,7 @@ protected function setUp(): void static::bootKernel(); - $this->repository = static::$container->get(ScopeRepositoryInterface::class); + $this->repository = static::getContainer()->get(ScopeRepositoryInterface::class); } public function testConfiguredScopesAreAvailableInScopeRepositoryInstance(): void diff --git a/Tests/Integration/Repository/RegistrationRepositoryTest.php b/Tests/Integration/Repository/RegistrationRepositoryTest.php index 143a66e..c302cff 100644 --- a/Tests/Integration/Repository/RegistrationRepositoryTest.php +++ b/Tests/Integration/Repository/RegistrationRepositoryTest.php @@ -39,7 +39,7 @@ protected function setUp(): void static::bootKernel(); - $this->subject = static::$container->get(RegistrationRepositoryInterface::class); + $this->subject = static::getContainer()->get(RegistrationRepositoryInterface::class); } public function testFind(): void diff --git a/Tests/Resources/Kernel/config/security.yaml b/Tests/Resources/Kernel/config/security.yaml index 5d9ae41..4e811ff 100644 --- a/Tests/Resources/Kernel/config/security.yaml +++ b/Tests/Resources/Kernel/config/security.yaml @@ -16,7 +16,7 @@ security: stateless: true lti1p3_service: { scopes: ['allowed-scope'] } main: - anonymous: lazy + lazy: true provider: users_in_memory - access_control: + access_control: \ No newline at end of file diff --git a/Tests/Traits/LoggerTestingTrait.php b/Tests/Traits/LoggerTestingTrait.php index 01550b3..a1ed7bb 100644 --- a/Tests/Traits/LoggerTestingTrait.php +++ b/Tests/Traits/LoggerTestingTrait.php @@ -36,7 +36,7 @@ protected function assertHasLogRecords($level): void $this->checkLoggerTestingTraitUsage(); $this->assertTrue( - static::$container->get(LoggerInterface::class)->hasRecords($level), + static::getContainer()->get(LoggerInterface::class)->hasRecords($level), sprintf( 'Failed asserting that logger contains records for level %s', $level @@ -52,7 +52,7 @@ protected function assertHasLogRecord($record, $level): void $this->checkLoggerTestingTraitUsage(); $this->assertTrue( - static::$container->get(LoggerInterface::class)->hasRecord($record, $level), + static::getContainer()->get(LoggerInterface::class)->hasRecord($record, $level), sprintf( 'Failed asserting that logger contains record: [%s] %s', $level, @@ -69,7 +69,7 @@ protected function assertHasLogRecordThatContains($record, $level): void $this->checkLoggerTestingTraitUsage(); $this->assertTrue( - static::$container->get(LoggerInterface::class)->hasRecordThatContains($record, $level), + static::getContainer()->get(LoggerInterface::class)->hasRecordThatContains($record, $level), sprintf( 'Failed asserting that logger contains record containing: [%s] %s', $level, @@ -85,7 +85,7 @@ protected function resetTestLogger(): void { $this->checkLoggerTestingTraitUsage(); - static::$container->get(LoggerInterface::class)->reset(); + static::getContainer()->get(LoggerInterface::class)->reset(); } /**