v1.13.0
Adds a directory-independent way to ask when a password expires, so a caller no longer has to assemble that from raw attributes — differently per backend, and wrongly in the cases that matter.
Password-expiry reporting
PasswordExpiryFor(ctx, user) returns when a user's password expires; UsersWithExpiringPasswords(ctx, within) returns the enabled users whose password expires inside a window, oldest deadline first. Both distinguish four states rather than returning a bare timestamp:
expires— a concrete deadline, inPasswordExpiry.At(which may already be in the past)never-expires—DONT_EXPIRE_PASSWORDon AD, or a policy withpwdMaxAge0must-change— ADpwdLastSet=0; the account is blocked until the password is changedunknown— the directory reported nothing usable
The distinction is the point. A consumer that collapses unknown into "expiring" would act on every account the directory happens to be quiet about — for a reminder mailer, that means mailing everyone.
Active Directory resolves from the constructed msDS-UserPasswordExpiryTimeComputed. Because it is constructed, the server folds in the domain policy and any Password Settings Object, so no privileged read of the Password Settings Container is required. New User fields PasswordExpiresAt, PwdChangedAt and PasswordPolicyDN expose the underlying attributes.
OpenLDAP resolves from the ppolicy operational pwdChangedTime plus the governing policy's pwdMaxAge, taken from the entry's pwdPolicySubentry or the new Config.PasswordPolicyDN. pwdMaxAge is memoised per policy DN, so a directory scan costs one policy read per distinct policy rather than one per user. Note that OpenLDAP has no userAccountControl: every OpenLDAP user reads as enabled, so a caller that must skip deactivated accounts has to do so by its own criterion. (#186, thanks @CybotTM)
Fixed
FindUserByDNContext requested a shorter attribute list than every other user search, so it silently returned a User with pwdLastSet, accountExpires, lockoutTime and whenCreated zeroed — the same type meaning different things depending on which finder produced it. It now uses the shared userFields.
Verification
The OpenLDAP path is exercised end to end against a real server: the ppolicy overlay is enabled on the test container, a password change records pwdChangedTime, and the resolved deadline is checked against pwdChangedTime + pwdMaxAge. A policy with pwdMaxAge 0 is verified to report never-expires, and a directory without the overlay to report unknown rather than expiring — the difference between warning the right people and mailing the whole directory. Two details are recorded in the test code because the server, not the documentation, taught them: pwdAttribute requires the numeric OID 2.5.4.35 and rejects the attribute name as a syntax error, and osixia's image ships ppolicy.la without loading it.
CI
The codecov unit flag is declared as unittests, matching what go-check.yml uploads under; the previous unit name left real unit coverage attributed to an undeclared flag while a stale one carried forward, so unit-tested lines could show as uncovered. (#187)
Full changelog: v1.12.2...v1.13.0