Active Directory (AD) is Microsoft's LDAP implementation with proprietary extensions. It adds attributes like sAMAccountName, userPrincipalName, memberOf, and userAccountControl. Domain join, Group Policy, and Kerberos are AD-specific.
| Item | Syntax / Value | Description |
|---|---|---|
| sAMAccountName | sAMAccountName: alice | Windows/AD login name (pre-Windows 2000) |
| userPrincipalName | userPrincipalName: alice@example.com | UPN — modern AD login: user@domain |
| distinguishedName | distinguishedName: CN=Alice Smith,OU=Users,DC=example,DC=com | AD-style DN uses CN= not uid= |
| CN= | CN=Alice Smith,OU=Users,DC=example,DC=com | AD uses CN for user RDN, not uid |
| OU= | OU=Engineering,DC=example,DC=com | Organizational Unit in AD |
| DC= | DC=example,DC=com | Domain Component in AD |
| memberOf | memberOf: CN=Developers,OU=Groups,DC=example,DC=com | Groups user belongs to (AD attribute) |
| userAccountControl | userAccountControl: 512 | Bitmask: 512=enabled, 514=disabled, 66048=enabled+no_expire |
| accountExpires | accountExpires: 0 / 132xxx | AD account expiration timestamp |
| pwdLastSet | pwdLastSet: 0 | 0=must change at next logon, -1=never expires |
| lastLogon | lastLogon: 132xxx | Last logon timestamp (not replicated between DCs) |
| lastLogonTimestamp | lastLogonTimestamp: 132xxx | Last logon (replicated — use this for reports) |
| objectCategory | objectCategory: CN=Person,CN=Schema,... | AD object category (more specific than objectClass) |
| objectSID | objectSID: S-1-5-21-xxx | Security Identifier — unique across domain |
| primaryGroupID | primaryGroupID: 513 | RID of primary group (Domain Users = 513) |
| AD bind | DOMAIN\\username or username@domain.com | AD bind formats |
| Global Catalog | port 3268 (ldap) / 3269 (ldaps) | AD Global Catalog — search across all domains |
| AD filter users | (&(objectCategory=person)(objectClass=user)) | Find all user accounts in AD |
| AD enabled users | (&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) | Active (non-disabled) users |
| AD group members | (memberOf=CN=groupname,OU=Groups,DC=domain,DC=com) | Direct group membership |
| AD nested groups | (memberOf:1.2.840.113556.1.4.1941:=CN=group,...) | Recursive/nested group membership |
| LDAP_MATCHING_RULE_IN_CHAIN | 1.2.840.113556.1.4.1941 | OID for recursive memberOf search |
| Kerberos bind | ldap3 with SASL GSSAPI | Use Kerberos ticket for authentication |
# Active Directory LDAP queries
# pip install ldap3
from ldap3 import Server, Connection, SUBTREE, ALL_ATTRIBUTES
# AD connection
AD_HOST = "dc01.example.com"
AD_DOMAIN = "EXAMPLE"
AD_USER = "binduser@example.com" # or EXAMPLE\binduser
AD_PASS = "password"
AD_BASE = "DC=example,DC=com"
server = Server(AD_HOST, port=636, use_ssl=True, get_info="ALL")
conn = Connection(server, user=AD_USER, password=AD_PASS, auto_bind=True)
# ── 1. Find all enabled users ─────────────────────────────────
conn.search(
AD_BASE,
# enabled users only (bit 2 of userAccountControl NOT set)
"(&(objectCategory=person)(objectClass=user)"
"(!(userAccountControl:1.2.840.113556.1.4.803:=2)))",
search_scope = SUBTREE,
attributes = ["sAMAccountName","displayName","mail","department",
"title","lastLogonTimestamp","memberOf"]
)
print(f"Found {len(conn.entries)} enabled users")
for user in conn.entries[:5]:
print(f" {str(user.sAMAccountName):15} {str(user.displayName):25} {str(user.mail)}")
# ── 2. Find user by email ─────────────────────────────────────
conn.search(AD_BASE,
f"(&(objectClass=user)(mail=alice@example.com))",
attributes=["sAMAccountName","displayName","memberOf","department"])
if conn.entries:
alice = conn.entries[0]
print(f"\nUser: {alice.displayName.value}")
print(f"Department: {alice.department.value}")
print("Groups:")
for group_dn in alice.memberOf.values:
# Extract CN from group DN
cn = group_dn.split(',')[0].replace('CN=','')
print(f" - {cn}")
# ── 3. Get all members of a group ────────────────────────────
group_dn = "CN=Developers,OU=Groups,DC=example,DC=com"
conn.search(AD_BASE,
f"(&(objectClass=user)(memberOf={group_dn}))",
attributes=["sAMAccountName","displayName","mail"])
print(f"\nDevelopers group members ({len(conn.entries)}):")
for m in conn.entries:
print(f" {str(m.sAMAccountName):15} {str(m.displayName)}")
# ── 4. Authenticate user (AD login) ───────────────────────────
def ad_authenticate(username, password, domain="example.com"):
try:
test_server = Server(AD_HOST, port=636, use_ssl=True)
test_conn = Connection(test_server,
user=f"{username}@{domain}",
password=password,
auto_bind=True)
test_conn.unbind()
return True, "Authentication successful"
except Exception as e:
return False, str(e)
ok, msg = ad_authenticate("alice", "correct_password")
print(f"\nAuth result: {ok} — {msg}")
# ── 5. Get users who haven't logged in 90 days ───────────────
import datetime
cutoff = datetime.datetime.now() - datetime.timedelta(days=90)
# AD timestamp = 100-nanosecond intervals since Jan 1, 1601
ad_timestamp = int((cutoff - datetime.datetime(1601, 1, 1)).total_seconds() * 10**7)
conn.search(AD_BASE,
f"(&(objectClass=user)(lastLogonTimestamp<={ad_timestamp}))",
attributes=["sAMAccountName","lastLogonTimestamp"])
print(f"\nUsers not logged in 90+ days: {len(conn.entries)}")
conn.unbind()
# python3 ad_demo.py (requires AD/LDAP server access)