diff --git a/.gitignore b/.gitignore index 294e396..457b22c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /public/bundles/ /var/ /vendor/ +/.idea/ ###< symfony/framework-bundle ### ###> phpunit/phpunit ### diff --git a/config/jwt/private.key b/config/jwt/private.key new file mode 100644 index 0000000..fab44e5 --- /dev/null +++ b/config/jwt/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1mXotmeuIDbQS +Ad+mK28GEahfpOLGvU/rO4HzgK4YuftiGaNf1fB/Y4ZmlnhVp9qEg0qTe8yDAhWC +ekk/WR4oTyEpUFcCbzGPS9xLks/VGTj/78PdjaznqOrDtWGm1azTHBn/4DSvV4/j +D6BDJ4CKqvd5pRXiXhAbUBR1l2GBRY9/0jYzyAICe1Gpi+X+3BF0OovqmCmX6EhQ +ypC5gNftk8oOaKaXi2PaqkQcn3h6P3YdRe07yCTOdPl8eHtvMTZy8jeTBiMEjZj+ +xlfv501EZPc1XBwjbTzYI1TK2ztEyjApKj/1BaCpP6w2lBbdYwR40EI5eiHS29B/ +icZRY7StAgMBAAECggEAHU4Y9SPqFNNf9Cn2PTq0GLl1FN4nCbrsxdadXWiuGj0P +r+CC80kIct43hvWQPK5bfoKGCy/GrHuDx2xUG/4Vy5vIC600eqbr9fu1dJf1Tvuv +gumzugc4raJIzu/wbrumPzkWiaqqMIODq44kJsId+wKk8K3vI/pZmgwTCSOL2hAp +2BeFRt1jMjsoFaBap/T8RS4F77WMOWsPkNEzisUm4yvAiOsrLWja43vMKunuJyGM +isjGi7gFWfcS2VYk31huo1OlbMcz9ytKSOZiw5Hd8XxKoAATkGD7CQ0uuEKhX49t +MJrH3vGWFRisIdeLd287XEo2YJ3MwZJiUzZo2Bf0NwKBgQDblFHLBsuXfI+y3f4a +ktkvziCwQYPzYsZF9W5/bxS/mTVvvuWqylPyi6NhO8gtICrjcLwD2tkbjnPV5mal +B9So3lh4TkixrLNCN++fcCuhuKvyks1lU8xIl6a474hiMkBo9Wzr+B8t1Oliy9pL +5IFlMqz13Gq5yo0sj6zVUw8P+wKBgQDTuHlvMEtSlJStJHwSq06OcLcharp3EI30 +8xr+JfzBUBSpCXI02HOTVZYXD8QoZIhTlbFKgonN7NeRyWtLVfQ4kwpcLbNkLJH2 +uOE56gC98i583zN4uTFOL1oC/gwCbppdiZ2VE6etaXPn0DrZM61g0U4jm8tNLcl4 +BEJU1/wldwKBgHjjWnitYA8hq7dtEoWszVfNYx/Gog+wFLrVWaVdEY4+mjXQYn85 +7ye8ixFwKU/2wsX+/fQdW6QZNFrSAzbebc0exJRPfSQckYBmbU1ZIxxhIIFnIx+j +F/frTgXJEkwFoIJohDQRoZDJBEi5NJDN2BNP5/tgA34QLtMWsq+rj8JbAoGBAIdw +Bx69wjF9ou5v3H8E3yf3qu7Rm573FBiSO75BBsOTOuQ3iruLi8PAiFcQWueMCDmQ +FO4ZO5Zj4DL+qohy39whFAuLoKqAaI9wDYRC0V6xQlPXZNHhhk0BtY8cfQpBPrZ/ +hjMLc8RXJTIx3rN7f3nj6xyUWSVyGOORte0YjdBZAoGBAJsrB8ZJePeQpP3uZKN5 +iUyOQD8r+MJZjlSHscFbJIdX7Z9X2X9wjeRV4iAQ2dSfDbw7okGIThVjiKFBSvtp +bAoycAQ8qSJS6FBg6U9L2z03P2HfXJ7JyLVrmZXuQ10gRLf7Z6Ukf8mqIVCAtICA +PU3C7sGZxVplUdvJ2w3Jf4GX +-----END PRIVATE KEY----- diff --git a/config/jwt/public.key b/config/jwt/public.key new file mode 100644 index 0000000..2c13531 --- /dev/null +++ b/config/jwt/public.key @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtZl6LZnriA20EgHfpitv +BhGoX6Tixr1P6zuB84CuGLn7YhmjX9Xwf2OGZpZ4VafahINKk3vMgwIVgnpJP1ke +KE8hKVBXAm8xj0vcS5LP1Rk4/+/D3Y2s56jqw7VhptWs0xwZ/+A0r1eP4w+gQyeA +iqr3eaUV4l4QG1AUdZdhgUWPf9I2M8gCAntRqYvl/twRdDqL6pgpl+hIUMqQuYDX +7ZPKDmiml4tj2qpEHJ94ej92HUXtO8gkznT5fHh7bzE2cvI3kwYjBI2Y/sZX7+dN +RGT3NVwcI2082CNUyts7RMowKSo/9QWgqT+sNpQW3WMEeNBCOXoh0tvQf4nGUWO0 +rQIDAQAB +-----END PUBLIC KEY----- diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index b81d06a..3ede03e 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,11 +1,16 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: secret: '%env(APP_SECRET)%' + http_method_override: false + handle_all_throwables: true # Note that the session will be started ONLY if you read or write from it. session: - cookie_domain: '.solutions-easy.moi' + #cookie_domain: '.solutions-easy.moi' cookie_samesite: lax # Use 'none' only with HTTPS + handler_id: null + cookie_secure: auto + storage_factory_id: session.storage.factory.native #esi: true #fragments: true diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 5178832..7533e6f 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -8,31 +8,16 @@ security: app_user_provider: entity: class: App\Entity\User - property: username + property: email firewalls: - api: - pattern: ^/oauth2/api - security: true - stateless: true - oauth2: true - token: - pattern: ^/token - security: false - oauth2_token: - pattern: ^/oauth2/token - security: false dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false - authorize: - pattern: ^/authorize - lazy: true - provider: app_user_provider - form_login: - login_path: app_login - check_path: app_login - enable_csrf: true - default_target_path: /authorize + api: + pattern: ^/oauth/api + security: true + stateless: true + oauth2: true main: lazy: true provider: app_user_provider @@ -40,7 +25,8 @@ security: login_path: app_login check_path: app_login enable_csrf: true - default_target_path: app_login_check + default_target_path: app_home + use_referer: true logout: path: app_logout target: app_login @@ -55,12 +41,9 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/login, roles: PUBLIC_ACCESS } - - { path: ^/login/check, roles: ROLE_USER } - - { path: ^/authorize, roles: ROLE_USER } - - { path: ^/oauth2/authorize, roles: ROLE_USER } - - { path: ^/oauth2/api, roles: PUBLIC_ACCESS } - { path: ^/token, roles: PUBLIC_ACCESS } - { path: ^/oauth2/token, roles: PUBLIC_ACCESS } + - { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED } - { path: ^/oauth2/userinfo, roles: IS_AUTHENTICATED_FULLY } - { path: ^/, roles: ROLE_USER } diff --git a/config/services.yaml b/config/services.yaml index 0fb9afc..c18468a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -21,17 +21,4 @@ services: - '../src/Kernel.php' # add more service definitions when explicit configuration is needed - # please note that last definitions always *replace* previous ones - - # OAuth2 repositories - App\Repository\ClientRepository: - tags: ['league.oauth2_server.repository'] - - App\Repository\AccessTokenRepository: - tags: ['league.oauth2_server.repository'] - - App\Repository\RefreshTokenRepository: - tags: ['league.oauth2_server.repository'] - - App\Repository\AuthCodeRepository: - tags: ['league.oauth2_server.repository'] + # please note that last definitions always *replace* previous ones \ No newline at end of file diff --git a/migrations/Version20250327181349.php b/migrations/Version20250327181349.php new file mode 100644 index 0000000..7f04d92 --- /dev/null +++ b/migrations/Version20250327181349.php @@ -0,0 +1,69 @@ +addSql('CREATE TABLE auth_code (identifier VARCHAR(80) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_identifier VARCHAR(255) DEFAULT NULL, client_identifier VARCHAR(32) NOT NULL, redirect_uri VARCHAR(255) DEFAULT NULL, revoked BOOLEAN NOT NULL, scopes JSON NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('COMMENT ON COLUMN auth_code.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE client (identifier VARCHAR(32) NOT NULL, secret VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, active BOOLEAN NOT NULL, redirect_uris JSON NOT NULL, grants JSON NOT NULL, scopes JSON NOT NULL, allow_plain_text_pkce BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('CREATE TABLE oauth2_access_token (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL, revoked BOOLEAN NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('CREATE INDEX IDX_454D9673C7440455 ON oauth2_access_token (client)'); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('CREATE TABLE oauth2_authorization_code (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL, revoked BOOLEAN NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('CREATE INDEX IDX_509FEF5FC7440455 ON oauth2_authorization_code (client)'); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('CREATE TABLE oauth2_client (identifier VARCHAR(32) NOT NULL, name VARCHAR(128) NOT NULL, secret VARCHAR(128) DEFAULT NULL, redirect_uris TEXT DEFAULT NULL, grants TEXT DEFAULT NULL, scopes TEXT DEFAULT NULL, active BOOLEAN NOT NULL, allow_plain_text_pkce BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('COMMENT ON COLUMN oauth2_client.redirect_uris IS \'(DC2Type:oauth2_redirect_uri)\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.grants IS \'(DC2Type:oauth2_grant)\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('CREATE TABLE oauth2_refresh_token (identifier CHAR(80) NOT NULL, access_token CHAR(80) DEFAULT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, revoked BOOLEAN NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('CREATE INDEX IDX_4DD90732B6A2DD68 ON oauth2_refresh_token (access_token)'); + $this->addSql('COMMENT ON COLUMN oauth2_refresh_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE refresh_token (identifier VARCHAR(80) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, access_token_identifier VARCHAR(80) NOT NULL, revoked BOOLEAN NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('COMMENT ON COLUMN refresh_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE oauth2_access_token ADD CONSTRAINT FK_454D9673C7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE oauth2_authorization_code ADD CONSTRAINT FK_509FEF5FC7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE oauth2_refresh_token ADD CONSTRAINT FK_4DD90732B6A2DD68 FOREIGN KEY (access_token) REFERENCES oauth2_access_token (identifier) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "user" ADD roles JSON NOT NULL'); + $this->addSql('ALTER TABLE "user" ALTER username TYPE VARCHAR(180)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON "user" (username)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE oauth2_access_token DROP CONSTRAINT FK_454D9673C7440455'); + $this->addSql('ALTER TABLE oauth2_authorization_code DROP CONSTRAINT FK_509FEF5FC7440455'); + $this->addSql('ALTER TABLE oauth2_refresh_token DROP CONSTRAINT FK_4DD90732B6A2DD68'); + $this->addSql('DROP TABLE auth_code'); + $this->addSql('DROP TABLE client'); + $this->addSql('DROP TABLE oauth2_access_token'); + $this->addSql('DROP TABLE oauth2_authorization_code'); + $this->addSql('DROP TABLE oauth2_client'); + $this->addSql('DROP TABLE oauth2_refresh_token'); + $this->addSql('DROP TABLE refresh_token'); + $this->addSql('DROP INDEX UNIQ_8D93D649F85E0677'); + $this->addSql('ALTER TABLE "user" DROP roles'); + $this->addSql('ALTER TABLE "user" ALTER username TYPE VARCHAR(255)'); + } +} diff --git a/migrations/Version20250327185727.php b/migrations/Version20250327185727.php new file mode 100644 index 0000000..2a6b69c --- /dev/null +++ b/migrations/Version20250327185727.php @@ -0,0 +1,38 @@ +addSql('DROP TABLE client'); + $this->addSql('DROP TABLE refresh_token'); + $this->addSql('DROP TABLE auth_code'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('CREATE TABLE client (identifier VARCHAR(32) NOT NULL, secret VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, active BOOLEAN NOT NULL, redirect_uris JSON NOT NULL, grants JSON NOT NULL, scopes JSON NOT NULL, allow_plain_text_pkce BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('CREATE TABLE refresh_token (identifier VARCHAR(80) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, access_token_identifier VARCHAR(80) NOT NULL, revoked BOOLEAN NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('COMMENT ON COLUMN refresh_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE auth_code (identifier VARCHAR(80) NOT NULL, expiry TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_identifier VARCHAR(255) DEFAULT NULL, client_identifier VARCHAR(32) NOT NULL, redirect_uri VARCHAR(255) DEFAULT NULL, revoked BOOLEAN NOT NULL, scopes JSON NOT NULL, PRIMARY KEY(identifier))'); + $this->addSql('COMMENT ON COLUMN auth_code.expiry IS \'(DC2Type:datetime_immutable)\''); + } +} diff --git a/src/Command/CreateOAuthClientCommand.php b/src/Command/CreateOAuthClientCommand.php deleted file mode 100644 index d7e025e..0000000 --- a/src/Command/CreateOAuthClientCommand.php +++ /dev/null @@ -1,94 +0,0 @@ -addArgument('identifier', InputArgument::REQUIRED, 'Client identifier (max 32 chars)') - ->addArgument('name', InputArgument::REQUIRED, 'Client name') - ->addArgument('redirect-uri', InputArgument::REQUIRED, 'Redirect URI') - ->addOption('grant-type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Grant type (authorization_code, client_credentials, implicit, password, refresh_token)', ['authorization_code']) - ->addOption('scope', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Scope', ['email']) - ->addOption('secret', null, InputOption::VALUE_OPTIONAL, 'Client secret (for confidential clients)') - ; - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $identifier = $input->getArgument('identifier'); - $name = $input->getArgument('name'); - $redirectUri = $input->getArgument('redirect-uri'); - $grantTypes = $input->getOption('grant-type'); - $scopes = $input->getOption('scope'); - $secret = $input->getOption('secret'); - - if (strlen($identifier) > 32) { - $io->error('Identifier must be 32 characters or less'); - return Command::FAILURE; - } - - if ($this->clientRepository->find($identifier)) { - $io->error(sprintf('Client with identifier "%s" already exists', $identifier)); - return Command::FAILURE; - } - - $client = new Client($identifier, $secret, $name); - - // Create and spread the redirect URI - $client->setRedirectUris(new RedirectUriVO($redirectUri)); - - // Create the grant type objects - $grantObjects = array_map(fn($grantType) => new GrantVO($grantType), $grantTypes); - // Spread the grant objects to the setter - $client->setGrants(...$grantObjects); - - // Create the scope objects - $scopeObjects = array_map(fn($scope) => new ScopeVO($scope), $scopes); - // Spread the scope objects to the setter - $client->setScopes(...$scopeObjects); - - $this->clientRepository->save($client, true); - - $io->success(sprintf('Client "%s" created successfully', $identifier)); - $io->table( - ['Property', 'Value'], - [ - ['Identifier', $identifier], - ['Secret', $secret ?: 'none (public client)'], - ['Name', $name], - ['Redirect URI', $redirectUri], - ['Grant Types', implode(', ', $grantTypes)], - ['Scopes', implode(', ', $scopes)], - ] - ); - - return Command::SUCCESS; - } -} \ No newline at end of file diff --git a/src/Command/CreateUserCommand.php b/src/Command/CreateUserCommand.php deleted file mode 100644 index eb8b03a..0000000 --- a/src/Command/CreateUserCommand.php +++ /dev/null @@ -1,88 +0,0 @@ -addArgument('username', InputArgument::REQUIRED, 'Username') - ->addArgument('email', InputArgument::REQUIRED, 'Email address') - ->addArgument('password', InputArgument::REQUIRED, 'Password') - ->addOption('admin', null, InputOption::VALUE_NONE, 'Set as admin user') - ; - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $username = $input->getArgument('username'); - $email = $input->getArgument('email'); - $plainPassword = $input->getArgument('password'); - $isAdmin = $input->getOption('admin'); - - // Check if username already exists - $existingUser = $this->userRepository->findOneBy(['username' => $username]); - if ($existingUser) { - $io->error(sprintf('User with username "%s" already exists', $username)); - return Command::FAILURE; - } - - // Check if email already exists - $existingUser = $this->userRepository->findOneBy(['email' => $email]); - if ($existingUser) { - $io->error(sprintf('User with email "%s" already exists', $email)); - return Command::FAILURE; - } - - $user = new User(); - $user->setUsername($username); - $user->setEmail($email); - - $hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword); - $user->setPassword($hashedPassword); - - $roles = ['ROLE_USER']; - if ($isAdmin) { - $roles[] = 'ROLE_ADMIN'; - } - $user->setRoles($roles); - - $this->userRepository->save($user, true); - - $io->success(sprintf('User "%s" created successfully', $username)); - $io->table( - ['Property', 'Value'], - [ - ['Username', $username], - ['Email', $email], - ['Roles', implode(', ', $user->getRoles())], - ] - ); - - return Command::SUCCESS; - } -} \ No newline at end of file diff --git a/src/Controller/OAuth2Controller.php b/src/Controller/OAuth2Controller.php index 9501ce1..79c38d1 100644 --- a/src/Controller/OAuth2Controller.php +++ b/src/Controller/OAuth2Controller.php @@ -2,34 +2,23 @@ namespace App\Controller; +use App\Entity\User; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; -use League\OAuth2\Server\AuthorizationServer; class OAuth2Controller extends AbstractController { - /** - * This controller intercepts the /authorize route before the League OAuth2 Server - * bundle handles it. If the user is logged in, it will pass through to the OAuth2 - * server. If not, it will redirect to the login page. - */ - #[Route('/authorize', name: 'oauth2_authorize_check', methods: ['GET', 'POST'])] - public function authorize(Request $request): Response + #[Route('/oauth/api/user', name: 'app_oauth_api_user')] + public function oauthApiUser(): JsonResponse { - // If the user is not logged in, redirect to login - if (!$this->getUser()) { - // Store the original request parameters - $session = $request->getSession(); - $session->set('oauth_authorization_request', $request->query->all()); - - // Redirect to login - return $this->redirectToRoute('app_login'); - } - - // User is logged in, let the OAuth2 server handle the authorization - // We'll just forward the request to the League OAuth2 Server bundle's controller - return $this->forward('league.oauth2_server.controller.authorization::indexAction'); + /** @var User $user */ + $user = $this->getUser(); + return new JsonResponse([ + 'message' => 'Authentification réussie !', + 'email' => $user->getEmail(), + 'name' => $user->getUsername(), + ]); + } } \ No newline at end of file diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 6749c2a..2f0136f 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -7,7 +7,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; -use KnpU\OAuth2ClientBundle\Client\ClientRegistry; final class SecurityController extends AbstractController { @@ -41,45 +40,4 @@ final class SecurityController extends AbstractController ]); } - #[Route('/connect/sso', name: 'connect_sso_start')] - public function connectAction(ClientRegistry $clientRegistry): Response - { - return $clientRegistry - ->getClient('sso_server') - ->redirect([ - 'profile', - 'email' - ]); - } - - #[Route('/connect/sso/check', name: 'connect_sso_check')] - public function connectCheckAction(): Response - { - // This method can be empty - it will be intercepted by the oauth2 firewall - throw new \Exception('Don\'t forget to activate check_path in security.yaml'); - } - - /** - * This route is used after successful authentication to redirect - * back to the OAuth2 authorization if there was a pending request - */ - #[Route('/login/check', name: 'app_login_check')] - public function loginCheck(Request $request): Response - { - // Check if there was a pending OAuth2 authorization request - $session = $request->getSession(); - $oauthRequest = $session->get('oauth_authorization_request'); - - if ($oauthRequest) { - // Clear the stored request - $session->remove('oauth_authorization_request'); - - // Rebuild the authorization URL - $queryString = http_build_query($oauthRequest); - return $this->redirect('/authorize?' . $queryString); - } - - // No pending OAuth2 request, go to homepage - return $this->redirectToRoute('app_home'); - } } diff --git a/src/Entity/AccessToken.php b/src/Entity/AccessToken.php deleted file mode 100644 index e868b66..0000000 --- a/src/Entity/AccessToken.php +++ /dev/null @@ -1,111 +0,0 @@ -identifier = $identifier; - $this->expiry = $expiry; - $this->userIdentifier = $userIdentifier; - $this->clientIdentifier = $clientIdentifier; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getExpiry(): \DateTimeImmutable - { - return $this->expiry; - } - - /** - * Proxies to getExpiry for backward compatibility with older versions of the library - */ - public function getExpiryDateTime(): \DateTimeImmutable - { - return $this->getExpiry(); - } - - public function getUserIdentifier(): string - { - return $this->userIdentifier; - } - - public function getClient(): ClientInterface - { - throw new \RuntimeException('Method not implemented.'); - } - - public function getClientIdentifier(): string - { - return $this->clientIdentifier; - } - - public function isRevoked(): bool - { - return $this->revoked; - } - - public function revoke(): AccessTokenInterface - { - $this->revoked = true; - return $this; - } - - public function getScopes(): array - { - return array_map( - static fn (string $scope): Scope => new ScopeVO($scope), - $this->scopes - ); - } - - public function setScopes(array $scopes): self - { - $this->scopes = array_map( - static fn (Scope $scope): string => (string) $scope, - $scopes - ); - return $this; - } - - public function __toString(): string - { - return $this->getIdentifier(); - } -} \ No newline at end of file diff --git a/src/Entity/AuthCode.php b/src/Entity/AuthCode.php deleted file mode 100644 index 01fdafc..0000000 --- a/src/Entity/AuthCode.php +++ /dev/null @@ -1,121 +0,0 @@ -identifier = $identifier; - $this->expiry = $expiry; - $this->userIdentifier = $userIdentifier; - $this->clientIdentifier = $clientIdentifier; - $this->redirectUri = $redirectUri; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getExpiry(): \DateTimeImmutable - { - return $this->expiry; - } - - /** - * Proxies to getExpiry for backward compatibility with older versions of the library - */ - public function getExpiryDateTime(): \DateTimeImmutable - { - return $this->getExpiry(); - } - - public function getUserIdentifier(): ?string - { - return $this->userIdentifier; - } - - public function getClient(): ClientInterface - { - throw new \RuntimeException('Method not implemented.'); - } - - public function getClientIdentifier(): string - { - return $this->clientIdentifier; - } - - public function getRedirectUri(): ?string - { - return $this->redirectUri; - } - - public function isRevoked(): bool - { - return $this->revoked; - } - - public function revoke(): AuthorizationCodeInterface - { - $this->revoked = true; - return $this; - } - - public function getScopes(): array - { - return array_map( - static fn (string $scope): Scope => new ScopeVO($scope), - $this->scopes - ); - } - - public function setScopes(array $scopes): self - { - $this->scopes = array_map( - static fn (Scope $scope): string => (string) $scope, - $scopes - ); - return $this; - } - - public function __toString(): string - { - return $this->getIdentifier(); - } -} \ No newline at end of file diff --git a/src/Entity/Client.php b/src/Entity/Client.php deleted file mode 100644 index 76cff78..0000000 --- a/src/Entity/Client.php +++ /dev/null @@ -1,160 +0,0 @@ - false])] - private bool $allowPlainTextPkce = false; - - public function __construct(string $identifier, ?string $secret, string $name) - { - $this->identifier = $identifier; - $this->secret = $secret; - $this->name = $name; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getSecret(): ?string - { - return $this->secret; - } - - public function getName(): string - { - return $this->name; - } - - public function isActive(): bool - { - return $this->active; - } - - public function setActive(bool $active): self - { - $this->active = $active; - return $this; - } - - public function getRedirectUri(): string|array - { - $uris = array_map( - static fn (string $redirectUri): string => $redirectUri, - $this->redirectUris - ); - - return $uris; - } - - public function getGrants(): array - { - return array_map( - static fn (string $grant): GrantVO => new GrantVO($grant), - $this->grants - ); - } - - public function setGrants(GrantVO ...$grants): self - { - $this->grants = array_map( - static fn (GrantVO $grant): string => (string) $grant, - $grants - ); - return $this; - } - - public function getScopes(): array - { - return array_map( - static fn (string $scope): ScopeVO => new ScopeVO($scope), - $this->scopes - ); - } - - public function setScopes(ScopeVO ...$scopes): self - { - $this->scopes = array_map( - static fn (ScopeVO $scope): string => (string) $scope, - $scopes - ); - return $this; - } - - public function getMetadata(): ClientMetadata - { - return new ClientMetadata($this->name); - } - - public function isConfidential(): bool - { - return $this->secret !== null; - } - - public function isPlainTextPkceAllowed(): bool - { - return $this->allowPlainTextPkce; - } - - public function setAllowPlainTextPkce(bool $allowPlainTextPkce): self - { - $this->allowPlainTextPkce = $allowPlainTextPkce; - return $this; - } - - public function __toString(): string - { - return $this->getIdentifier(); - } - - public function getRedirectUris(): array - { - return array_map( - static fn (string $redirectUri): RedirectUriVO => new RedirectUriVO($redirectUri), - $this->redirectUris - ); - } - - public function setRedirectUris(RedirectUriVO ...$redirectUris): self - { - $this->redirectUris = array_map( - static fn (RedirectUriVO $redirectUri): string => (string) $redirectUri, - $redirectUris - ); - return $this; - } -} \ No newline at end of file diff --git a/src/Entity/RefreshToken.php b/src/Entity/RefreshToken.php deleted file mode 100644 index f736828..0000000 --- a/src/Entity/RefreshToken.php +++ /dev/null @@ -1,79 +0,0 @@ -identifier = $identifier; - $this->expiry = $expiry; - $this->accessTokenIdentifier = $accessTokenIdentifier; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getExpiry(): \DateTimeImmutable - { - return $this->expiry; - } - - /** - * Proxies to getExpiry for backward compatibility with older versions of the library - */ - public function getExpiryDateTime(): \DateTimeImmutable - { - return $this->getExpiry(); - } - - public function getAccessToken(): AccessTokenInterface - { - throw new \RuntimeException('Method not implemented.'); - } - - public function getAccessTokenIdentifier(): string - { - return $this->accessTokenIdentifier; - } - - public function isRevoked(): bool - { - return $this->revoked; - } - - public function revoke(): RefreshTokenInterface - { - $this->revoked = true; - return $this; - } - - public function __toString(): string - { - return $this->getIdentifier(); - } -} \ No newline at end of file diff --git a/src/EventSubscriber/AuthorizationCodeSubscriber.php b/src/EventSubscriber/AuthorizationCodeSubscriber.php new file mode 100644 index 0000000..f41cedd --- /dev/null +++ b/src/EventSubscriber/AuthorizationCodeSubscriber.php @@ -0,0 +1,60 @@ +security = $security; + $this->urlGenerator = $urlGenerator; + $this->requestStack = $requestStack; + $this->firewallName = $firewallMap->getFirewallConfig($requestStack->getCurrentRequest())->getName(); + } + + public function onLeagueOauth2ServerEventAuthorizationRequestResolve(AuthorizationRequestResolveEvent $event): void + { + $request = $this->requestStack->getCurrentRequest(); + $user = $this->security->getUser(); + $this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri()); + $response = new RedirectResponse($this->urlGenerator->generate('app_login'), 307); + if ($user instanceof UserInterface) { + //On approuve le consentement automatiquement + $event->resolveAuthorization(true); + $request->getSession()->remove('consent_granted'); + return; + //Decommenter et implemeter pour rediriger vers les constentement + /*if ($request->getSession()->get('consent_granted') !== null) { + $event->resolveAuthorization($request->getSession()->get('consent_granted')); + $request->getSession()->remove('consent_granted'); + return; + } + $response = new RedirectResponse($this->urlGenerator->generate('app_consent', $request->query->all()), 307);*/ + } + $event->setResponse($response); + } + + public static function getSubscribedEvents(): array + { + return [ + 'league.oauth2_server.event.authorization_request_resolve' => 'onLeagueOauth2ServerEventAuthorizationRequestResolve', + ]; + } +} \ No newline at end of file diff --git a/src/Repository/AccessTokenRepository.php b/src/Repository/AccessTokenRepository.php deleted file mode 100644 index e0ffe17..0000000 --- a/src/Repository/AccessTokenRepository.php +++ /dev/null @@ -1,96 +0,0 @@ - - */ -class AccessTokenRepository extends ServiceEntityRepository implements AccessTokenRepositoryInterface -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, AccessToken::class); - } - - public function save(AccessToken $entity, bool $flush = false): void - { - $this->getEntityManager()->persist($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function remove(AccessToken $entity, bool $flush = false): void - { - $this->getEntityManager()->remove($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function getAccessToken(string $identifier): ?AccessTokenInterface - { - return $this->find($identifier); - } - - public function persistNewAccessToken(AccessTokenInterface $accessToken): void - { - if (!$accessToken instanceof AccessToken) { - throw new \RuntimeException('Invalid access token type'); - } - - $this->save($accessToken, true); - } - - public function revokeAccessToken(string $identifier): void - { - $accessToken = $this->find($identifier); - if (null !== $accessToken) { - $accessToken->revoke(); - $this->save($accessToken, true); - } - } - - public function isAccessTokenRevoked(string $identifier): bool - { - $accessToken = $this->find($identifier); - if (null === $accessToken) { - return true; - } - - return $accessToken->isRevoked(); - } - - public function createNewAccessToken( - ClientInterface $client, - array $scopes, - string $userIdentifier = null - ): AccessTokenInterface { - $accessToken = new AccessToken( - bin2hex(random_bytes(40)), - new \DateTimeImmutable('+1 hour'), - $userIdentifier ?? '', - $client->getIdentifier() - ); - - $accessToken->setScopes(array_map( - static fn(string $scope) => new Scope($scope), - array_map( - static fn($scope) => (string) $scope, - $scopes - ) - )); - - return $accessToken; - } -} \ No newline at end of file diff --git a/src/Repository/AuthCodeRepository.php b/src/Repository/AuthCodeRepository.php deleted file mode 100644 index dc7e9f2..0000000 --- a/src/Repository/AuthCodeRepository.php +++ /dev/null @@ -1,98 +0,0 @@ - - */ -class AuthCodeRepository extends ServiceEntityRepository implements AuthorizationCodeRepositoryInterface -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, AuthCode::class); - } - - public function save(AuthCode $entity, bool $flush = false): void - { - $this->getEntityManager()->persist($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function remove(AuthCode $entity, bool $flush = false): void - { - $this->getEntityManager()->remove($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function getAuthorizationCode(string $identifier): ?AuthorizationCodeInterface - { - return $this->find($identifier); - } - - public function persistNewAuthorizationCode(AuthorizationCodeInterface $authorizationCode): void - { - if (!$authorizationCode instanceof AuthCode) { - throw new \RuntimeException('Invalid authorization code type'); - } - - $this->save($authorizationCode, true); - } - - public function revokeAuthorizationCode(string $identifier): void - { - $authCode = $this->find($identifier); - if (null !== $authCode) { - $authCode->revoke(); - $this->save($authCode, true); - } - } - - public function isAuthorizationCodeRevoked(string $identifier): bool - { - $authCode = $this->find($identifier); - if (null === $authCode) { - return true; - } - - return $authCode->isRevoked(); - } - - public function createNewAuthorizationCode( - ClientInterface $client, - array $scopes, - ?string $userIdentifier, - ?string $redirectUri - ): AuthorizationCodeInterface { - $authCode = new AuthCode( - bin2hex(random_bytes(40)), - new \DateTimeImmutable('+5 minutes'), - $userIdentifier ?? '', - $client->getIdentifier(), - $redirectUri - ); - - $authCode->setScopes(array_map( - static fn(string $scope) => new Scope($scope), - array_map( - static fn($scope) => (string) $scope, - $scopes - ) - )); - - return $authCode; - } -} \ No newline at end of file diff --git a/src/Repository/ClientRepository.php b/src/Repository/ClientRepository.php deleted file mode 100644 index f241f3f..0000000 --- a/src/Repository/ClientRepository.php +++ /dev/null @@ -1,75 +0,0 @@ - - */ -class ClientRepository extends ServiceEntityRepository implements ClientRepositoryInterface -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Client::class); - } - - public function save(Client $entity, bool $flush = false): void - { - $this->getEntityManager()->persist($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function remove(Client $entity, bool $flush = false): void - { - $this->getEntityManager()->remove($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface - { - return $this->find($clientIdentifier); - } - - public function validateClient(string $clientIdentifier, ?string $clientSecret, ?string $grantType): bool - { - $client = $this->find($clientIdentifier); - - if (null === $client || !$client->isActive()) { - return false; - } - - // Validate grant type - if (null !== $grantType) { - $validGrantTypes = array_map( - static fn($grant) => (string) $grant, - $client->getGrants() - ); - - if (!in_array($grantType, $validGrantTypes, true)) { - return false; - } - } - - // Validate secret - if (null === $client->getSecret()) { - return true; - } - - if (null === $clientSecret) { - return false; - } - - return $client->getSecret() === $clientSecret; - } -} \ No newline at end of file diff --git a/src/Repository/RefreshTokenRepository.php b/src/Repository/RefreshTokenRepository.php deleted file mode 100644 index 80aa787..0000000 --- a/src/Repository/RefreshTokenRepository.php +++ /dev/null @@ -1,83 +0,0 @@ - - */ -class RefreshTokenRepository extends ServiceEntityRepository implements RefreshTokenRepositoryInterface -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, RefreshToken::class); - } - - public function save(RefreshToken $entity, bool $flush = false): void - { - $this->getEntityManager()->persist($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function remove(RefreshToken $entity, bool $flush = false): void - { - $this->getEntityManager()->remove($entity); - - if ($flush) { - $this->getEntityManager()->flush(); - } - } - - public function getRefreshToken(string $identifier): ?RefreshTokenInterface - { - return $this->find($identifier); - } - - public function persistNewRefreshToken(RefreshTokenInterface $refreshToken): void - { - if (!$refreshToken instanceof RefreshToken) { - throw new \RuntimeException('Invalid refresh token type'); - } - - $this->save($refreshToken, true); - } - - public function revokeRefreshToken(string $identifier): void - { - $refreshToken = $this->find($identifier); - if (null !== $refreshToken) { - $refreshToken->revoke(); - $this->save($refreshToken, true); - } - } - - public function isRefreshTokenRevoked(string $identifier): bool - { - $refreshToken = $this->find($identifier); - if (null === $refreshToken) { - return true; - } - - return $refreshToken->isRevoked(); - } - - public function createNewRefreshToken( - AccessTokenInterface $accessToken, - \DateTimeImmutable $expiry - ): RefreshTokenInterface { - return new RefreshToken( - bin2hex(random_bytes(40)), - $expiry, - $accessToken->getIdentifier() - ); - } -} \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index f24cbb6..3cfb957 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -14,7 +14,7 @@ {% endblock %} - + {% block body %}{% endblock %}