Skip to content

Add support for argon2#808

Open
Ph0tonic wants to merge 1 commit into
DirectoryTree:masterfrom
Ph0tonic:master
Open

Add support for argon2#808
Ph0tonic wants to merge 1 commit into
DirectoryTree:masterfrom
Ph0tonic:master

Conversation

@Ph0tonic

@Ph0tonic Ph0tonic commented Jun 9, 2026

Copy link
Copy Markdown

Hey, here is a PR to support argon hashing algorithm 🎉

Add argon2i and argon2id password hashing support

Adds Password::argon2i() and Password::argon2id() methods to the Password class, enabling use of the {ARGON2I} and {ARGON2ID} userPassword schemes supported by OpenLDAP via the pw-argon2 module.

Changes

  • Add Password::argon2i() and Password::argon2id() static methods using PHP's native password_hash()
  • Throw a LdapRecordException when attempting to change an argon2 password using the array syntax ($user->password = ['old', 'new']), since argon2 hashes are non-deterministic and the old hash cannot be reproduced to satisfy the LDAP REMOVE operation

Notes

  • Requires PHP 8.1+ (already the project minimum) with libargon2 support (standard on modern distributions)
  • Requires the OpenLDAP pw-argon2 contrib module to be loaded server-side (moduleload pw-argon2) — without it, the server will reject the {ARGON2ID} scheme
  • Password reset ($user->password = 'new') works as expected; only the password change (array) flow is unsupported — this can be addressed in a follow-up by implementing the LDAP Password Modify extended operation (ldap_exop_passwd)

@stevebauman

Copy link
Copy Markdown
Member

Thanks for the PR @Ph0tonic!

In regards to this:

Password reset ($user->password = 'new') works as expected; only the password change (array) flow is unsupported — this can be addressed in a follow-up by implementing the LDAP Password Modify extended operation (ldap_exop_passwd)

According to the docs for this method, it supports being called with $old_password here:

https://www.php.net/manual/en/function.ldap-exop-passwd.php

function ldap_exop_passwd(
    LDAP\Connection $ldap,
    string $user = "",
    #[\SensitiveParameter] string $old_password = "",
    #[\SensitiveParameter] string $new_password = "",
    array &$controls = null
): string|bool

So it looks like we may be able to support the same flow without throwing the exception here.

We could extract the mechanism for changing passwords into some interface and classes depending on the method being used. Maybe something like (psuedo-code):

$changer = match (strtolower($method)) {
    'argon2i', 'argon2id' => new ArgonPasswordChanger(...),
    default => new DefaultPasswordChanger(...),
};

Thoughts?

@stevebauman

stevebauman commented Jun 9, 2026

Copy link
Copy Markdown
Member

In other words I think we should implement the full operation here instead of in a follow up, because if the exception is no longer thrown then that's a major breaking change.

@Ph0tonic

Copy link
Copy Markdown
Author

I agree — supporting this directly is the right approach.

The challenge is that setChangedPassword works by queuing a BatchModification that gets executed asynchronously when save() is called. ldap_exop_passwd doesn't fit into that model — it's a separate LDAP protocol operation that can't be expressed as a batch modification, so it can't participate in the existing modification queue.

If we call exopPasswd directly in the setter it executes immediately, breaking the deferred-write contract that the rest of the password flow follows.

To defer it to save() time, we'd need an extension point in Model::performUpdate() between the "no modifications, bail early" guard and the event dispatching — something like hasPendingUpdates() / executePendingUpdates() hooks that traits could override. Without that hook, we're forced to either override performUpdate() entirely (which duplicates the dispatch/sync sequence) or execute eagerly in the setter (which breaks the save contract).

Would you be open to adding such hooks to Model, or is there a pattern in the codebase I'm missing that already handles this kind of deferred non-batch operation?

@stevebauman

Copy link
Copy Markdown
Member

Yea you're right, will be tricky to maintain that queued behaviour. I think we could register a one-time event listener on the updating event to bail/throw an exception if the password change fails. Let me take a look into this and see what I can do 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants