Files

119 lines
6.0 KiB
Python
Raw Permalink Normal View History

"""The directory additions and the ad-reconcile job against the CI Samba domain controller (deploy/samba).
Skipped unless the TEST_LDAP_* values are set, like test_ldap_dc.py. The seed leaves PS-14-Electrical-Lead out,
adds PS-Extra-Legacy, nests Shop-Welders-Nested into PS-10-WeldAssembly-Worker, disables disabled.dan, gives
dup.dave and dup.dina the same employeeID and noemp.nora none. Nothing here writes to the directory.
"""
from __future__ import annotations
import uuid
from pathlib import Path
from typing import Any
import pytest
from pshelpers import app_conn, make_settings, needs_pg
from test_ldap_dc import MISSING, ldap_values
from packetstation.auth import directory as userdir
from packetstation.auth import groups, sessions
from packetstation.auth.ldap import AdUser, Directory
from packetstation.settings import Settings
from packetstation.worker import jobs, runs
from packetstation.worker.jobs import ad_reconcile
from packetstation.worker.scheduler import Scheduler
pytestmark = pytest.mark.skipif(bool(MISSING), reason=f"no test domain controller ({', '.join(MISSING)} not set)")
@pytest.fixture
def directory(tmp_path: Path) -> Directory:
return Directory(make_settings(tmp_path, **ldap_values()))
def test_list_groups_detail(directory: Directory) -> None:
detail = directory.list_groups_detail()
names = {n.lower() for n, _ in detail}
assert "ps-14-electrical-lead" not in names and "ps-extra-legacy" in names
assert sum(1 for n in groups.group_names() if n.lower() in names) == 49
base = directory.settings.ldap_group_base_dn.lower()
assert all(dn.lower().endswith(base) and dn.lower().startswith(f"cn={n.lower()},") for n, dn in detail)
def test_members_of_dn_nested_and_disabled(directory: Directory) -> None:
dn = dict(directory.list_groups_detail())
assert {u.sam_account for u in directory.members_of_dn(dn["PS-10-WeldAssembly-Worker"])} == {"nested.ned"}
weld = {u.sam_account: u for u in directory.members_of_dn(dn["PS-03-WeldParts-Worker"])}
assert {"weld.andy", "both.bo", "dup.dave", "disabled.dan"} <= set(weld)
assert weld["disabled.dan"].disabled is True and weld["weld.andy"].disabled is False
assert directory.members_of_dn("CN=PS-No-Such-Group," + directory.settings.ldap_group_base_dn) == []
def test_search_groups_and_users_by_guid(directory: Directory) -> None:
found = directory.search_groups("Welders-Nest")
assert [g["name"] for g in found] == ["Shop-Welders-Nested"]
assert found[0]["description"] and found[0]["dn"].lower().endswith(directory.settings.ldap_base_dn.lower())
assert len(directory.search_groups("PS-", limit=3)) == 3
ann = directory.find_user("admin.ann")
assert ann is not None
ghost = uuid.uuid4()
got = directory.users_by_guid([ann.guid, ghost])
assert got[ghost] is None and got[ann.guid] is not None and got[ann.guid].sam_account == "admin.ann"
def _run(settings: Settings, d: Directory) -> dict[str, Any]:
sch = Scheduler(settings, build="pytest", jobs={"ad-reconcile": ad_reconcile.SPEC})
sch.stack.cache["directory"] = d
return runs.run_job(sch, "ad-reconcile", "schedule")
def _session(settings: Settings, conn: Any, user_id: uuid.UUID) -> uuid.UUID:
_, row = sessions.create(
conn, settings, settings.session_key(), user_id=user_id, kind="password", roles=["clerical"], groups=[]
)
sid: uuid.UUID = row["id"]
return sid
@needs_pg
def test_reconcile_against_dc(pg_settings: Settings, directory: Directory, monkeypatch: pytest.MonkeyPatch) -> None:
raised: list[tuple[str, dict[str, Any]]] = []
monkeypatch.setattr(jobs, "raise_event", lambda conn, t, **kw: raised.append((t, kw)))
monkeypatch.setattr(jobs, "resolve_event", lambda conn, t, **kw: None)
row = _run(pg_settings, directory)
assert row["status"] == "ok", row["errors"]
c = row["counts"]
assert c["groups_missing"] == ["PS-14-Electrical-Lead"] and c["groups_extra"] == ["PS-Extra-Legacy"]
assert c["groups_checked"] == 49 and c["users_seen"] == 16
assert "noemp.nora" in c["emp_missing"] and c["emp_duplicate"] == ["9001"]
assert ("group_missing", "PS-14-Electrical-Lead") in [(t, kw["subject"]) for t, kw in raised]
assert any(t == "ad_reconcile_report" for t, _ in raised)
dan = directory.find_user("disabled.dan")
nick = directory.find_user("norole.nick")
ned = directory.find_user("nested.ned")
assert dan is not None and nick is not None and ned is not None
ghost = AdUser(uuid.uuid4(), "CN=ghost,DC=pstest,DC=local", "ghost.gary", None, "Gary Ghost", "9999", None, False)
with app_conn(pg_settings) as conn:
flags = dict(conn.execute("SELECT sam_account, directory_flags FROM users").fetchall())
assert flags["dup.dave"]["emp_duplicate"] and flags["dup.dina"]["emp_duplicate"]
assert flags["noemp.nora"]["emp_missing"] and flags["disabled.dan"]["disabled"]
assert userdir.roles_of(conn, ned.guid) == ["shop:10:worker"]
assert conn.execute("SELECT active FROM users WHERE id = %s", (dan.guid,)).fetchone()[0] is False
# A disabled account that kept a session, an account in no group, an account AD no longer has
userdir.upsert_user(conn, nick, groups=[])
userdir.upsert_user(conn, ghost, groups=[])
sid = {u: _session(pg_settings, conn, u) for u in (dan.guid, nick.guid, ghost.guid)}
row = _run(pg_settings, directory)
assert row["status"] == "ok", row["errors"]
assert row["counts"]["sessions_ended"] == 3 and row["counts"]["deactivated"] == 1 # the ghost
with app_conn(pg_settings) as conn:
why = dict(conn.execute("SELECT id, revoked_reason FROM sessions").fetchall())
assert why[sid[dan.guid]] == "ad_disabled" and why[sid[nick.guid]] == "roles_lost"
assert why[sid[ghost.guid]] == "ad_gone"
gary = conn.execute("SELECT active, directory_flags FROM users WHERE id = %s", (ghost.guid,)).fetchone()
assert gary[0] is False and gary[1]["gone"] is True
assert conn.execute("SELECT active FROM users WHERE id = %s", (nick.guid,)).fetchone()[0] is True