Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Entity/CasUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace L3\Bundle\CasGuardBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;

/**
* this is a very basic implementation exemple, use your own "CasUser" or extends this class
*/
class CasUser implements CasUserInterface, UserInterface
{
private string $identifier;

private array $roles = [];

private $attributes = [];

public function __construct(string $identifier)
{
$this->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<string> $roles
*/
public function setRoles(array $roles): static
{
$this->roles = $roles;

return $this;
}

/**
* @return non-empty-string
*/
public function getUserIdentifier(): string
{
return $this->identifier;
}
}
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
```
<?php

namespace App\Security;

use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use L3\Bundle\CasGuardBundle\Entity\CasUserInterface;
use L3\Bundle\CasGuardBundle\Security\CasAttributeStorage;
use L3\Bundle\CasGuardBundle\Security\CasUserProvider;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class CustomUserProvider extends CasUserProvider
{
public function __construct(private CasAttributeStorage $casAttributeStorage, private UserRepository $userRepository, private EntityManagerInterface $entityManagerInterface)
{
parent::__construct($casAttributeStorage);
}
public function loadUserByIdentifier(string $identifier): UserInterface
{
$attributes = $this->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 :
Expand Down
11 changes: 10 additions & 1 deletion Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

18 changes: 18 additions & 0 deletions Security/CasAttributeStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace L3\Bundle\CasGuardBundle\Security;

class CasAttributeStorage
{
private array $attributes = [];

public function setAttributes(array $attributes): void
{
$this->attributes = $attributes;
}

public function getAttributes(): array
{
return $this->attributes;
}
}
7 changes: 6 additions & 1 deletion Security/CasAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
61 changes: 61 additions & 0 deletions Security/CasUserProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace L3\Bundle\CasGuardBundle\Security;

use L3\Bundle\CasGuardBundle\Entity\CasUser;
use L3\Bundle\CasGuardBundle\Entity\CasUserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;


/**
* this is a very basic implementation exemple, use your own "CasUserProvider" or extends this class
*/
class CasUserProvider implements UserProviderInterface
{

public function __construct(
private CasAttributeStorage $casAttributeStorage
) {
}

public function loadUserByIdentifier(string $identifier): UserInterface
{
$attributes = $this->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);
}
}