Skip to content

Enumerate AD group membership

This operation is the library's local LDAP traversal workflow and the data source that machine authorization ultimately depends on. It resolves the target AD group, optionally walks nested subgroups, accumulates distinct user accounts while guarding against cycles and duplicates, and returns the sorted sAMAccountName list that authorization logic compares against.

Field Value
Operation ID OP-ENV-008
Trigger Caller requests the users contained in an AD group
Inputs groupName, ldap, domain, recurse
Outputs Distinct sorted string[] of sAMAccountName values, or null
Primary components ADService.GetUsersByGroup, ADService.GetMembership, ADService.GetMembershipRecursively
Entry point SAIF.Framework.Security.EnvironmentApi\Core\Services\ADService.cs

Steps

  1. Open a PrincipalContext for the requested domain and resolve the target group with GroupPrincipal.FindByIdentity.
  2. If the group cannot be found, return null without throwing, so a caller can distinguish "group does not exist" from "group has no members."
  3. Open a DirectoryEntry rooted at the supplied LDAP path and begin traversal from the group's distinguished name.
  4. Query direct membership with a DirectorySearcher filter of (&(objectClass=<type>)(memberOf=<groupDn>)), loading objectGUID, sAMAccountName, and distinguishedName, and sorting the results by sAMAccountName.
  5. When recurse is true, visit subgroup results first, skipping any subgroup name that has already been traversed so nested membership cycles cannot cause infinite or duplicated work.
  6. Visit user results, skipping any user name that has already been yielded so the same account does not appear twice when it belongs to multiple groups in the traversal path.
  7. Project the collected results to sAMAccountName, apply Distinct() and OrderBy(), and return the final array that authorization checks consume (BR-006).

Process flow

The following diagram visualizes this operation's control flow.

flowchart TD
    Start([Caller requests AD group members]) --> Group[Resolve group identity]
    Group --> Exists{Group found?}
    Exists -->|No| ReturnNull([Return null])
    Exists -->|Yes| Root[Open LDAP root]
    Root --> Nested{Recurse into subgroups?}
    Nested -->|Yes| Walk[Walk unseen subgroups]
    Nested -->|No| Users[Read direct users]
    Walk --> Users
    Users --> Distinct[Deduplicate and sort sAMAccountName values]
    Distinct --> End([Return member array])

Failure and edge-case behavior

  • A missing group returns null rather than an empty array, so callers can tell "no such group" apart from "group exists but is empty."
  • Recursion tracks seen subgroup and user names in separate sets to prevent both infinite cycles and duplicate output.
  • The method does not catch LDAP or directory-service exceptions; those propagate to the caller unhandled.

Sources

  • SAIF.Framework.Security.EnvironmentApi\Core\Services\ADService.cs