Publish curated Unraid-AppOps-Gateway source from 451646fee806a47e3256731766980a05a262dcdd
This commit is contained in:
1 parent
532f3d9f26
commit
0a82153d95
43 files changed
+17722
-93
No files matched your search
@@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlmodel import Session, SQLModel, create_engine
|
||||
|
||||
from backend.app.services import user_access
|
||||
|
||||
_REAL_CURRENT_USER = user_access.current_user
|
||||
_PROVISION_PATH = "/api/apps/authentik/forge/controller-reader/provision"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def forge_reader_operator_client(tmp_path, monkeypatch: pytest.MonkeyPatch):
|
||||
from backend.app.core.config import settings
|
||||
from backend.app.db import session as db_session
|
||||
from backend.app.db.init_db import init_db
|
||||
from backend.app.main import app
|
||||
|
||||
if os.name == "nt":
|
||||
from backend.app.services import forge_oidc_provisioning
|
||||
|
||||
monkeypatch.setattr(
|
||||
forge_oidc_provisioning,
|
||||
"_windows_private_path",
|
||||
lambda *_args, **_kwargs: None,
|
||||
)
|
||||
else:
|
||||
from backend.app.services import forge_oidc_provisioning
|
||||
|
||||
monkeypatch.setattr(
|
||||
forge_oidc_provisioning,
|
||||
"_FORGE_OIDC_LOCK_ROOT",
|
||||
tmp_path / "forge-reader-api-lock",
|
||||
)
|
||||
|
||||
engine = create_engine(
|
||||
f"sqlite:///{tmp_path / 'forge-reader-operator.db'}",
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
monkeypatch.setattr(db_session, "engine", engine)
|
||||
monkeypatch.setenv("APPOPS_REQUIRE_OPERATOR_AUTH", "false")
|
||||
monkeypatch.setattr(settings, "operator_proxy_auth_enabled", False)
|
||||
monkeypatch.setattr(settings, "auth_admin_subjects", ["authentik:owner"], raising=False)
|
||||
monkeypatch.setattr(settings, "auth_admin_emails", [], raising=False)
|
||||
monkeypatch.setattr(settings, "data_dir", str(tmp_path))
|
||||
monkeypatch.setattr(user_access, "current_user", _REAL_CURRENT_USER)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
init_db()
|
||||
client = TestClient(app, client=("198.18.10.20", 42000))
|
||||
yield client
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def _spy_actual_connector_loader(monkeypatch: pytest.MonkeyPatch):
|
||||
from backend.app.services import setup_service
|
||||
|
||||
original = setup_service.apply_stored_connector_settings
|
||||
calls = []
|
||||
|
||||
def tracked(session, connector_ids=None):
|
||||
calls.append(connector_ids)
|
||||
return original(session, connector_ids)
|
||||
|
||||
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked)
|
||||
return calls
|
||||
|
||||
|
||||
def _sign_in(client: TestClient, subject: str) -> None:
|
||||
from backend.app.db import session as db_session
|
||||
from backend.app.services.user_access import issue_session, provision_user
|
||||
|
||||
with Session(db_session.engine) as session:
|
||||
user = provision_user(
|
||||
session,
|
||||
"authentik",
|
||||
"https://auth.example/application/o/appops/",
|
||||
{"sub": subject, "email": f"{subject}@example.com"},
|
||||
)
|
||||
cookie = issue_session(session, user)
|
||||
client.cookies.set("appops_user_session", cookie)
|
||||
|
||||
|
||||
def test_forge_reader_route_requires_authenticated_operator_and_csrf(
|
||||
forge_reader_operator_client: TestClient,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
loader_calls = _spy_actual_connector_loader(monkeypatch)
|
||||
unauthenticated = forge_reader_operator_client.post(_PROVISION_PATH, json={})
|
||||
assert unauthenticated.status_code == 401
|
||||
|
||||
_sign_in(forge_reader_operator_client, "owner")
|
||||
missing_csrf = forge_reader_operator_client.post(_PROVISION_PATH, json={})
|
||||
assert missing_csrf.status_code == 403
|
||||
assert loader_calls == []
|
||||
|
||||
|
||||
def test_forge_reader_route_rejects_read_only_user_and_caller_selectors(
|
||||
forge_reader_operator_client: TestClient,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
loader_calls = _spy_actual_connector_loader(monkeypatch)
|
||||
_sign_in(forge_reader_operator_client, "reader")
|
||||
denied = forge_reader_operator_client.post(
|
||||
_PROVISION_PATH,
|
||||
json={},
|
||||
headers={"X-AppOps-Session-CSRF": "1"},
|
||||
)
|
||||
assert denied.status_code == 403
|
||||
|
||||
_sign_in(forge_reader_operator_client, "owner")
|
||||
invalid = forge_reader_operator_client.post(
|
||||
_PROVISION_PATH,
|
||||
json={"token": "caller-selected"},
|
||||
headers={"X-AppOps-Session-CSRF": "1"},
|
||||
)
|
||||
assert invalid.status_code == 422
|
||||
assert loader_calls == []
|
||||
|
||||
|
||||
def test_forge_reader_default_denied_policy_does_not_load_connector_or_mutate(
|
||||
forge_reader_operator_client: TestClient,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
from backend.app.db import session as db_session
|
||||
from backend.app.db.models import Policy
|
||||
|
||||
loader_calls = _spy_actual_connector_loader(monkeypatch)
|
||||
_sign_in(forge_reader_operator_client, "owner")
|
||||
with Session(db_session.engine) as session:
|
||||
policy = session.get(Policy, "policy-forge-oidc-provision")
|
||||
policy.manual = "blocked"
|
||||
session.add(policy)
|
||||
session.commit()
|
||||
|
||||
response = forge_reader_operator_client.post(
|
||||
_PROVISION_PATH,
|
||||
json={},
|
||||
headers={"X-AppOps-Session-CSRF": "1"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "blocked"
|
||||
assert response.json()["message"] == "forge_reader_exact_target_policy_required"
|
||||
assert loader_calls == []
|
||||
|
||||
|
||||
def test_forge_reader_route_passes_operator_and_projects_only_safe_result(
|
||||
forge_reader_operator_client: TestClient,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
from backend.app.api import routes_authentik
|
||||
|
||||
_sign_in(forge_reader_operator_client, "owner")
|
||||
loader_calls = _spy_actual_connector_loader(monkeypatch)
|
||||
calls = []
|
||||
held_token = "reader-token-must-never-be-returned"
|
||||
|
||||
def provision(session, *, actor_identity):
|
||||
calls.append((session, actor_identity))
|
||||
return {
|
||||
"success": True,
|
||||
"status": "ready",
|
||||
"message": "forge_reader_ready",
|
||||
"ready": True,
|
||||
"secretRef": "ITWORX_FORGE_AUTHENTIK_READ_TOKEN",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(routes_authentik, "provision_forge_controller_reader", provision)
|
||||
response = forge_reader_operator_client.post(
|
||||
_PROVISION_PATH,
|
||||
json={},
|
||||
headers={"X-AppOps-Session-CSRF": "1"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"success": True,
|
||||
"status": "ready",
|
||||
"message": "forge_reader_ready",
|
||||
"ready": True,
|
||||
"secretRef": "ITWORX_FORGE_AUTHENTIK_READ_TOKEN",
|
||||
}
|
||||
assert calls and calls[0][1] == "authentik:owner"
|
||||
assert loader_calls == []
|
||||
assert held_token not in response.text
|
||||
Reference in new issue
Block a user