From 21574d3a57216c65bddcddaac04444b4191bb3eb Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Mon, 27 Apr 2026 18:43:40 +0200 Subject: [PATCH] feat: implement LDAP auth fallback in authenticate_user --- app/modules/auth/service.py | 33 ++++++++++++++++++----- tests/test_auth_service.py | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/app/modules/auth/service.py b/app/modules/auth/service.py index a220ac9..cbd7b63 100644 --- a/app/modules/auth/service.py +++ b/app/modules/auth/service.py @@ -21,22 +21,41 @@ def get_user(db: Session, username: str) -> Optional[User]: return db.query(User).filter(User.username == username).first() +def upsert_ldap_user(db: Session, attrs: dict) -> User: + """Create or update a User from LDAP attribute dict, commit and return.""" + from app.modules.auth.ldap import _upsert_from_attrs + _upsert_from_attrs(db, attrs) + db.commit() + return get_user(db, attrs["username"]) + + def authenticate_user( db: Session, username: str, password: str, ldap_enabled: bool ) -> Optional[User]: user = get_user(db, username) - if user is None or not user.is_active: + + # Explicitly deactivated users are always blocked + if user is not None and not user.is_active: return None - local_ok = user.pw_hash is not None and verify_password(password, user.pw_hash) - if local_ok: + # Local auth + if user is not None and user.pw_hash is not None: + if verify_password(password, user.pw_hash): + _touch_last_login(db, user) + return user + + # LDAP auth + if ldap_enabled: + from app.core.config import get_settings + from app.modules.auth.ldap import ldap_authenticate + s = get_settings() + attrs = ldap_authenticate(username, password, s.LDAP_SERVER, s.LDAP_DOMAIN) + if attrs is None: + return None + user = upsert_ldap_user(db, attrs) _touch_last_login(db, user) return user - if ldap_enabled: - # LDAP auth implemented in Part 2 - pass - return None diff --git a/tests/test_auth_service.py b/tests/test_auth_service.py index 52da537..a5242e2 100644 --- a/tests/test_auth_service.py +++ b/tests/test_auth_service.py @@ -67,3 +67,56 @@ def test_authenticate_no_pw_hash_no_ldap(db): db.add(user) db.commit() assert authenticate_user(db, "ldaponly", "any", ldap_enabled=False) is None + + +# --- LDAP auth integration (uses mocked ldap_authenticate) --- + +from unittest.mock import patch as mock_patch + + +def test_authenticate_creates_user_on_ldap_success(db): + ldap_attrs = { + "username": "newldap", + "full_name": "New LDAP User", + "department": "EFI", + "role": "ST", + "account_expires": None, + } + with mock_patch("app.modules.auth.ldap.ldap_authenticate", return_value=ldap_attrs): + user = authenticate_user(db, "newldap", "password", ldap_enabled=True) + assert user is not None + assert user.username == "newldap" + assert user.full_name == "New LDAP User" + assert user.last_login is not None + + +def test_authenticate_updates_existing_user_on_ldap_success(db): + existing = User(username="existing", full_name="Old Name", pw_hash=None) + db.add(existing) + db.commit() + ldap_attrs = { + "username": "existing", + "full_name": "New Name", + "department": "EFI", + "role": "PF", + "account_expires": None, + } + with mock_patch("app.modules.auth.ldap.ldap_authenticate", return_value=ldap_attrs): + user = authenticate_user(db, "existing", "password", ldap_enabled=True) + assert user.full_name == "New Name" + + +def test_authenticate_returns_none_on_ldap_failure(db): + with mock_patch("app.modules.auth.ldap.ldap_authenticate", return_value=None): + result = authenticate_user(db, "anyone", "wrong", ldap_enabled=True) + assert result is None + + +def test_authenticate_blocked_user_not_bypassed_by_ldap(db): + blocked = User(username="blocked", full_name="B", pw_hash=None, is_active=False) + db.add(blocked) + db.commit() + ldap_attrs = {"username": "blocked", "full_name": "B", "department": "", "role": "", "account_expires": None} + with mock_patch("app.modules.auth.ldap.ldap_authenticate", return_value=ldap_attrs): + result = authenticate_user(db, "blocked", "any", ldap_enabled=True) + assert result is None