"""Integration: provider accounts are found by (source, subject) against real Postgres, never merged into a local account by a matching name.""" from __future__ import annotations import asyncio import os import uuid import pytest pytestmark = pytest.mark.integration @pytest.fixture def app(): if not os.environ.get("STEWARD_DATABASE_URL"): pytest.skip("needs Postgres") from steward.app import create_app return create_app(testing=False) def test_provider_accounts_keep_their_source(app): from sqlalchemy import select, update from steward.auth.routes import _provision_external_user from steward.models.users import User, UserRole async def _go(): tag = uuid.uuid4().hex[:8] local = f"loc-{tag}" async with app.db_sessionmaker() as s: async with s.begin(): s.add(User(username=local, email=f"{local}@test", password_hash="x", role=UserRole.admin)) # A directory entry with a local user's name is refused, not merged. user, refusal = await _provision_external_user( app, "ldap", f"uid={local},dc=x", local, "", "admin") assert user is None and f"named {local}" in refusal and "LDAP" in refusal # First sign-in creates the account; the next finds it by subject and # follows the provider's role. name = f"sso-{tag}" first, refusal = await _provision_external_user(app, "oidc", f"sub-{tag}", name, "", "viewer") assert refusal is None assert (first.auth_source, first.external_id, first.role) == ("oidc", f"sub-{tag}", UserRole.viewer) again, _ = await _provision_external_user(app, "oidc", f"sub-{tag}", name, "", "operator") assert again.id == first.id and again.role == UserRole.operator # Another subject claiming the same username is someone else. other, refusal = await _provision_external_user(app, "oidc", f"sub2-{tag}", name, "", "admin") assert other is None and refusal # Disabled in Steward stays out, whatever the provider says. async with app.db_sessionmaker() as s: async with s.begin(): await s.execute(update(User).where(User.id == first.id).values(is_active=False)) gone, refusal = await _provision_external_user(app, "oidc", f"sub-{tag}", name, "", "admin") assert gone is None and "disabled" in refusal async with app.db_sessionmaker() as s: row = (await s.execute(select(User).where(User.username == local))).scalar_one() assert (row.auth_source, row.external_id, row.role) == ("local", None, UserRole.admin) asyncio.run(_go())