Skip to content

[POC][Security] Split tokens in request token + authentication token (towards making tokens first-class citizens) #21068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Security\Core\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Token\AnonymousRequestToken;
use Symfony\Component\Security\Core\Authentication\Token\AuthenticatedAnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
Expand Down Expand Up @@ -53,14 +55,23 @@ public function authenticate(TokenInterface $token)
throw new BadCredentialsException('The Token does not contain the expected key.');
}

return $token;
return new AuthenticatedAnonymousToken($token->getSecret(), $token->getUsername());
}

/**
* {@inheritdoc}
*/
public function supports(TokenInterface $token)
{
return $token instanceof AnonymousToken;
if ($token instanceof AnonymousRequestToken) {
return true;
}

if (!$token instanceof AnonymousToken || $token instanceof AuthenticatedAnonymousToken) {
return false;
}

@trigger_error('Support for AnonymousToken in the AnonymousAuthenticationProvider class is deprecated in 3.1 and will be removed in 4.0. Pass an AnonymousRequestToken object instead.', E_USER_DEPRECATED);
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Symfony\Component\Security\Core\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Token\AuthenticatedRememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\AuthenticatedUserToken;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeRequestToken;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
Expand Down Expand Up @@ -52,7 +55,7 @@ public function authenticate(TokenInterface $token)
$user = $token->getUser();
$this->userChecker->checkPreAuth($user);

$authenticatedToken = new RememberMeToken($user, $this->providerKey, $this->secret);
$authenticatedToken = new AuthenticatedRememberMeToken($user, $this->providerKey, $this->secret);
$authenticatedToken->setAttributes($token->getAttributes());

return $authenticatedToken;
Expand All @@ -63,6 +66,16 @@ public function authenticate(TokenInterface $token)
*/
public function supports(TokenInterface $token)
{
return $token instanceof RememberMeToken && $token->getProviderKey() === $this->providerKey;
if ($token instanceof RememberMeRequestToken) {
return $token->getProviderKey() === $this->providerKey;
}

if ($token instanceof RememberMeToken) {
@trigger_error('Support for RememberMeToken in the RememberMeAuthenticationProvider class is deprecated in 3.1 and will be removed in 4.0. Pass a RememberMeRequestToken object instead.', E_USER_DEPRECATED);

return $token->getProviderKey() === $this->providerKey;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Security\Core\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Token\AuthenticatedUserToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordRequestToken;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
Expand Down Expand Up @@ -66,8 +68,15 @@ public function authenticate(TokenInterface $token)
$username = AuthenticationProviderInterface::USERNAME_NONE_PROVIDED;
}

// For backwards compatibility with <3.1. Deprecation notice is already triggered in supports().
if (!$token instanceof UsernamePasswordRequestToken) {
$newToken = new UsernamePasswordRequestToken($token->getUsername(), $token->getCredentials(), $token->getProviderKey(), $token->getRoles());
$newToken->setAttributes($token->getAttributes());
$token = $newToken;
}

try {
$user = $this->retrieveUser($username, $token);
$user = $this->getUserFromToken($username, $token);
} catch (UsernameNotFoundException $e) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials.', 0, $e);
Expand All @@ -78,12 +87,12 @@ public function authenticate(TokenInterface $token)
}

if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
throw new AuthenticationServiceException('getUserFromToken() must return a UserInterface.');
}

try {
$this->userChecker->checkPreAuth($user);
$this->checkAuthentication($user, $token);
$this->authenticateUser($user, $token);
$this->userChecker->checkPostAuth($user);
} catch (BadCredentialsException $e) {
if ($this->hideUserNotFoundExceptions) {
Expand All @@ -93,7 +102,7 @@ public function authenticate(TokenInterface $token)
throw $e;
}

$authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token));
$authenticatedToken = new AuthenticatedUserToken($user, $user->getRoles(), $this->providerKey);
$authenticatedToken->setAttributes($token->getAttributes());

