Reporter @Fuechslein flagged that disabling LDAP auto-provision left admins
with no UI path to onboard new users — the create-user form had zero LDAP
awareness and the only workaround was hand-editing the database.
Add a Local / LDAP tab toggle to the create-user modal (hidden when LDAP is
disabled). The LDAP tab is a debounced directory search (≥2 chars, 300ms)
that returns up to 25 matches via the service-account bind, annotated with
already_provisioned so existing usernames render disabled. Clicking
"Provision user" re-resolves via the service bind and creates the user
through the same _provision_ldap_user helper the auto-provision login path
uses, so group mapping, default-group fallback, and email sync are identical
regardless of which path created the user.
The picker component is shared across all four create-user modal paths
(UsersPage basic + advanced, SettingsPage basic + advanced).
Two ldap3 schema-check workarounds were needed for OpenLDAP installs:
- Open the search connection with check_names=False so ldap3 doesn't reject
the cross-schema OR filter (sAMAccountName/displayName are AD-only)
- Request attributes=["*"] because ldap3's build_attribute_selection
validates each named attribute against the server schema regardless of
check_names, and only the * wildcard is in its hard-coded exclusion list
Login/lookup paths keep check_names=True so typos in user_filter still fail
loudly.
Backend
- New routes: GET /auth/ldap/search, POST /auth/ldap/provision (both gated
by USERS_CREATE; 503 details include ldap3 exception class + message)
- Extract _open_service_connection + _extract_user_info helpers so
authenticate_ldap_user, lookup_ldap_user, and search_ldap_users share the
bind and attribute-extraction logic
Frontend
- New LdapUserPicker component (debounced search, result list, provision
mutation, already-provisioned guard, error surface)
- Tab toggle wired into UsersPage and SettingsPage modals, plus
CreateUserAdvancedAuthModal props
- 14 i18n keys added to en.ts (other locales fall back to English)
Two related LDAP authentication changes.
Fix: POSIX primary group membership was ignored. authenticate_ldap_user
only searched for posixGroup entries via memberUid (supplementary
groups). A user's primary group — referenced by the gidNumber attribute
on the user object matching gidNumber on a posixGroup — was never
resolved, so users whose role came from their primary group landed
without the expected permissions. The authenticator now runs a second
search for posixGroup entries whose gidNumber matches the user's
primary gidNumber, then dedupes DNs case-insensitively before passing
the list to resolve_group_mapping (LDAP DNs are case-insensitive by
spec).
New feature: ldap_default_group setting. Settings → Authentication →
LDAP → Advanced has a new "Default group" selector. When an LDAP user
authenticates but is not listed in any mapped LDAP group, they are
assigned to this fallback group instead of being left with no groups
(and therefore no permissions). A warning is logged each time the
fallback is applied so admins can spot missing group assignments.
Empty setting preserves the old behavior.
Tests: added 4 mocked authenticate_ldap_user tests covering primary
gidNumber lookup, dedupe of overlapping memberUid+primary gid matches,
case-insensitive DN dedupe, and the guard when a user entry has no
gidNumber attribute. Also extended the existing parse_ldap_config tests
to cover the new default_group field.
Backend: ldap_service.py (primary group + dedupe + default_group
field), schemas/settings.py (schema field), api/routes/auth.py
(fallback wiring in _provision_ldap_user / _sync_ldap_user).
Frontend: LDAPSettings.tsx default-group dropdown in the Advanced
collapsible, api/client.ts type field, new i18n keys in all 7 locales
(defaultGroup, defaultGroupNone, defaultGroupHint).
Users can authenticate against an LDAP/AD server with configurable
server URL, bind DN, search base, and user filter. Supports StartTLS
and LDAPS — plaintext is not allowed. Both Active Directory (memberOf)
and POSIX groups (memberUid) are mapped to BamBuddy groups on each
login. Auto-provisioning creates local accounts on first LDAP login.
Local admin accounts remain as fallback when LDAP is unreachable.
Password management is disabled for LDAP users.