feat(access): derive authenticated baseline roles

This commit is contained in:
2026-07-21 20:48:21 +02:00
parent 21f77ffc50
commit 4075ac4d73
4 changed files with 218 additions and 7 deletions

View File

@@ -0,0 +1,137 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from govoplan_access.backend.admin.service import ensure_default_roles, set_user_roles
from govoplan_access.backend.db.models import Account, Role, User, UserRoleAssignment
from govoplan_access.backend.security.sessions import collect_user_authorization_context
from govoplan_core.core.modules import RoleTemplate
from govoplan_core.db.base import Base
from govoplan_core.tenancy.scope import Tenant, create_scope_tables, scope_registry
DOCS_READER = RoleTemplate(
slug="docs_reader",
name="Documentation reader",
description="Authenticated documentation baseline.",
permissions=("docs:documentation:read",),
default_authenticated=True,
)
class DefaultAuthenticatedRoleTests(unittest.TestCase):
def setUp(self) -> None:
self.engine = create_engine("sqlite:///:memory:")
create_scope_tables(self.engine)
Base.metadata.create_all(bind=self.engine)
self.Session = sessionmaker(bind=self.engine)
self.session = self.Session()
self.tenant = Tenant(id="tenant-1", slug="tenant-1", name="Tenant 1")
self.session.add(self.tenant)
self.session.flush()
def tearDown(self) -> None:
self.session.close()
Base.metadata.drop_all(bind=self.engine)
scope_registry.metadata.drop_all(bind=self.engine)
self.engine.dispose()
def _add_user(self, suffix: str) -> User:
account = Account(
id=f"account-{suffix}",
email=f"{suffix}@example.test",
normalized_email=f"{suffix}@example.test",
)
user = User(
id=f"user-{suffix}",
tenant_id=self.tenant.id,
account_id=account.id,
email=account.email,
)
self.session.add_all([account, user])
self.session.flush()
return user
def test_materialized_default_role_is_implicit_and_cannot_be_assigned_or_removed(self) -> None:
first = self._add_user("first")
self._add_user("second")
with (
patch(
"govoplan_access.backend.admin.service.role_templates_for_level",
return_value=(DOCS_READER,),
),
patch(
"govoplan_access.backend.security.sessions.role_templates_for_level",
return_value=(DOCS_READER,),
),
):
roles = ensure_default_roles(self.session, self.tenant)
default_role = roles["docs_reader"]
self.assertFalse(default_role.is_assignable)
self.assertEqual(self.session.query(UserRoleAssignment).count(), 0)
# The database row is only an administration projection. Even if
# stale or tampered, it must never broaden the manifest baseline.
default_role.permissions = ["mail:profile:write"]
self.session.flush()
set_user_roles(self.session, user=first, role_ids=[])
context = collect_user_authorization_context(
self.session,
first,
account=first.account,
include_system=False,
)
self.assertEqual(self.session.query(UserRoleAssignment).count(), 0)
self.assertIn("docs:documentation:read", context.scopes)
self.assertNotIn("mail:profile:write", context.scopes)
self.assertEqual([role.slug for role in context.tenant_roles], ["docs_reader"])
def test_first_authorized_request_grants_default_without_database_mutation(self) -> None:
user = self._add_user("new")
with patch(
"govoplan_access.backend.security.sessions.role_templates_for_level",
return_value=(DOCS_READER,),
):
context = collect_user_authorization_context(
self.session,
user,
account=user.account,
include_system=False,
)
self.assertIn("docs:documentation:read", context.scopes)
self.assertEqual(context.tenant_roles, [])
self.assertEqual(self.session.query(Role).count(), 0)
self.assertEqual(self.session.query(UserRoleAssignment).count(), 0)
self.assertFalse(self.session.new)
self.assertFalse(self.session.dirty)
user_id = user.id
self.session.commit()
self.session.close()
self.session = self.Session()
persisted_user = self.session.get(User, user_id)
with patch(
"govoplan_access.backend.security.sessions.role_templates_for_level",
return_value=(DOCS_READER,),
):
reopened_context = collect_user_authorization_context(
self.session,
persisted_user,
account=persisted_user.account,
include_system=False,
)
self.assertIn("docs:documentation:read", reopened_context.scopes)
self.assertEqual(self.session.query(Role).count(), 0)
self.assertEqual(self.session.query(UserRoleAssignment).count(), 0)
if __name__ == "__main__":
unittest.main()