1010/** @mixin Model */
1111trait HasPassword
1212{
13+ /**
14+ * Pending argon2 password change to be executed on save via RFC 3062 extended operation.
15+ *
16+ * @var array{0: string, 1: string}|null
17+ */
18+ protected ?array $ pendingPasswordExopChange = null ;
19+
20+ /**
21+ * Execute any pending argon2 password change via exop, then process remaining modifications.
22+ */
23+ protected function performUpdate (): void
24+ {
25+ if ($ this ->pendingPasswordExopChange === null ) {
26+ parent ::performUpdate ();
27+
28+ return ;
29+ }
30+
31+ [$ oldPassword , $ newPassword ] = $ this ->pendingPasswordExopChange ;
32+
33+ $ this ->pendingPasswordExopChange = null ;
34+
35+ $ modifications = $ this ->getModifications ();
36+
37+ $ this ->dispatch ('updating ' );
38+
39+ $ this ->setChangedPasswordViaExop ($ oldPassword , $ newPassword );
40+
41+ if (count ($ modifications )) {
42+ $ this ->newQuery ()->update ($ this ->dn , $ modifications );
43+ }
44+
45+ $ this ->dispatch ('updated ' );
46+ $ this ->syncChanges ();
47+ $ this ->syncOriginal ();
48+ }
49+
1350 /**
1451 * Set the password on the user.
1552 *
@@ -29,17 +66,14 @@ public function setPasswordAttribute(array|string $password): void
2966 // If the password given is an array, we can assume we
3067 // are changing the password for the current user.
3168 if (is_array ($ password )) {
32- if (in_array (strtolower ($ method ), ['argon2i ' , 'argon2id ' ])) {
33- throw new LdapRecordException (
34- "Argon2 passwords cannot be changed using this method. Use the LDAP Password Modify extended operation instead. "
35- );
36- }
37-
38- $ this ->setChangedPassword (
39- $ this ->getHashedPassword ($ method , $ password [0 ], $ this ->getPasswordSalt ($ method )),
40- $ this ->getHashedPassword ($ method , $ password [1 ]),
41- $ this ->getPasswordAttributeName ()
42- );
69+ match (true ) {
70+ Password::hashMethodRequiresExop ($ method ) => $ this ->pendingPasswordExopChange = [$ password [0 ], $ password [1 ]],
71+ default => $ this ->setChangedPassword (
72+ $ this ->getHashedPassword ($ method , $ password [0 ], $ this ->getPasswordSalt ($ method )),
73+ $ this ->getHashedPassword ($ method , $ password [1 ]),
74+ $ this ->getPasswordAttributeName ()
75+ ),
76+ };
4377 }
4478 // Otherwise, we will assume the password is being
4579 // reset, overwriting the one currently in place.
@@ -125,6 +159,24 @@ protected function setChangedPassword(string $oldPassword, string $newPassword,
125159 );
126160 }
127161
162+ /**
163+ * Change the password using the LDAP Password Modify extended operation (RFC 3062).
164+ *
165+ * @throws LdapRecordException
166+ */
167+ protected function setChangedPasswordViaExop (string $ oldPassword , string $ newPassword ): void
168+ {
169+ if (! $ this ->exists || ($ dn = $ this ->getDn ()) === null ) {
170+ throw new LdapRecordException (
171+ 'Cannot change password on a model that does not exist in the directory. '
172+ );
173+ }
174+
175+ $ this ->getConnection ()
176+ ->getLdapConnection ()
177+ ->exopPasswd ($ dn , $ oldPassword , $ newPassword );
178+ }
179+
128180 /**
129181 * Set the password on the model.
130182 */
0 commit comments