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] Deprecate UserInterface & TokenInterface's eraseCredentials() #59106

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 1 commit into from

Conversation

chalasr
Copy link
Member

@chalasr chalasr commented Dec 6, 2024

Q A
Branch? 7.3
Bug fix? no
New feature? no
Deprecations? yes
Issues Fix #57842
License MIT

Better avoid storing plain credentials on the security user object itself. Core no longer needs to handle this behavior to me.

Comment on lines 7 to 8
* Deprecate `UserInterface::eraseCredentials()`, use a dedicated DTO or erase credentials
on your own e.g. upon `AuthenticationTokenCreatedEvent` instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use __serialize()/__deserialize() instead to only serialize the intended fields? Or is this something we don't want to encourage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I think the DTO approach is the best but the serialization one is also valid https://stackoverflow.com/questions/42074225/symfony-userinterface-is-serializing-the-entire-massive-user-entity. I'm going to update the deprecation messages and upgrade/changelog instructions to mention it, thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about this, I think we should not suggest it: indefinitely keeping plain credentials on this object is likely to lead to bad situations e.g. having credentials persisted in database or exposed elsewhere e.g. logs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if you have then in a non-persisted field of the entity, you will still need to configure serialization (PHP's serialization will not be impacted by which properties are mapped in the ORM metadata)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and there is also another solution: never writing the plain password at all in your entity by using -h' hash_property_path option of the PasswordType (defined by the appropriate type extension when the password-hasher component is available) to write directly the password hash instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one

@chalasr chalasr force-pushed the deprecate-erase-credentials branch 2 times, most recently from a0b8874 to e622479 Compare December 7, 2024 13:06
src/Symfony/Component/Security/Core/CHANGELOG.md Outdated Show resolved Hide resolved
@chalasr chalasr force-pushed the deprecate-erase-credentials branch 3 times, most recently from 2263cdf to 3a93276 Compare December 9, 2024 13:20
@chalasr chalasr changed the title [Security] Deprecate UserInterface::eraseCredentials() [Security] Deprecate UserInterface & TokenInterface's eraseCredentials() Dec 9, 2024
@chalasr chalasr force-pushed the deprecate-erase-credentials branch 2 times, most recently from aece9de to 3f2503c Compare December 9, 2024 14:35
@chalasr
Copy link
Member Author

chalasr commented Dec 9, 2024

All comments have been addressed

@xabbuh
Copy link
Member

xabbuh commented Dec 9, 2024

The verity packages job failure kids
looks related to me.

@chalasr chalasr force-pushed the deprecate-erase-credentials branch from 3f2503c to c057ba3 Compare December 10, 2024 00:35
@chalasr
Copy link
Member Author

chalasr commented Dec 10, 2024

@xabbuh Indeed, fixed. Thank you

UPGRADE-7.3.md Outdated Show resolved Hide resolved
@@ -135,6 +135,9 @@ public function load(array $configs, ContainerBuilder $container): void

// set some global scalars
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
if (true === $config['erase_credentials']) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we do this check in the MainConfiguration class instead?

@nicolas-grekas
Copy link
Member

What's the alternative? Isn't this PR making it easier to inadvertently put passwords in the session storage?

@chalasr
Copy link
Member Author

chalasr commented Jan 7, 2025

The alternative is to never store plain credentials in the user object especially if it's a doctrine entity but also any object that lives longer than the time required to deal with plain credentials e.g. using a DTO instead as suggested in deprec notices or just an extra form field in case of symfony/form, one that don't map to any property of the User object - that's what make:registration-form does for a while hence the 98% of user classes having this method empty.

@rosier
Copy link
Contributor

rosier commented Jan 11, 2025

I'm wondering if moving the method eraseCredentials() from the UserInterface to it's own EraseCredentialsInterface could be a better way to allow the removal of the empty eraseCredentials() method from the User objects and also provide a smooth upgrade path for projects that still need the feature.

Combined with not deprecating the TokenInterface or a listener that checks for the EraseCredentialsInterface.

@chalasr
Copy link
Member Author

chalasr commented Jan 11, 2025

Based on my experience both as a Symfony core developer and a consultant, we don't need an equivalent given this is not needed for 90+% use case, a few sentences in the documentation are enough instead taking what we already have in term of educative content and docs into account. But maybe I'm wrong, if other core members and good part of Symfony developers think such alternative is needed in core, I will happily reconsider and smoothly turn this config option to false by default + extract this out of UserInterface.

@rosier
Copy link
Contributor

rosier commented Jan 11, 2025

To be clear. I'm fine with completely removing the erase credentials feature.

I just figured if others see a roadblock splitting the interface might be a way out to get ride of the empty eraseCredentials() method from the User objects.

@chalasr
Copy link
Member Author

chalasr commented Jan 11, 2025

👍 I don't think we are there. Also talked about this on stage at SymfonyCon and didn't got any negative reaction, not even questions about this. The practice behind the deprecated feature is subject to trouble, I think the community already moved away from it.

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too abrupt.
We're forcing a deprecation on code that could be perfectly fine.
I'm fine with not forcing an empty eraseCredentials method, but not with making it harder for ppl that need a way to erase credentials.

UPGRADE-7.3.md Outdated Show resolved Hide resolved
UPGRADE-7.3.md Outdated Show resolved Hide resolved
@nicolas-grekas
Copy link
Member

nicolas-grekas commented Jan 17, 2025

Note that I'm not sure putting hashed passwords in the session storage should really be encouraged, so having a non-empty eraseCrentials (as generated by maker-bundle) might not be a best-practice either.

@chalasr
Copy link
Member Author

chalasr commented Jan 17, 2025

Let’s go for a dedicated interface then, leaving the config as it is today.

@stof
Copy link
Member

stof commented Jan 17, 2025

@nicolas-grekas eraseCredentials is not a proper solution to avoid including the hashed password in the serialized object. For that, the proper solution is __serialize.
Removing the hashed password from the object is very risky, because if you call flush() on the ORM after that, it will update the database erasing the credentials (and then you cannot authenticate anymore)

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Jan 17, 2025

Maybe the security system should always clone the object it stores before? Because I agree mutating is risky.

@nicolas-grekas
Copy link
Member

For that, the proper solution is __serialize

I'm wondering about this also: is the proper solution to implement __serialize / __unserialize?
@chalasr you're talking about having a DTO, but I'm not sure this is really a DX-friendly alternative. I wouldn't know how to implement that in just a few lines, while I could with added serialization logic.

@stof
Copy link
Member

stof commented Jan 17, 2025

Having a method on the class that is unsafe to call without first cloning the object might be a footgun in projects. And this would also require that the object is cloneable, which is not a requirement today (and for instance, I'm totally not sure about whether cloning a Doctrine ORM proxy instance works fine when the proxy has not been initialized yet or whether it produces a broken instance that cannot ever be initialized, while using a Doctrine entity as the user implementation is a common case and so we could end up with a proxy instance)

@nicolas-grekas
Copy link
Member

I don't think serializing an uninitialized entity can happen: it'd mean serializing an entity who's password was never checked. From the pov of the security component, this doesn't make sense.

But cloning using clone is not needed if that's not safe: we can instead serialize then unserialize. That's another way to clone that's explicitly required for user objects. Maybe a better way forward?

@nicolas-grekas
Copy link
Member

So, it looks like we're good with adding the suggestion of implementing __serialize() as the recommended method, isn't it?

This is somewhat related to #59562 where I explain how the magic method can be used to tweak what's serialized.
Let's update this PR to give a hint in deprecations? Also some PHP doc somewhere?

@chalasr
Copy link
Member Author

chalasr commented Jan 20, 2025

Not the alternative I would take but that'll be for a blogpost, let's do it the fast way 👍 Thanks for your help on moving forward here, I'll update this asap.

nicolas-grekas added a commit that referenced this pull request Jan 29, 2025
…as not stored in the session (nicolas-grekas)

This PR was merged into the 7.3 branch.

Discussion
----------

[Security] Don't invalidate the user when the password was not stored in the session

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Related to #59106: this PR does is considering that if `$originalUser` (the object coming from the session) has a null password, then we don't consider it changed from `$refreshedUser`. Aka we don't log out the user in such case.

The benefit is allowing to not put the hashed password in the session. I think that's desirable.

Commits
-------

3d618db [Security] Don't invalidate the user when the password was not stored in the session
@chalasr chalasr force-pushed the deprecate-erase-credentials branch from 99372fe to e36259d Compare January 31, 2025 13:17
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  • I think we should tell about __serialize, it's a way better alternative that doesn't require mutating the user/token objects. My review notes are in this direction.
  • I don't like the fact we have yet another option to explicitly configure just to unconfigure it on 8.1, forcing 100% of apps to add the line, and remove it in a few years. I have an idea but I need to code it - doesn't fit as a review comment and might be a dead end :)

UPGRADE-7.3.md Show resolved Hide resolved
UPGRADE-7.3.md Show resolved Hide resolved
UPGRADE-7.3.md Show resolved Hide resolved
@nicolas-grekas
Copy link
Member

See #59682 for what I had in mind.

@chalasr
Copy link
Member Author

chalasr commented Feb 4, 2025

Closing in favor of #59682

@chalasr chalasr closed this Feb 4, 2025
nicolas-grekas added a commit that referenced this pull request Feb 4, 2025
…`eraseCredentials()` (chalasr, nicolas-grekas)

This PR was merged into the 7.3 branch.

Discussion
----------

[Security] Deprecate UserInterface & TokenInterface's `eraseCredentials()`

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | yes
| Issues        | Fix #57842
| License       | MIT

As promised, this PR adds a commit on top of #59106 to improve the BC layer. This approach didn't fit in a review comment :) /cc `@chalasr`

This PR leverages the new `#[\Deprecated]` attribute to decide if some `eraseCredentials()` method is to be called or not.

My target DX here is to save us all (the community) from having to add `erase_credentials: false` configuration in all our apps.

So, instead of having to opt-out from the deprecation using this config option, the opt-out is done by adding the attribute on the method:

Before:
```php
public function eraseCredentials(): void
{
}
```

After:
```php
#[\Deprecated]
public function eraseCredentials(): void
{
}

// If your eraseCredentials() method was used to empty a "password" property:
public function __serialize(): array
{
    $data = (array) $this;
    unset($data["\0".self::class."\0password"]);

    return $data;
}
```

This should provide a smoother upgrade path (and maker-bundle could be updated right-away).

Commits
-------

e5c94e6 [Security] Improve BC-layer to deprecate eraseCredentials methods
e556606 [Security] Deprecate `UserInterface` & `TokenInterface`'s `eraseCredentials()`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Move or retire UserInterface::eraseCredentials
9 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.