diff --git a/Entity/CasUser.php b/Entity/CasUser.php new file mode 100644 index 0000000..621d6fe --- /dev/null +++ b/Entity/CasUser.php @@ -0,0 +1,62 @@ +identifier = $identifier; + } + + public function setCasAttributes(array $attributes) + { + $this->attributes = $attributes; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + /** + * @param list $roles + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @return non-empty-string + */ + public function getUserIdentifier(): string + { + return $this->identifier; + } +} diff --git a/README.md b/README.md index 86332dc..1167077 100644 --- a/README.md +++ b/README.md @@ -751,6 +751,97 @@ class MyUserEntity implements CasUserInterface ... `````` +Finally, you can work with a custom UserProvider (that extends, or not, CasUserProvider), to create users from cas attributes only (and skip useless ldap extra queries), like this exemple : +``` +casAttributeStorage->getAttributes(); + + // try to get user from db + $user = $this->userRepository->findOneBy(['username' => $identifier]); + // if not found, create a inactive user + if (is_null($user)) { + $user = new User(); + $user + ->setUsername($identifier) + ->setEnabled(0); + } + if ($user instanceof CasUserInterface) { + $user->setCasAttributes($attributes); + } + $this->computeRoles($user); + + $this->entityManagerInterface->persist($user); + $this->entityManagerInterface->flush(); + + if (is_null($user)) { + throw new UserNotFoundException('Utilisateur introuvable ou desactivé !'); + } + + return $user; + } + + private function computeRoles(User $user) + { + $attributes = $user->getAttributs(); + $roles = ['ROLE_USER']; + if (isset($attributes['memberof']) && in_array('ADMIN', $attributes['memberof'])) { + $roles[] = 'ROLE_ADMIN'; + } + if ($user->getUserIdentifier() == 'darkvador') { + $roles[] = 'ROLE_ADMIN'; + } + $user->setRoles($roles); + } + + /** + * Refreshes the user after being reloaded from the session. + * + * @return UserInterface + */ + public function refreshUser(UserInterface $user): UserInterface + { + $user = $this->userRepository->find($user->getUserIdentifier()); + + if (is_null($user) || !$user->isEnabled()) { + throw new UserNotFoundException('Utilisateur introuvable ou desactivé !'); + } + + $this->computeRoles($user); + + return $user; + } + + public function supportsClass(string $class): bool + { + return is_subclass_of($class, UserInterface::class) || is_subclass_of($class, CasUserInterface::class); + } +} + +``` + + Annotations --- The Route annotations run if you install this package : diff --git a/Resources/config/services.yml b/Resources/config/services.yml index c97729e..031b791 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -10,5 +10,14 @@ services: # public: false cas.security.authentication.authenticator: class: L3\Bundle\CasGuardBundle\Security\CasAuthenticator - arguments: ['%cas%','@event_dispatcher'] + arguments: ['%cas%','@event_dispatcher',"@cas.security.authentication.attrStorage"] public: false + + cas.security.authentication.attrStorage: + class: L3\Bundle\CasGuardBundle\Security\CasAttributeStorage + + cas.security.authentication.provider: + class: L3\Bundle\CasGuardBundle\Security\CasUserProvider + arguments: ["@cas.security.authentication.attrStorage"] + public: true + diff --git a/Security/CasAttributeStorage.php b/Security/CasAttributeStorage.php new file mode 100644 index 0000000..76f6b95 --- /dev/null +++ b/Security/CasAttributeStorage.php @@ -0,0 +1,18 @@ +attributes = $attributes; + } + + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/Security/CasAuthenticator.php b/Security/CasAuthenticator.php index 200b67e..d82fe8b 100644 --- a/Security/CasAuthenticator.php +++ b/Security/CasAuthenticator.php @@ -10,6 +10,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Security\Core\User\UserInterface; +use L3\Bundle\CasGuardBundle\Security\CasAttributeStorage; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -28,7 +29,7 @@ class CasAuthenticator extends AbstractAuthenticator { * Process configuration * @param array $config */ - public function __construct(array $config = Array(), EventDispatcherInterface $eventDispatcher = null) { + public function __construct(array $config = Array(), EventDispatcherInterface $eventDispatcher = null, private CasAttributeStorage $casAttributeStorage ) { $this->config = $config; $this->eventDispatcher = $eventDispatcher; } @@ -142,6 +143,10 @@ public function authenticate(Request $request): Passport } } + // save attributes to get them in UserProvider + $attributes = \phpCAS::getAttributes(); + $this->casAttributeStorage->setAttributes($attributes); + $passport = new SelfValidatingPassport(new UserBadge($user), []); return $passport; diff --git a/Security/CasUserProvider.php b/Security/CasUserProvider.php new file mode 100644 index 0000000..8e74b0d --- /dev/null +++ b/Security/CasUserProvider.php @@ -0,0 +1,61 @@ +casAttributeStorage->getAttributes(); + + $user = new CasUser($identifier); + if ($user instanceof CasUserInterface) { + $user->setCasAttributes($attributes); + + $roles = ['ROLE_USER']; + if (isset($attributes['memberOf']) && in_array('admin', $attributes['memberOf'])) { + $role[] = 'ROLE_AMIN'; + } + $user->setRoles($roles); + } + + if (is_null($user)) { + throw new UserNotFoundException('Utilisateur introuvable ou desactivé !'); + } + + return $user; + } + + /** + * Refreshes the user after being reloaded from the session. + * + * @return UserInterface + */ + public function refreshUser(UserInterface $user): UserInterface + { + return $user; + } + + public function supportsClass(string $class): bool + { + return is_subclass_of($class, UserInterface::class) || is_subclass_of($class, CasUserInterface::class); + } +}