Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Copy audience claims to user roles #15

Merged
merged 10 commits into from
Feb 7, 2017
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ class PreAuthenticatedUserProvider implements UserProviderInterface
}
```

### Assigning audience to user roles

JwtBundle is can assign the audience claims in the JwtToken to the User objects user roles properties.

In order to do this the User class needs to implement the `KleijnWeb\JwtBundle\User\UserInterface` interface

## License

KleijnWeb\JwtBundle is made available under the terms of the [LGPL, version 3.0](https://spdx.org/licenses/LGPL-3.0.html#licenseText).
31 changes: 31 additions & 0 deletions src/Authenticator/Authenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use KleijnWeb\JwtBundle\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;

Expand Down Expand Up @@ -101,6 +102,10 @@ public function authenticateToken(TokenInterface $token, UserProviderInterface $

$user = $userProvider->loadUserByUsername($jwtToken->getSubject());

if ($user instanceof UserInterface) {
$user = $this->setUserRolesFromAudienceClaims($user, $token);
}

return new PreAuthenticatedToken($user, $token, $providerKey, $user->getRoles());
}

Expand All @@ -114,4 +119,30 @@ public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}

/**
* @param UserInterface $user
* @param TokenInterface $token
*
* @return UserInterface
*/
public function setUserRolesFromAudienceClaims(UserInterface $user, TokenInterface $token)
{
/** @var JwtToken $credentials */
$credentials = $token->getCredentials();

foreach ($credentials->getClaims() as $claimKey => $claimValue) {
if ($claimKey === 'aud') {
if (is_array($claimValue)) {
foreach ($claimValue as $role) {
$user->addRole("ROLE_" . strtoupper($role));
}
} elseif (is_string($claimValue)) {
$user->addRole("ROLE_" . strtoupper($claimValue));
}
}
}

return $user;
}
}
64 changes: 64 additions & 0 deletions src/Tests/Authenticator/AuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use KleijnWeb\JwtBundle\Authenticator\Authenticator;
use KleijnWeb\JwtBundle\Authenticator\JwtKey;
use KleijnWeb\JwtBundle\Authenticator\JwtToken;
use KleijnWeb\JwtBundle\User\UserInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\User\User;
Expand Down Expand Up @@ -181,6 +182,69 @@ public function canGetAnonTokenWithClaims()
$this->assertEquals($expected, $token->getCredentials());
}

/**
* @test
*/
public function willAddRolesFromAudienceClaimsInToken()
{
$authenticator = new Authenticator($this->keys);
$token = $this->createToken(['sub' => 'john', 'aud' => 'guests']);
$anonToken = new PreAuthenticatedToken('foo', $token, 'myprovider');

$user = $this->getMockBuilder(
'KleijnWeb\JwtBundle\User\UserInterface'
)->getMockForAbstractClass();

$userProvider = $this->getMockBuilder(
'Symfony\Component\Security\Core\User\UserProviderInterface'
)->getMockForAbstractClass();

$userProvider->expects($this->once())
->method('loadUserByUsername')
->willReturn($user);

$user->expects($this->once())
->method('addRole')
->with('ROLE_GUESTS');

$user->expects($this->once())
->method('getRoles')
->willReturn(['ROLE_GUESTS']);

$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider');
}

/**
* @test
*/
public function willAddMultipleRolesFromAudienceClaimsInToken()
{
$authenticator = new Authenticator($this->keys);
$token = $this->createToken(['sub' => 'john', 'aud' => ['guests', 'users']]);
$anonToken = new PreAuthenticatedToken('foo', $token, 'myprovider');

$user = $this->getMockBuilder(
'KleijnWeb\JwtBundle\User\UserInterface'
)->getMockForAbstractClass();

$userProvider = $this->getMockBuilder(
'Symfony\Component\Security\Core\User\UserProviderInterface'
)->getMockForAbstractClass();

$userProvider->expects($this->once())
->method('loadUserByUsername')
->willReturn($user);

$user->expects($this->exactly(2))
->method('addRole');

$user->expects($this->once())
->method('getRoles')
->willReturn(['guests', 'users']);

$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider');
}

/**
* @param array $claims
*
Expand Down
120 changes: 120 additions & 0 deletions src/Tests/Classes/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
namespace KleijnWeb\JwtBundle\Tests\Classes;

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

/**
* User is the user implementation used by the in-memory user provider.
*
* This should not be used for anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class User implements AdvancedUserInterface
{
private $username;
private $password;
private $enabled;
private $accountNonExpired;
private $credentialsNonExpired;
private $accountNonLocked;
private $roles;

public function __construct($username, $password, array $roles = array(), $enabled = true, $userNonExpired = true, $credentialsNonExpired = true, $userNonLocked = true)
{
if ('' === $username || null === $username) {
throw new \InvalidArgumentException('The username cannot be empty.');
}

$this->username = $username;
$this->password = $password;
$this->enabled = $enabled;
$this->accountNonExpired = $userNonExpired;
$this->credentialsNonExpired = $credentialsNonExpired;
$this->accountNonLocked = $userNonLocked;
$this->roles = $roles;
}

public function __toString()
{
return $this->getUsername();
}

/**
* {@inheritdoc}
*/
public function getRoles()
{
return $this->roles;
}

/**
* @param $role
*/
public function addRole($role)
{
$this->roles[] = $role;
}

/**
* {@inheritdoc}
*/
public function getPassword()
{
return $this->password;
}

/**
* {@inheritdoc}
*/
public function getSalt()
{
}

/**
* {@inheritdoc}
*/
public function getUsername()
{
return $this->username;
}

/**
* {@inheritdoc}
*/
public function isAccountNonExpired()
{
return $this->accountNonExpired;
}

/**
* {@inheritdoc}
*/
public function isAccountNonLocked()
{
return $this->accountNonLocked;
}

/**
* {@inheritdoc}
*/
public function isCredentialsNonExpired()
{
return $this->credentialsNonExpired;
}

/**
* {@inheritdoc}
*/
public function isEnabled()
{
return $this->enabled;
}

/**
* {@inheritdoc}
*/
public function eraseCredentials()
{
}
}
15 changes: 15 additions & 0 deletions src/User/UserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
namespace KleijnWeb\JwtBundle\User;

interface UserInterface extends \Symfony\Component\Security\Core\User\UserInterface
{
/**
* @param array $roles
* @return mixed
*/
public function setRoles($roles);

public function addRole($role);

public function removeRole($role);
}