Initial commit: add compliance_checks table, per-check metadata on assets, and compliance audit trail
This commit is contained in:
84
app/services/ldap_service.py
Normal file
84
app/services/ldap_service.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from ldap3 import Server, Connection, ALL, SUBTREE
|
||||
from flask import current_app
|
||||
|
||||
|
||||
class LDAPService:
|
||||
"""Wraps ldap3 to sync users from Active Directory."""
|
||||
|
||||
ATTRIBUTES = [
|
||||
'employeeID', 'sAMAccountName', 'givenName', 'sn',
|
||||
'mail', 'department', 'title', 'telephoneNumber',
|
||||
'distinguishedName', 'physicalDeliveryOfficeName',
|
||||
'userAccountControl',
|
||||
]
|
||||
|
||||
def _connect(self):
|
||||
cfg = current_app.config
|
||||
server = Server(
|
||||
cfg['LDAP_SERVER'],
|
||||
port=cfg['LDAP_PORT'],
|
||||
use_ssl=cfg['LDAP_USE_SSL'],
|
||||
get_info=ALL,
|
||||
)
|
||||
conn = Connection(
|
||||
server,
|
||||
user=cfg['LDAP_BIND_USER'],
|
||||
password=cfg['LDAP_BIND_PASSWORD'],
|
||||
auto_bind=True,
|
||||
)
|
||||
return conn
|
||||
|
||||
def sync_users(self):
|
||||
"""
|
||||
Query AD and return a list of dicts ready to be upserted into the
|
||||
User model. Raises an exception if the connection fails.
|
||||
"""
|
||||
cfg = current_app.config
|
||||
conn = self._connect()
|
||||
|
||||
conn.search(
|
||||
search_base=cfg['LDAP_BASE_DN'],
|
||||
search_filter=cfg['LDAP_USER_SEARCH_FILTER'],
|
||||
search_scope=SUBTREE,
|
||||
attributes=self.ATTRIBUTES,
|
||||
)
|
||||
|
||||
wid_attr = cfg['LDAP_WINDOWS_ID_ATTR']
|
||||
users = []
|
||||
for entry in conn.entries:
|
||||
# Resolve windows_id from the configured attribute, fall back to sAMAccountName
|
||||
raw_wid = str(getattr(entry, wid_attr, '') or '')
|
||||
if not raw_wid:
|
||||
raw_wid = str(entry.sAMAccountName or '')
|
||||
if not raw_wid:
|
||||
continue # skip entries with no identifier
|
||||
|
||||
# userAccountControl bit 2 = disabled account
|
||||
uac = 0
|
||||
try:
|
||||
uac = int(str(entry.userAccountControl or 0))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
is_active = not bool(uac & 2)
|
||||
|
||||
users.append({
|
||||
'windows_id': raw_wid.strip(),
|
||||
'first_name': str(entry.givenName or '').strip(),
|
||||
'last_name': str(entry.sn or '').strip(),
|
||||
'email': str(entry.mail or '').strip(),
|
||||
'department': str(entry.department or '').strip(),
|
||||
'job_title': str(entry.title or '').strip(),
|
||||
'phone': str(entry.telephoneNumber or '').strip(),
|
||||
'location': str(entry.physicalDeliveryOfficeName or '').strip(),
|
||||
'ldap_dn': str(entry.distinguishedName or '').strip(),
|
||||
'is_active': is_active,
|
||||
})
|
||||
|
||||
conn.unbind()
|
||||
return users
|
||||
|
||||
def test_connection(self):
|
||||
"""Returns True if a bind succeeds, raises on failure."""
|
||||
conn = self._connect()
|
||||
conn.unbind()
|
||||
return True
|
||||
Reference in New Issue
Block a user