Skip to content

v1.13.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 20:21
· 71 commits to main since this release
v1.13.0
f2fc316

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, in PasswordExpiry.At (which may already be in the past)
  • never-expiresDONT_EXPIRE_PASSWORD on AD, or a policy with pwdMaxAge 0
  • must-change — AD pwdLastSet=0; the account is blocked until the password is changed
  • unknown — 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