Files
TheWitness bb92c46e78 feat(auth): automatic User Group assignment from Login Provider groups (#8129)
* feat(auth): automatic User Group assignment from Login Provider groups

Add per-Login-Provider "Auto Assignment" rules to the User Group editor so a
user is added to (or removed from) a Cacti User Group at every login based on
the groups their Login Provider reports.

User Group editor (user_group_admin.php):
- Add General / Auto Assignment / Rights spacers to the General tab and rename
  "General Group Options" to "Group Options".
- Render one text field per eligible Login Provider under Auto Assignment
  (label = provider name). The admin enters a plain Group Name (e.g.
  "Cacti Admins"), never a DN.
- Store the rules as JSON in the new user_auth_group.auto_assignments column,
  keyed by login_providers.id.

Group resolution:
- SAML2 and OpenID match the configured name against the group attribute /
  group claim values.
- LDAP/AD resolve membership live against the directory using a raw group name
  plus the Search Base, trying AD (memberOf + nested LDAP_MATCHING_RULE_IN_CHAIN),
  FreeIPA / 389 DS (groupOfNames/groupOfUniqueNames) and RFC2307 posixGroup
  shapes so one plain name works everywhere. This requires a directory-read
  bind, so the field is only offered for Anonymous Searching or Specific
  Searching with a stored service account.
- A directory that cannot be reached returns "undetermined" so existing
  memberships are left untouched rather than revoked on a transient outage.

Lifecycle:
- auto_assignments is reconciled on every login (lib/auth.php credential flow
  and login_sso.php redirect flow).
- Deleting a Login Provider strips its key from every group's auto_assignments.

Schema: add user_auth_group.auto_assignments (cacti.sql + 1_3_0 upgrade).

* ci: document free-text group-name validateInput for constraint ratchet

* fix(auth): address PR review on automatic User Group assignment

- LDAP: add an Active Directory bare-name query (objectClass=group with the
  member:1.2.840.113556.1.4.1941 nested-membership rule) so a plain group
  name resolves on AD, which neither the posixGroup nor groupOfNames probe
  could match.
- LDAP: make single-group membership tri-state and propagate an "undetermined"
  result, so a search that fails after binding (timeout/dropped connection) no
  longer looks like confirmed non-membership and revokes assignments.
- SAML2/OpenID: supportsAutoAssignment() now requires a configured group
  claim, and applyAutoAssignments() skips any provider that is not currently
  capable, so a blank claim can never strip users from their groups.
- Only reset the permission cache when a membership row actually changed
  (accumulate db_affected_rows) rather than on every login that has a rule.
- Add unit tests for the reconciliation boundaries (add / remove / no-op /
  incapable / undetermined) and for the LDAP group-query construction
  (AD plain + nested, posixGroup, groupOfNames, DN mode).
- Regenerate locales/po/cacti.pot for the new UI strings.

* fix(phpstan): resolve level-8 errors from auto group assignment

- Guard supportsAutoAssignment() behind an instanceof AbstractLoginProvider
  check so it is not called on the base LoginProviderInterface.
- Assign the injected field list to a variable (inject_form_variables takes it
  by reference) and pass an is_array-guarded group row.
- Bump the lib/ldap.php ldap_get_entries baseline count 3->6 for the three new
  group-membership query helpers.
2026-10-01 19:48:31 -04:00
..
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00
2026-01-28 16:11:10 -05:00