-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Security] New Password Policy listener #49821
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
Conversation
ca3689a
to
944f4d1
Compare
Hi! Thanks for the series of security feature PRs! However, I have some doubts about this particular feature: A) If we ignore length constraints, all other constraints seem more suited in a registration process and not an authentication process. I can't recall seeing a website that tells me I have an unsafe password during authentication. For the basic constraints like length, I don't think we need to integrate with the Validator component (we already check on length btw) B) The badges and passport listeners are created to centralize security sensitive code. I don't consider this validator a security critical feature. Also, given the validator component has its own simple high level API, I think using it directly in your own listener or authenticator instead of this badge is more clear of the intent. |
The use case we talked about with @Spomky made sense to me: By having constraints as a badge, we can then easily implement login strategies where a password reset is enforced when ppl log in with a weak password (when some policy is strengthened). |
Hi @wouterj, Many thanks for your feedback and your advice. |
@wouterj WDYT of our responses to your comment? |
Sorry, I have been thinking about this PR the past weeks, but I didn't write down my thoughts here. I think the "prevent login with a compromised password" idea is very interesting. This is becoming a more common practice, and is also recommended by NIST. However, I think I would like a very scoped badge rather than a generic badge like this one, to "limit" the usages, which makes it easier to make changes on later on and discourage bad practices (e.g. requiring a password to contain at least 1 special character, etc.). Two other things I have in mind are:
|
I agree such concern would be better handled outside of the authenticator despite the "opportunistic upon login" aspect. |
Just a dummy example, we might do something like this: namespace Symfony\Component\PasswordHasher\Policy;
interface PasswordPolicy
{
public function accept(string $plaintextPassword): bool;
} security:
password_policy:
- NotCompromisedPolicy
- 'App\Security\CustomPolicy' A listener on We could extract the haveibeenpwned integration from the Validation component (into PasswordHasher?) and reuse it in both the constraint and policy. Maybe we implement some more policies, but we should make sure to only implement policies following best practices (e.g. from NIST). This last point is also why I'm not keen to integrate with the Validator component, this makes it way to easy to follow bad practices imho. This would make it quite easy to enforce these policies, while still allowing users to implement not-best practice policies when necessary (e.g. a client of us requires us to have an "update password every x months" policy). |
Many thanks for your feedback.
To be honest, this is what I usually do and that was used in recent workshops (badge and listener). It works fine and most of the code is mostly copied from the constraint.
OK noted. When the badge is marked as resolved, I will clear it.
OK. To me, this means this |
I understand the logic. Let me time for implementing it. |
Cool, thanks for continueing this. Take it easy, the next future freeze is far away 🙂 and feel free to ping me if you need help getting this ready for 6.4. |
ConstraintBadge
and listener
🫢I took time to redo this PR (only the component). I concur with you, the design is much better now. |
f1d8efd
to
f730b43
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the XSD file must be updated
src/Symfony/Component/Security/Http/EventListener/PasswordPolicyListener.php
Outdated
Show resolved
Hide resolved
...ymfony/Bundle/SecurityBundle/Tests/Functional/app/PasswordPolicy/BlacklistPasswordPolicy.php
Outdated
Show resolved
Hide resolved
src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
Outdated
Show resolved
Hide resolved
src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
Outdated
Show resolved
Hide resolved
Many thanks @stof. I addressed your comments. I have an issue with the test that fails. It looks like the value of |
Well, I don't think using the same name for the singular and the plural works fine. I think you should use an actual plural name |
|
c110187
to
ea2516f
Compare
ea2516f
to
1b69f77
Compare
if (!$config['password_policies']) { | ||
$container->removeDefinition('security.password_policy_listener'); | ||
} else { | ||
$policyServices = array_map(static fn (string $id) => new Definition($id), $config['password_policies']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is correct: entries in the list of policies cannot directly map to a service definition. that'd mean not being allowed to configure their constructor arguments for example.
Instead, we could use new Reference
, but I'm wondering if we're not missing a DI tag to do the link, and use tagged_iterator()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. I modified the extension and my service definitions in the test.
The use of a tag is a good idea, but can we detect if a service is tagged in this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not in this method. We would have to do it in a compiler pass (or use tagged_iterator
without checking things)
src/Symfony/Component/Security/Http/Authenticator/Passport/Policy/PolicyInterface.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Http/Authenticator/Passport/Policy/PolicyInterface.php
Outdated
Show resolved
Hide resolved
d77603d
to
b508f6d
Compare
7a94c5b
to
ca558b9
Compare
*/ | ||
public function __construct( | ||
private readonly ValidatorInterface $validator, | ||
private readonly array $constraints = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing comma at the end
/** | ||
* @throws AuthenticationException | ||
*/ | ||
public function verify(#[\SensitiveParameter] string $plaintextPassword): bool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we stipulate that, instead of returning a bool, it must either throw a PasswordPolicyException
or return void?
f8d02f3
to
dcca81c
Compare
Closing as the concept should be revised in depth. |
This PR adds the possibility to define security policy on the password.
When at least one rule is defined, the PasswordPolicyListener will be called to verify the submitted password (on login) is acceptable.
The aim of this feature is to enforce the update of passwords.
Hereafter some use cases:
The listener has a high priority (512, TBD) in order to be called before the user is fetched and the password is checked against the value in the database. The policy is only based on the plain text password value and not on the user state (e.g. last password update).
Todo: