import pytest from app.core.config import settings from app.crud import role as role_crud from app.crud import user as user_crud from app.services import ldap as ldap_service @pytest.fixture def ldap_enabled(monkeypatch): monkeypatch.setattr(settings, "ldap_enabled", True) def _fake_identity(username="jdoe", full_name="Jane Doe"): return ldap_service.LdapIdentity( username=username, email=f"{username}@example.com", full_name=full_name, ) async def test_ldap_login_provisions_user_with_default_role( client, seeded, db_sessionmaker, monkeypatch, ldap_enabled ): # An admin has marked the "user" role as default. async with db_sessionmaker() as db: role = await role_crud.get(db, seeded["roles"]["user"].id) await role_crud.set_default(db, role) async def fake_authenticate(username, password): assert (username, password) == ("jdoe", "secret") return _fake_identity() monkeypatch.setattr(ldap_service, "authenticate", fake_authenticate) resp = await client.post( "/api/auth/ldap-login", data={"username": "jdoe", "password": "secret"}, ) assert resp.status_code == 200, resp.text access = resp.json()["access_token"] # A local profile was provisioned from the username with the default role. async with db_sessionmaker() as db: user = await user_crud.get_by_username(db, "jdoe") assert user is not None assert user.email == "jdoe@example.com" assert user.full_name == "Jane Doe" assert user.auth_source == "ldap" assert user.hashed_password is None assert {r.name for r in user.roles} == {"user"} # The issued token authenticates. me = await client.get( "/api/users/me", headers={"Authorization": f"Bearer {access}"} ) assert me.status_code == 200 assert me.json()["username"] == "jdoe" async def test_ldap_login_is_idempotent( client, seeded, db_sessionmaker, monkeypatch, ldap_enabled ): async with db_sessionmaker() as db: role = await role_crud.get(db, seeded["roles"]["user"].id) await role_crud.set_default(db, role) async def fake_authenticate(username, password): return _fake_identity() monkeypatch.setattr(ldap_service, "authenticate", fake_authenticate) for _ in range(2): resp = await client.post( "/api/auth/ldap-login", data={"username": "jdoe", "password": "secret"} ) assert resp.status_code == 200 async with db_sessionmaker() as db: assert await user_crud.count(db) == 1 async def test_ldap_login_bad_credentials( client, seeded, monkeypatch, ldap_enabled ): async def fake_authenticate(username, password): return None monkeypatch.setattr(ldap_service, "authenticate", fake_authenticate) resp = await client.post( "/api/auth/ldap-login", data={"username": "jdoe", "password": "nope"} ) assert resp.status_code == 401 async def test_ldap_login_disabled_returns_404(client, seeded): # ldap_enabled defaults to False. resp = await client.post( "/api/auth/ldap-login", data={"username": "jdoe", "password": "secret"} ) assert resp.status_code == 404 async def test_ldap_authenticate_short_circuits_when_disabled(): # No network call: disabled or empty credentials return None immediately. assert await ldap_service.authenticate("jdoe", "secret") is None async def test_set_default_role_is_exclusive(db_sessionmaker): async with db_sessionmaker() as db: a = await role_crud.create(db, name="alpha") b = await role_crud.create(db, name="beta") await role_crud.set_default(db, a) assert (await role_crud.get_default(db)).id == a.id # Marking another default clears the previous one. await role_crud.set_default(db, b) default = await role_crud.get_default(db) assert default.id == b.id await db.refresh(a) assert a.is_default is False async def test_local_login_rejects_ldap_user( client, seeded, db_sessionmaker, monkeypatch, ldap_enabled ): """An LDAP-provisioned user (no local password) can't use the local login.""" async with db_sessionmaker() as db: role = await role_crud.get(db, seeded["roles"]["user"].id) await role_crud.set_default(db, role) async def fake_authenticate(username, password): return _fake_identity() monkeypatch.setattr(ldap_service, "authenticate", fake_authenticate) await client.post( "/api/auth/ldap-login", data={"username": "jdoe", "password": "secret"} ) # Local login with the synthesized email must fail (no local password). resp = await client.post( "/api/auth/login", data={"username": "jdoe@example.com", "password": "secret"}, ) assert resp.status_code == 401