feat: enhance project and scenario management with role-based access control
- Implemented role-based access control for project and scenario routes. - Added authorization checks to ensure users have appropriate roles for viewing and managing projects and scenarios. - Introduced utility functions for ensuring project and scenario access based on user roles. - Refactored project and scenario routes to utilize new authorization helpers. - Created initial data seeding script to set up default roles and an admin user. - Added tests for authorization helpers and initial data seeding functionality. - Updated exception handling to include authorization errors.
This commit is contained in:
165
tests/test_authorization_helpers.py
Normal file
165
tests/test_authorization_helpers.py
Normal file
@@ -0,0 +1,165 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from models import MiningOperationType, Project, Scenario, ScenarioStatus, User
|
||||
from services.authorization import (
|
||||
ensure_project_access,
|
||||
ensure_scenario_access,
|
||||
ensure_scenario_in_project,
|
||||
)
|
||||
from services.exceptions import AuthorizationError, EntityNotFoundError
|
||||
from services.unit_of_work import UnitOfWork
|
||||
|
||||
|
||||
def _create_user_with_roles(
|
||||
uow: UnitOfWork,
|
||||
*,
|
||||
email: str,
|
||||
username: str,
|
||||
roles: set[str],
|
||||
) -> User:
|
||||
assert uow.users is not None
|
||||
assert uow.roles is not None
|
||||
|
||||
user = User(
|
||||
email=email,
|
||||
username=username,
|
||||
password_hash=User.hash_password("secret"),
|
||||
is_active=True,
|
||||
)
|
||||
uow.users.create(user)
|
||||
uow.ensure_default_roles()
|
||||
|
||||
for role_name in roles:
|
||||
role = uow.roles.get_by_name(role_name)
|
||||
assert role is not None, f"Role {role_name} should exist"
|
||||
uow.users.assign_role(user_id=user.id, role_id=role.id)
|
||||
|
||||
return uow.users.get(user.id, with_roles=True)
|
||||
|
||||
|
||||
def _create_project(uow: UnitOfWork, name: str) -> Project:
|
||||
assert uow.projects is not None
|
||||
project = Project(
|
||||
name=name,
|
||||
location=None,
|
||||
operation_type=MiningOperationType.OTHER,
|
||||
description=None,
|
||||
)
|
||||
uow.projects.create(project)
|
||||
return project
|
||||
|
||||
|
||||
def _create_scenario(uow: UnitOfWork, project: Project, name: str) -> Scenario:
|
||||
assert uow.scenarios is not None
|
||||
scenario = Scenario(
|
||||
project_id=project.id,
|
||||
name=name,
|
||||
description=None,
|
||||
status=ScenarioStatus.DRAFT,
|
||||
)
|
||||
uow.scenarios.create(scenario)
|
||||
return scenario
|
||||
|
||||
|
||||
def test_ensure_project_access_allows_view_roles(unit_of_work_factory) -> None:
|
||||
with unit_of_work_factory() as uow:
|
||||
project = _create_project(uow, "Project A")
|
||||
user = _create_user_with_roles(
|
||||
uow,
|
||||
email="viewer@example.com",
|
||||
username="viewer",
|
||||
roles={"viewer"},
|
||||
)
|
||||
|
||||
resolved = ensure_project_access(
|
||||
uow,
|
||||
project_id=project.id,
|
||||
user=user,
|
||||
)
|
||||
assert resolved.id == project.id
|
||||
|
||||
with pytest.raises(AuthorizationError):
|
||||
ensure_project_access(
|
||||
uow,
|
||||
project_id=project.id,
|
||||
user=user,
|
||||
require_manage=True,
|
||||
)
|
||||
|
||||
|
||||
def test_ensure_project_access_allows_manage_roles(unit_of_work_factory) -> None:
|
||||
with unit_of_work_factory() as uow:
|
||||
project = _create_project(uow, "Project B")
|
||||
user = _create_user_with_roles(
|
||||
uow,
|
||||
email="manager@example.com",
|
||||
username="manager",
|
||||
roles={"project_manager"},
|
||||
)
|
||||
|
||||
resolved = ensure_project_access(
|
||||
uow,
|
||||
project_id=project.id,
|
||||
user=user,
|
||||
require_manage=True,
|
||||
)
|
||||
assert resolved.id == project.id
|
||||
|
||||
|
||||
def test_ensure_scenario_access(unit_of_work_factory) -> None:
|
||||
with unit_of_work_factory() as uow:
|
||||
project = _create_project(uow, "Project C")
|
||||
scenario = _create_scenario(uow, project, "Scenario C1")
|
||||
user = _create_user_with_roles(
|
||||
uow,
|
||||
email="analyst@example.com",
|
||||
username="analyst",
|
||||
roles={"analyst"},
|
||||
)
|
||||
|
||||
resolved = ensure_scenario_access(
|
||||
uow,
|
||||
scenario_id=scenario.id,
|
||||
user=user,
|
||||
)
|
||||
assert resolved.id == scenario.id
|
||||
|
||||
with pytest.raises(AuthorizationError):
|
||||
ensure_scenario_access(
|
||||
uow,
|
||||
scenario_id=scenario.id,
|
||||
user=user,
|
||||
require_manage=True,
|
||||
)
|
||||
|
||||
|
||||
def test_ensure_scenario_in_project_validates_membership(unit_of_work_factory) -> None:
|
||||
with unit_of_work_factory() as uow:
|
||||
project_one = _create_project(uow, "Project D")
|
||||
project_two = _create_project(uow, "Project E")
|
||||
scenario = _create_scenario(uow, project_one, "Scenario D1")
|
||||
user = _create_user_with_roles(
|
||||
uow,
|
||||
email="manager2@example.com",
|
||||
username="manager2",
|
||||
roles={"project_manager"},
|
||||
)
|
||||
|
||||
resolved = ensure_scenario_in_project(
|
||||
uow,
|
||||
project_id=project_one.id,
|
||||
scenario_id=scenario.id,
|
||||
user=user,
|
||||
require_manage=True,
|
||||
)
|
||||
assert resolved.id == scenario.id
|
||||
|
||||
with pytest.raises(EntityNotFoundError):
|
||||
ensure_scenario_in_project(
|
||||
uow,
|
||||
project_id=project_two.id,
|
||||
scenario_id=scenario.id,
|
||||
user=user,
|
||||
)
|
||||
Reference in New Issue
Block a user