diff --git a/composer.json b/composer.json index bca2c09b54..7f15627089 100755 --- a/composer.json +++ b/composer.json @@ -117,6 +117,7 @@ "sensio/framework-extra-bundle": "~6.1", "simpod/doctrine-utcdatetime": "^0.1.2", "sonata-project/exporter": "^2.2", + "stevenmaguire/oauth2-keycloak": "^5.1", "stof/doctrine-extensions-bundle": "^1.10", "sunra/php-simple-html-dom-parser": "~1.5", "symfony/apache-pack": "^1.0", diff --git a/config/authentication.yaml b/config/authentication.yaml index c1b9916ac5..0dd4e1d5e8 100644 --- a/config/authentication.yaml +++ b/config/authentication.yaml @@ -34,3 +34,15 @@ parameters: client_secret: '' graph_api_version: 'v20.0' redirect_params: { } + + keycloak: + enabled: false + client_id: '' + client_secret: '' + auth_server_url: '' + realm: '' + version: '' + encryption_algorithm: null + encryption_key_path: null + encryption_key: null + redirect_params: { } diff --git a/config/packages/knpu_oauth2_client.yaml b/config/packages/knpu_oauth2_client.yaml index 950ed3d420..587858bc46 100644 --- a/config/packages/knpu_oauth2_client.yaml +++ b/config/packages/knpu_oauth2_client.yaml @@ -15,4 +15,13 @@ knpu_oauth2_client: graph_api_version: '' redirect_params: { } + keycloak: + type: keycloak + client_id: '' + client_secret: '' + redirect_route: chamilo.oauth2_keycloak_check + redirect_params: { } + auth_server_url: null + realm: null + # configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 9e94706e91..17642dff65 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -117,6 +117,7 @@ security: custom_authenticators: - Chamilo\CoreBundle\Security\Authenticator\OAuth2\GenericAuthenticator - Chamilo\CoreBundle\Security\Authenticator\OAuth2\FacebookAuthenticator + - Chamilo\CoreBundle\Security\Authenticator\OAuth2\KeycloakAuthenticator access_control: - {path: ^/login, roles: PUBLIC_ACCESS} diff --git a/src/CoreBundle/Controller/OAuth2/KeycloakProviderController.php b/src/CoreBundle/Controller/OAuth2/KeycloakProviderController.php new file mode 100644 index 0000000000..563f0b15ba --- /dev/null +++ b/src/CoreBundle/Controller/OAuth2/KeycloakProviderController.php @@ -0,0 +1,26 @@ +getStartResponse('keycloak', $clientRegistry, $authenticationConfigHelper); + } + + #[Route('/connect/keycloak/check', name: 'chamilo.oauth2_keycloak_check')] + public function connectCheck(): void {} +} diff --git a/src/CoreBundle/Decorator/OAuth2ProviderFactoryDecorator.php b/src/CoreBundle/Decorator/OAuth2ProviderFactoryDecorator.php index 93aa3e0671..43727e4d4a 100644 --- a/src/CoreBundle/Decorator/OAuth2ProviderFactoryDecorator.php +++ b/src/CoreBundle/Decorator/OAuth2ProviderFactoryDecorator.php @@ -13,6 +13,7 @@ use KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Facebook; use League\OAuth2\Client\Provider\GenericProvider; +use Stevenmaguire\OAuth2\Client\Provider\Keycloak; use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; @@ -35,6 +36,7 @@ readonly class OAuth2ProviderFactoryDecorator $options = match ($class) { GenericProvider::class => $this->getProviderOptions('generic'), Facebook::class => $this->getProviderOptions('facebook'), + Keycloak::class => $this->getProviderOptions('keycloak'), }; return $this->inner->createProvider($class, $options, $redirectUri, $redirectParams, $collaborators); diff --git a/src/CoreBundle/Security/Authenticator/OAuth2/KeycloakAuthenticator.php b/src/CoreBundle/Security/Authenticator/OAuth2/KeycloakAuthenticator.php new file mode 100644 index 0000000000..eecd23bf60 --- /dev/null +++ b/src/CoreBundle/Security/Authenticator/OAuth2/KeycloakAuthenticator.php @@ -0,0 +1,57 @@ +attributes->get('_route'); + } + + protected function userLoader(AccessToken $accessToken): User + { + /** @var KeycloakResourceOwner $resourceOwner */ + $resourceOwner = $this->client->fetchUserFromToken($accessToken); + + $user = $this->userRepository->findOneBy(['username' => $resourceOwner->getUsername()]) + ?: + $this->userRepository->findOneBy(['username' => $resourceOwner->getId()]); + + if (!$user) { + $user = (new User()) + ->setCreatorId($this->userRepository->getRootUser()->getId()) + ; + } + + $username = $resourceOwner->getUsername() ?: $resourceOwner->getId(); + + $user + ->setFirstname($resourceOwner->getFirstName()) + ->setLastname($resourceOwner->getLastName()) + ->setEmail($resourceOwner->getEmail()) + ->setUsername($username) + ->setPlainPassword('keycloak') + ->setStatus(STUDENT) + ->setAuthSource('keycloak') + ->setRoleFromStatus(STUDENT) + ; + + $this->userRepository->updateUser($user); + // updateAccessUrls ? + + return $user; + } +}