Let users sign in with their directory (Active Directory / OpenLDAP)
credentials through the existing login form. Reuses sso_providers
(kind='ldap') and user_sso_identities, so no schema change is needed.
Authentication is search-then-bind: a service account resolves the user and
only the returned DN is bound with the submitted password, so user input is
never used as a bind identity. Roles come from a role template at first
provisioning only; changing a directory group later never re-templates an
existing account. Directory outages and wrong passwords stay distinguishable
(502 LDAP_UNAVAILABLE vs 401 AUTH_FAILED).
Hardening from review:
- An account holding its own password stops at the local check, so a local
secret is never forwarded to the directory nor counted against that
directory's own lockout policy.
- A lookup without a unique exact identifier match is refused instead of
being bound against the first hit.
- A failure during the user bind surfaces as LDAP_UNAVAILABLE rather than a
401 credential verdict.
- Binds are throttled before they reach the directory, keyed per identifier
and client address, with the address resolved from a trusted peer so a
spoofed X-Forwarded-For cannot reset the budget.
- Enabling a plain ldap:// URL without StartTLS is rejected; a disabled
certificate check is surfaced as a warning.
- auto_provision defaults to off, with an optional allowed_groups allow-list
and a group-search mode (member / uniqueMember / memberUid) for directories
that do not expose memberOf. Nested groups are not resolved.
- The identity key is configurable (entryUUID / objectGUID) and blank by
default; a connectivity probe reports which attribute the directory
exposes.
- Config changes are audited against the acting admin, and referrals are no
longer followed during binds.
Tests cover the above without a live directory (ldap3.Connection is the only
thing replaced); a live test exercises a real directory when configured.
Co-authored-by: jubaoliang <jubaoliang@gmail.com>