Adding LDAP login
This commit is contained in:
1 parent
ab39191af5
commit
cda15df2ed
22 files changed
+582
-15
No files matched your search
@@ -0,0 +1,148 @@
|
||||
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
|
||||
Reference in new issue
Block a user