return $authenticatedToken;
Expand All @@ -104,7 +113,17 @@ public function authenticate(TokenInterface $token)
*/
public function supports(TokenInterface $token)
{
return $token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey();
if ($token instanceof UsernamePasswordRequestToken) {
return $this->providerKey === $token->getProviderKey();
}

if ($token instanceof UsernamePasswordToken) {
@trigger_error('Support for UsernamePasswordToken in the '.__CLASS__.' class is deprecated in 3.1 and will be removed in 4.0. Pass a UsernamePasswordRequestToken object instead.', E_USER_DEPRECATED);

return $this->providerKey === $token->getProviderKey();
}

return false;
}

/**
Expand All @@ -131,25 +150,52 @@ private function getRoles(UserInterface $user, TokenInterface $token)
}

/**
* Retrieves the user from an implementation-specific location.
* @deprecated Since Symfony 3.1, to be removed in 4.0. Implement getUserFromToken() instead.
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
throw new \LogicException('Method UserAuthenticationProvider::getUserFromToken() needs to be implemented.');
}

/**
* Retrieves the user by the provided username and information from the token.
*
* @param string $username The username to retrieve
* @param UsernamePasswordToken $token The Token
* @param string $username
* @param UsernamePasswordRequestToken $token
*
* @return UserInterface The user
* @return UserInterface
*
* @throws AuthenticationException if the credentials could not be validated
* @throws AuthenticationException If no user could be found for the provided information
*/
abstract protected function retrieveUser($username, UsernamePasswordToken $token);
protected function getUserFromToken($username, UsernamePasswordRequestToken $token)
{
@trigger_error('Method '.__CLASS__.'::retrieveUser() is deprecated since version 3.1 and will be removed in 4.0. Override getUserFromToken() instead.', E_USER_DEPRECATED);

return $this->retrieveUser($username, $token);
}

/**
* Does additional checks on the user and token (like validating the
* credentials).
* Checks whether the user is correctly authenticated.
*
* @param UserInterface $user The retrieved UserInterface instance
* @param UsernamePasswordToken $token The UsernamePasswordToken token to be authenticated
* This is done by e.g. comparing the user password to the provided credentials.
*
* @throws AuthenticationException if the credentials could not be validated
* @param UserInterface $user The user retrieved by the requested username
* @param UsernamePasswordRequestToken $token
*
* @throws AuthenticationException If the user is not correctly authenticated.
*/
protected function authenticateUser(UserInterface $user, UsernamePasswordRequestToken $token)
{
@trigger_error('Method '.__CLASS__.'::checkAuthentication() is deprecated since version 3.1 and will be removed in 4.0. Override authenticateUser() instead.', E_USER_DEPRECATED);

return $this->checkAuthentication($user, $token);
}

