Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Security] Remove getPassword() and getSalt() from UserInterface #41982

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

Merged
merged 1 commit into from
Jul 9, 2021
Merged
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 @@ -133,16 +133,8 @@ public function supportsClass(string $class)
*
* @final
*/
public function upgradePassword($user, string $newHashedPassword): void
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof PasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/doctrine-bridge', '5.3', 'The "%s::upgradePassword()" method expects an instance of "%s" as first argument, the "%s" class should implement it.', PasswordUpgraderInterface::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user));

if (!$user instanceof UserInterface) {
throw new \TypeError(sprintf('The "%s::upgradePassword()" method expects an instance of "%s" as first argument, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
}
}

$class = $this->getClass();
if (!$user instanceof $class) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user)));
Expand Down
4 changes: 0 additions & 4 deletions 4 src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ public function getPassword(): ?string
{
}

public function getSalt(): ?string
{
}

public function getUsername(): string
{
return $this->name;
Expand Down
4 changes: 2 additions & 2 deletions 4 src/Symfony/Bridge/Doctrine/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"symfony/property-access": "^5.4|^6.0",
"symfony/property-info": "^5.4|^6.0",
"symfony/proxy-manager-bridge": "^5.4|^6.0",
"symfony/security-core": "^5.4|^6.0",
"symfony/security-core": "^6.0",
"symfony/expression-language": "^5.4|^6.0",
"symfony/uid": "^5.4|^6.0",
"symfony/validator": "^5.4|^6.0",
Expand All @@ -58,7 +58,7 @@
"symfony/messenger": "<5.4",
"symfony/property-info": "<5.4",
"symfony/security-bundle": "<5.4",
"symfony/security-core": "<5.4",
"symfony/security-core": "<6.0",
"symfony/validator": "<5.4"
},
"suggest": {
Expand Down
3 changes: 2 additions & 1 deletion 3 src/Symfony/Component/Ldap/Security/LdapUserProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
Expand Down Expand Up @@ -132,7 +133,7 @@ public function refreshUser(UserInterface $user)
*
* @final
*/
public function upgradePassword($user, string $newHashedPassword): void
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof LdapUser) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
Expand Down Expand Up @@ -51,8 +52,13 @@ public function __construct(UserProviderInterface $userProvider, UserCheckerInte
*/
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
if (!$user instanceof PasswordAuthenticatedUserInterface) {
throw new InvalidArgumentException(sprintf('Class "%s" must implement "%s" for using password-based authentication.', get_debug_type($user), PasswordAuthenticatedUserInterface::class));
}

$currentUser = $token->getUser();
if ($currentUser instanceof UserInterface) {

if ($currentUser instanceof PasswordAuthenticatedUserInterface) {
if ($currentUser->getPassword() !== $user->getPassword()) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
Expand All @@ -65,15 +71,7 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke
throw new BadCredentialsException('The presented password is invalid.');
}

if (!$user instanceof PasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-core', '5.3', 'Using password-based authentication listeners while not implementing "%s" interface from class "%s" is deprecated.', PasswordAuthenticatedUserInterface::class, get_debug_type($user));
}

$salt = $user->getSalt();
if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user));
}

$salt = $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null;
$hasher = $this->hasherFactory->getPasswordHasher($user);

if (!$hasher->verify($user->getPassword(), $presentedPassword, $salt)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Core\Authentication\Token;

use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

Expand Down Expand Up @@ -273,14 +274,18 @@ private function hasUserChanged(UserInterface $user): bool
return !(bool) $this->user->isEqualTo($user);
}

