125 lines
4.7 KiB
Python
125 lines
4.7 KiB
Python
"""'/' root redirect and the '/login' landing page."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from uuid import uuid4
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from ragcore.application.auth import SessionService, SignedTokenCodec
|
|
from ragcore.application.auth.ports import AuthorizationChallenge
|
|
from ragcore.domain.auth import OidcClaims
|
|
from ragcore.domain.control import Role
|
|
from ragcore.main import create_app
|
|
from ragcore.web.auth.dependencies import get_session_codec, get_session_service
|
|
|
|
SECRET = b"0" * 32
|
|
|
|
|
|
class FakeOidcProvider:
|
|
def __init__(self, claims: OidcClaims) -> None:
|
|
self.claims = claims
|
|
|
|
async def build_authorization_challenge(self, *, redirect_uri: str) -> AuthorizationChallenge:
|
|
return AuthorizationChallenge(
|
|
redirect_url="https://idp.example.test/authorize?mock=1",
|
|
state="fixed-state-for-tests",
|
|
nonce="fixed-nonce-for-tests",
|
|
code_verifier="fixed-verifier-for-tests",
|
|
)
|
|
|
|
async def complete_login(
|
|
self, *, code: str, code_verifier: str, nonce: str, redirect_uri: str
|
|
) -> OidcClaims:
|
|
return self.claims
|
|
|
|
|
|
def _signed_in_client() -> TestClient:
|
|
provider = FakeOidcProvider(
|
|
OidcClaims(subject="alice", email="alice@example.test", groups=("ragcore-editors",))
|
|
)
|
|
service = SessionService(
|
|
provider=provider, group_roles={"ragcore-editors": Role.EDITOR}, workspace_id=uuid4()
|
|
)
|
|
codec = SignedTokenCodec(secret=SECRET)
|
|
app = create_app()
|
|
app.dependency_overrides[get_session_service] = lambda: service
|
|
app.dependency_overrides[get_session_codec] = lambda: codec
|
|
# SessionAuthenticationMiddleware reads the codec straight off app.state
|
|
# (it runs outside FastAPI's dependency-injection system), so the
|
|
# dependency override above -- which only affects the /auth/* routes --
|
|
# is not enough to make it recognize the cookie those routes just set.
|
|
app.state.auth_session_codec = codec
|
|
client = TestClient(app, follow_redirects=False)
|
|
client.get("/auth/login")
|
|
client.get("/auth/callback", params={"code": "auth-code", "state": "fixed-state-for-tests"})
|
|
return client
|
|
|
|
|
|
def test_root_redirects_unauthenticated_visitors_to_login() -> None:
|
|
client = TestClient(create_app(), follow_redirects=False)
|
|
response = client.get("/")
|
|
assert response.status_code == 302
|
|
assert response.headers["location"] == "/login"
|
|
|
|
|
|
def test_root_redirects_authenticated_visitors_to_dashboard() -> None:
|
|
client = _signed_in_client()
|
|
response = client.get("/")
|
|
assert response.status_code == 302
|
|
assert response.headers["location"] == "/admin/dashboard"
|
|
|
|
|
|
def test_login_page_shows_not_configured_when_oidc_is_absent() -> None:
|
|
client = TestClient(create_app(), follow_redirects=False)
|
|
response = client.get("/login")
|
|
assert response.status_code == 200
|
|
assert "No identity provider is configured" in response.text
|
|
assert "Sign in with SSO" not in response.text
|
|
assert "OIDC secured" not in response.text
|
|
assert "Setup required" in response.text
|
|
|
|
|
|
def test_login_page_offers_sso_when_oidc_is_configured() -> None:
|
|
app = create_app()
|
|
app.state.session_service = object()
|
|
client = TestClient(app, follow_redirects=False)
|
|
response = client.get("/login")
|
|
assert response.status_code == 200
|
|
assert "Sign in with SSO" in response.text
|
|
assert 'href="/auth/login"' in response.text
|
|
|
|
|
|
def test_login_page_introduces_the_real_ragcore_value_flow() -> None:
|
|
app = create_app()
|
|
app.state.session_service = object()
|
|
response = TestClient(app, follow_redirects=False).get("/login")
|
|
|
|
assert response.status_code == 200
|
|
assert 'data-auth-stage="ragcore"' in response.text
|
|
assert "Private knowledge. Verifiable answers." in response.text
|
|
assert 'data-rag-flow="sources-retrieval-answers"' in response.text
|
|
assert "Dense + sparse" in response.text
|
|
assert "Cited answers" in response.text
|
|
assert 'aria-describedby="sign-in-description"' in response.text
|
|
assert "https://" not in response.text
|
|
|
|
|
|
def test_login_page_declares_a_loadable_local_favicon() -> None:
|
|
client = TestClient(create_app(), follow_redirects=False)
|
|
|
|
response = client.get("/login")
|
|
favicon = client.get("/static/favicon.svg")
|
|
|
|
assert response.status_code == 200
|
|
assert '<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">' in response.text
|
|
assert favicon.status_code == 200
|
|
assert favicon.headers["content-type"].startswith("image/svg+xml")
|
|
|
|
|
|
def test_login_page_redirects_an_already_authenticated_visitor() -> None:
|
|
client = _signed_in_client()
|
|
response = client.get("/login")
|
|
assert response.status_code == 302
|
|
assert response.headers["location"] == "/admin/dashboard"
|