The customer reported, and I can reproduce it, that the password cannot be changed from a teacher (user) after the password is expired by a policy and the user logs in via saml. How to reproduce: root@primary:~# univention-app info UCS: 5.0-9 errata1125 Installed: samba4=4.16 self-service=5.0 self-service-backend=5.0 or root@ucs-single:~# univention-app info UCS: 5.0-9 errata1125 Installed: self-service=5.0 self-service-backend=5.0 Upgradable: use a password policy, to set the password expiry date: DN: cn=pw-teacher,cn=policies,dc=lilly,dc=cat expiryInterval: 3 ldapFilter: None length: 1 name: pw-teacher pwLength: 10 pwQualityCheck: TRUE Adjust this policy on the user. When the day comes, the user is prompted to change his password. He logs in via saml/sso (no keycloak) and sets the new password. Positive feedback " Your password is changed, please login" Using the new Password shows: "Nutzername oder Passwort falsch. Entweder es konnte kein Nutzer mit dem angegebenen Nutzernamen gefunden werden oder das Passwort ist falsch. Überprüfen Sie die Zugangsdaten und probieren Sie es nochmal" ----------------- "Username or password incorrect. Either no user with the specified user name could be found or the password is incorrect. Check the access data and try again” So this is a loop now, using the old password, the user is still prompted to set a new password. Next day, everything works fine. So swpool is a user, where the checkbox, "has to change his password on next login" is set manually. --------------- root@primary:~# univention-ldapsearch -LLL uid=swpool shadowMax shadowLastChange sambaPwdLastSet krb5PasswordEnd dn: uid=swpool,cn=users,dc=lilly,dc=cat shadowMax: 1 shadowLastChange: 20015 sambaPwdLastSet: 0 krb5PasswordEnd: 20241021000000Z lillycat is a user, with the password policy root@primary:~# univention-ldapsearch -LLL uid=lillycat shadowMax shadowLastChange sambaPwdLastSet krb5PasswordEnd dn: uid=lillycat,cn=users,dc=lilly,dc=cat shadowMax: 3 shadowLastChange: 20014 sambaPwdLastSet: 1729243804 krb5PasswordEnd: 20241021000000Z ---------------- udm shows different: DN: uid=swpool,cn=users,dc=lilly,dc=cat passwordexpiry: 2024-10-20 DN: uid=lillycat,cn=users,dc=lilly,dc=cat passwordexpiry: 2024-10-21 If we do not use sso, the password is not marked as expired and works fine. For analysis I have a test environment (10.200.43.140)
The reason is, that at the day of expiry, both shadowLastChange + shadowMax and krb5PasswordEnd point to the same day. shadowLastChange + shadowMax on that user equate to '2024-10-21' And krb5PasswordEnd is 20241021000000Z. This should mean that they are consistent, right? Suprise, they are not. Kerberos regards krb5PasswordEnd as the first day where the password is expired. While Unix (with the shadow attributes) regards the day of expiry as the last day the password is valid. This leads to this very strange behaviour on the day where the password expires. SimpleSAMLphp checks in its php code if the password is expired. It checks the Kerberos attributes and the Unix attributes. When any of them regard the password as being expired, it prepares for a password change and the password change page is rendered. After the user puts in username and password, the UMCs set-password endpoint is called. The UMC goes through the PAM stack. And the PAM stack uses pam_unix first. For pam_unix, the user is dandy, not expired, auth successful, no passwordchange to do. Returns success. Then the user gets a "password change successful" message and is happy and can login. Every time they log in again on that day, they'll get that password change prompt and they'll only be able to log in with the old password and not with the new one (because no passwordchange has actually happened). From reading the code, this looks like the same problem could be in Keycloak. I don't know how to solve this best. Maybe we could set shadowMax to be one day less than the Administrator configured in their passwordpolicy.
From looking at the code this is also relevant for Keycloak.
A great example for the ambiguous meaning of "expiry date": Is it the day that the password expires (meaning that if 21.11. is configured you cannot login anymore beginning with 21.11. 00:00 h) or is it the last day until the password expires (like a "best before" date: You have _until the 21.11. to change the password, afterwards on the 22.11. 00:00 h, it will be expired). We are aiming for a uniform, consistent handling in our products in alignment with connected services if possible, so we should go for the former: If it says "21.11." then you cannot login on the 21.11. 00:00.
e94b377c00d | Issue univention/ucs#2552: shadowMax := pwhistoryPolicy.expiryInterval - 1 0f8bb9696c0 | Issue univention/ucs#2552 changelogs and advisories Package: univention-directory-manager-modules Version: 15.0.28-9 Branch: 5.0-0 Scope: errata5.0-9 Package: univention-s4-connector Version: 14.0.19-2 Branch: 5.0-0 Scope: errata5.0-9
0c139780155 | Issue univention/ucs#2552: AD-Connector too Package: univention-ad-connector Version: 14.0.20-4 Branch: 5.0-0 Scope: errata5.0-9
cf02335b4 | Adjust shadowbind overlay to treat shadowMax == 0 like pam_unix does Package: openldap Version: 2.4.47+dfsg-3+deb10u7A~5.0.0.202503041938 Branch: 5.0-0 Scope: errata5.0-9
d46080ed935 | Handle case pwhistoryPolicy.expiryInterval == 0 (or undefined) bb1cd434b11 | adjust tests to work with shadowMax = expiryInterval - 1 Package: univention-directory-manager-modules Version: 15.0.28-10 Branch: 5.0-0 Scope: errata5.0-9 Package: univention-ad-connector Version: 14.0.20-5 Branch: 5.0-0 Scope: errata5.0-9 Package: ucs-test Version: 12.0.23-41 Branch: 5.0-0 Scope: errata5.0-9
"b50-scope errata5.0-9" gets the version wrong (should be ~5.0-9...), so I've rebuilt the package and directly specified the version: Package: openldap Version: 2.4.47+dfsg-3+deb10u7A~5.0.9.202503051037 Branch: 5.0-0 Scope: errata5.0-9
As the process for the patch level release 5.0-10 is already in it's final stages we decided not to interfere and release this via errata5.0-10. a05327945dc | shadowMax := pwhistoryPolicy.expiryInterval - 1 b1148fe5df6 | changelogs and advisories 0f471b96d46 | Handle case pwhistoryPolicy.expiryInterval == 0 (or undefined) Package: univention-directory-manager-modules Version: 15.0.29-1 Branch: 5.0-0 Scope: errata5.0-10 Package: univention-s4-connector Version: 14.0.20-1 Branch: 5.0-0 Scope: errata5.0-10 ab2a0db34b8 | AD-Connector too 7da8a92a0a7 | changelogs and advisories (fixup) Package: univention-ad-connector Version: 14.0.21-1 Branch: 5.0-0 Scope: errata5.0-10 d5632c3fcc2 | reduce flakiness of playwright password change test Package: ucs-test Version: 10.0.24-5 Branch: 5.0-0 Scope: errata5.0-10 repo_admin.py --cherrypick --release 5.0-0 --source errata5.0-9 --releasedest 5.0-0 --dest errata5.0-10 --package openldap 75cde93ca | patch merged by repo-ng build-package-architecture-ng -r 5.0-0-0 -s errata5.0-10 --version "2.4.47+dfsg-3+deb10u7A~5.0.10.202503071652" -p openldap Package: openldap Version: 2.4.47+dfsg-3+deb10u7A~5.0.10.202503071652 Branch: 5.0-0 Scope: errata5.0-10 e8d07f728ac | Advisory update for errata5.0-10
OK: YAML OK: Changelog OK: Password change on the day of password expiry works both with Keycloak and simpleSAMLphp OK: Tests OK: User has to change password on next login OK: change password in Windows correctly syncs PW expiry to UCS (S4C) OK: change password in Windows correctly syncs PW expiry to UCS (ADC) OK: Password change prompt on day of expiry aligns with Windows
a61c0574cab | fix 55_adconnector/503test_password_change_next_logon Package: ucs-test Version: 10.0.24-7 Branch: 5.0-0 Scope: errata5.0-10
<https://errata.software-univention.de/#/?erratum=5.0x1221> <https://errata.software-univention.de/#/?erratum=5.0x1222> <https://errata.software-univention.de/#/?erratum=5.0x1223> <https://errata.software-univention.de/#/?erratum=5.0x1224>