// @deprecated since Symfony 5.3, check for PasswordAuthenticatedUserInterface on both user objects before comparing passwords
if ($this->user->getPassword() !== $user->getPassword()) {
return true;
}
if ($this->user instanceof PasswordAuthenticatedUserInterface || $user instanceof PasswordAuthenticatedUserInterface) {
if (!$this->user instanceof PasswordAuthenticatedUserInterface || !$user instanceof PasswordAuthenticatedUserInterface || $this->user->getPassword() !== $user->getPassword()) {
return true;
}

// @deprecated since Symfony 5.3, check for LegacyPasswordAuthenticatedUserInterface on both user objects before comparing salts
if ($this->user->getSalt() !== $user->getSalt()) {
return true;
if ($this->user instanceof LegacyPasswordAuthenticatedUserInterface xor $user instanceof LegacyPasswordAuthenticatedUserInterface) {
return true;
}

if ($this->user instanceof LegacyPasswordAuthenticatedUserInterface && $user instanceof LegacyPasswordAuthenticatedUserInterface && $this->user->getSalt() !== $user->getSalt()) {
return true;
}
}

$userRoles = array_map('strval', (array) $user->getRoles());
Expand Down
4 changes: 3 additions & 1 deletion 4 src/Symfony/Component/Security/Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ CHANGELOG
---

* `TokenInterface` does not extend `Serializable` anymore
* Remove all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead
* Remove all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead
* Remove methods `getPassword()` and `getSalt()` from `UserInterface`, use `PasswordAuthenticatedUserInterface`
or `LegacyPasswordAuthenticatedUserInterface` instead

5.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
Expand Down Expand Up @@ -188,7 +189,7 @@ public function testCheckAuthenticationWhenCredentialsAreNotValid()
public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChanged()
{
$this->expectException(BadCredentialsException::class);
$user = $this->createMock(UserInterface::class);
$user = $this->createMock(TestUser::class);
$user->expects($this->once())
->method('getPassword')
->willReturn('foo')
Expand All @@ -199,7 +200,7 @@ public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChang
->method('getUser')
->willReturn($user);

$dbUser = $this->createMock(UserInterface::class);
$dbUser = $this->createMock(TestUser::class);
$dbUser->expects($this->once())
->method('getPassword')
->willReturn('newFoo')
Expand All @@ -213,7 +214,7 @@ public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChang

public function testCheckAuthenticationWhenTokenNeedsReauthenticationWorksWithoutOriginalCredentials()
{
$user = $this->createMock(UserInterface::class);
$user = $this->createMock(TestUser::class);
$user->expects($this->once())
->method('getPassword')
->willReturn('foo')
Expand All @@ -224,7 +225,7 @@ public function testCheckAuthenticationWhenTokenNeedsReauthenticationWorksWithou
->method('getUser')
->willReturn($user);

$dbUser = $this->createMock(UserInterface::class);
$dbUser = $this->createMock(TestUser::class);
$dbUser->expects($this->once())
->method('getPassword')
->willReturn('foo')
Expand Down Expand Up @@ -338,7 +339,7 @@ protected function getProvider($user = null, $userChecker = null, $passwordHashe
}
}

class TestUser implements UserInterface
class TestUser implements PasswordAuthenticatedUserInterface, UserInterface
chalasr marked this conversation as resolved.
Show resolved Hide resolved
{
public function getRoles(): array
{
Expand All @@ -350,11 +351,6 @@ public function getPassword(): ?string
return 'secret';
}

public function getSalt(): ?string
{
return null;
}

public function getUsername(): string
{
return 'jane_doe';
Expand All @@ -371,7 +367,7 @@ public function eraseCredentials()
}
interface PasswordUpgraderProvider extends UserProviderInterface, PasswordUpgraderInterface
{
public function upgradePassword($user, string $newHashedPassword): void;
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void;

public function loadUserByIdentifier(string $identifier): UserInterface;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,8 @@ public function supportsClass(string $class)
/**
* {@inheritdoc}
*/
public function upgradePassword($user, string $newHashedPassword): void
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof PasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-core', '5.3', 'The "%s::upgradePassword()" method expects an instance of "%s" as first argument, the "%s" class should implement it.', PasswordUpgraderInterface::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user));

if (!$user instanceof UserInterface) {
throw new \TypeError(sprintf('The "%s::upgradePassword()" method expects an instance of "%s" as first argument, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
}
}

foreach ($this->providers as $provider) {
if ($provider instanceof PasswordUpgraderInterface) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@

/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @method void upgradePassword(PasswordAuthenticatedUserInterface|UserInterface $user, string $newHashedPassword) Upgrades the hashed password of a user, typically for using a better hash algorithm.
* This method should persist the new password in the user storage and update the $user object accordingly.
* Because you don't want your users not being able to log in, this method should be opportunistic:
* it's fine if it does nothing or if it fails without throwing any exception.
*/
interface PasswordUpgraderInterface
{
/**
* Upgrades the hashed password of a user, typically for using a better hash algorithm.
*
* This method should persist the new password in the user storage and update the $user object accordingly.
* Because you don't want your users not being able to log in, this method should be opportunistic:
* it's fine if it does nothing or if it fails without throwing any exception.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void;
}
23 changes: 0 additions & 23 deletions 23 src/Symfony/Component/Security/Core/User/UserInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,6 @@ interface UserInterface
*/
public function getRoles();

/**
* Returns the password used to authenticate the user.
*
* This should be the hashed password. On authentication, a plain-text
* password will be hashed, and then compared to this value.
*
* This method is deprecated since Symfony 5.3, implement it from {@link PasswordAuthenticatedUserInterface} instead.
*
* @return string|null The hashed password if any
*/
public function getPassword();

/**
* Returns the salt that was originally used to hash the password.
*
* This can return null if the password was not hashed using a salt.
*
* This method is deprecated since Symfony 5.3, implement it from {@link LegacyPasswordAuthenticatedUserInterface} instead.
*
* @return string|null The salt
*/
public function getSalt();

/**
* Removes sensitive data from the user.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
Expand Down Expand Up @@ -53,22 +52,13 @@ public function validate(mixed $password, Constraint $constraint)

$user = $this->tokenStorage->getToken()->getUser();

if (!$user instanceof UserInterface) {
throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
chalasr marked this conversation as resolved.
Show resolved Hide resolved
}

if (!$user instanceof PasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-core', '5.3', 'Using the "%s" validation constraint without implementing the "%s" interface is deprecated, the "%s" class should implement it.', UserPassword::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user));
}

$salt = $user->getSalt();
if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user));
throw new ConstraintDefinitionException(sprintf('The "%s" class must implement the "%s" interface.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
}

$hasher = $this->hasherFactory->getPasswordHasher($user);

if (null === $user->getPassword() || !$hasher->verify($user->getPassword(), $password, $user->getSalt())) {
if (null === $user->getPassword() || !$hasher->verify($user->getPassword(), $password, $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null)) {
$this->context->addViolation($constraint->message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator

throw new BadCredentialsException(sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', get_debug_type($guardAuthenticator)));
}
if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && $this->passwordHasher->needsRehash($user)) {

if ($user instanceof PasswordAuthenticatedUserInterface && $this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && $this->passwordHasher->needsRehash($user)) {
$this->userProvider->upgradePassword($user, $this->passwordHasher->hashPassword($user, $password));
}
$this->userChecker->checkPostAuth($user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function checkPassport(CheckPassportEvent $event): void
$user = $passport->getUser();

if (!$user instanceof PasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-http', '5.3', 'Not implementing the "%s" interface in class "%s" while using password-based authentication is deprecated.', PasswordAuthenticatedUserInterface::class, get_debug_type($user));
throw new \LogicException(sprintf('Class "%s" must implement "%s" for using password-based authentication.', get_debug_type($user), PasswordAuthenticatedUserInterface::class));
}

/** @var PasswordCredentials $badge */
Expand All @@ -66,12 +66,7 @@ public function checkPassport(CheckPassportEvent $event): void
throw new BadCredentialsException('The presented password is invalid.');
}

$salt = $user->getSalt();
if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) {
trigger_deprecation('symfony/security-http', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user));
}

if (!$this->hasherFactory->getPasswordHasher($user)->verify($user->getPassword(), $presentedPassword, $salt)) {
if (!$this->hasherFactory->getPasswordHasher($user)->verify($user->getPassword(), $presentedPassword, $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null)) {
throw new BadCredentialsException('The presented password is invalid.');
}

Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.