/**
* @deprecated Since Symfony 3.1, to be removed in 4.0. Implement authenticateUser() instead.
*/
abstract protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token);
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
throw new \LogicException('Method UserAuthenticationProvider::authenticateUser() needs to be implemented.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Symfony\Component\Security\Core\Authentication\Token;

/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
abstract class AbstractAuthenticatedToken extends AbstractToken implements AuthenticatedTokenInterface
{
private $identifier;

/**
* @param string $identifier An identifier for the authenticated user
* @param object $user The current user
* @param array $roles The roles of the authenticated user
*/
public function __construct($identifier, $user, array $roles)
{
parent::__construct($roles);

$this->setUser($user);
}

public function getIdentifier()
{
return $this->identifier;
}

/**
* {@inheritdoc}
*/
public function getCredentials()
{
@trigger_error(__METHOD__.' is deprecated since version 3.1 and will be removed in 4.0.', E_USER_DEPRECATED);
}

/**
* {@inheritdoc}
*/
public function eraseCredentials()
{
@trigger_error(__METHOD__.' is deprecated since version 3.1 and will be removed in 4.0.', E_USER_DEPRECATED);

parent::eraseCredentials();
}

/**
* {@inheritdoc}
*/
public function isAuthenticated()
{
@trigger_error(__METHOD__.' is deprecated since version 3.1 and will be removed in 4.0. Use an instance of check with AuthenticatedTokenInterface instead.', E_USER_DEPRECATED);

return true;
}

/**
* {@inheritdoc}
*/
public function setAuthenticated($authenticated)
{
@trigger_error(__METHOD__.' is deprecated since version 3.1 and will be removed in 4.0.', E_USER_DEPRECATED);
}

/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(array($this->identifier, $this->roles, $this->attributes));
}

/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
list($this->identifier, $this->roles, $this->attributes) = unserialize($serialized);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @deprecated Since version 3.1, to be removed in 4.0. Use AbstractAuthenticatedToken or AbstractRequestToken instead.
*/
abstract class AbstractToken implements TokenInterface
{
private $user;
private $roles = array();
protected $user;
protected $roles = array();
private $authenticated = false;
private $attributes = array();
protected $attributes = array();

/**
* Constructor.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Symfony\Component\Security\Core\Authentication\Token;

/**
* Requests anonymous authentication.
*
* @author Wouter de Jong <wouter@wouterj.nl>
* @author Fabien Potencier <fabien@symfony.com>
*/
class AnonymousRequestToken extends AnonymousToken implements AuthenticationRequestTokenInterface
{
/**
* @param string $secret A secret used to make sure the token is created by the app and not by a malicious client
* @param string $identifier The name of the user (probably "anon.")
* @param array $roles Deprecated, auth request tokens cannot have roles
*/
public function __construct($secret, $identifier, array $roles = [])
{
if (0 < count($roles)) {
@trigger_error('The roles parameter of the constructor of '.__CLASS__.' is deprecated since vesion 3.1 and will be removed in 4.0.', E_USER_DEPRECATED);
}

parent::__construct($secret, $identifier, $roles, false);
}

/**
* {@inheritdoc}
*
* @deprecated Since version 3.1, to be removed in 4.0. Unauthenticated tokens cannot have roles.
*/
public function getRoles()
{
@trigger_error('Method '.__METHOD__.' on unauthenticated tokens is deprecated since version 3.1 and will be removed in 4.0.', E_USER_DEPRECATED);

return parent::getRoles();
}

/**
* {@inheritdoc}
*
* @deprecated Since version 3.1, to be removed in 4.0. Unauthenticated tokens cannot have a user object.
*/
public function getUser()
{
@trigger_error('Method '.__METHOD__.' on unauthenticated tokens is deprecated since version 3.1 and will be removed in 4.0. Use getUsername() instead to retrieve the identifier passed with the request.', E_USER_DEPRECATED);

return parent::getUser();
}

/**
* {@inheritdoc}
*/
public function isAuthenticated()
{
@trigger_error('Method '.__METHOD__.' is deprecated since version 3.1 and will be removed in 4.0. Use an instance of check with AuthenticatedTokenInterface instead.', E_USER_DEPRECATED);

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* AnonymousToken represents an anonymous token.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Since version 3.1, to be removed in 4.0. Use AnonymousRequestToken or AuthenticatedAnonymousToken instead.
*/
class AnonymousToken extends AbstractToken
{
Expand All @@ -29,8 +31,12 @@ class AnonymousToken extends AbstractToken
* @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
* @param Role[] $roles An array of roles
*/
public function __construct($secret, $user, array $roles = array())
public function __construct($secret, $user, array $roles = array(), $deprecation = true)
{
if ($deprecation) {
@trigger_error(__CLASS__.' is deprecated since version 3.1 and will be removed in 4.0. Use AnonymousRequestToken or AuthenticatedAnonymousToken instead.', E_USER_DEPRECATED);
}

parent::__construct($roles);

$this->secret = $secret;
Expand Down
Loading