feat(server): add project RBAC and guarded workflows

This commit is contained in:
2026-07-30 16:45:09 +08:00
parent 3fbb17bb30
commit ae1a657554
29 changed files with 1431 additions and 412 deletions
+17 -12
View File
@@ -138,7 +138,7 @@ async def test_batch_sync_metadata_users_returns_per_user_results(monkeypatch):
keycloak_id=users[1].keycloak_id,
username="bob",
email="bob@example.com",
role="viewer",
role="user",
is_active=True,
),
]
@@ -156,7 +156,7 @@ async def test_batch_sync_metadata_users_returns_per_user_results(monkeypatch):
@pytest.mark.anyio
async def test_update_metadata_user_updates_role_and_active_status(monkeypatch):
user_id = uuid4()
updated = _user(id=user_id, role="operator", is_active=False)
updated = _user(id=user_id, role="user", is_active=False)
repo = SimpleNamespace(
session=object(),
update_user_admin=AsyncMock(return_value=updated),
@@ -165,7 +165,7 @@ async def test_update_metadata_user_updates_role_and_active_status(monkeypatch):
response = await admin_metadata.update_metadata_user(
MetadataUserUpdateRequest(
role="operator",
role="user",
is_active=False,
),
user_id=user_id,
@@ -175,9 +175,9 @@ async def test_update_metadata_user_updates_role_and_active_status(monkeypatch):
repo.update_user_admin.assert_awaited_once_with(
user_id,
updates={"role": "operator", "is_active": False},
updates={"role": "user", "is_active": False},
)
assert response.role == "operator"
assert response.role == "user"
admin_metadata.log_audit_event.assert_awaited_once()
@@ -192,7 +192,7 @@ async def test_update_metadata_user_rejects_self_update(monkeypatch):
with pytest.raises(HTTPException) as exc:
await admin_metadata.update_metadata_user(
MetadataUserUpdateRequest(role="viewer"),
MetadataUserUpdateRequest(role="user"),
user_id=current_user.id,
current_user=current_user,
metadata_repo=repo,
@@ -221,6 +221,7 @@ async def test_create_project_audits_metadata_admin_change(monkeypatch):
create_project=AsyncMock(return_value=project),
)
monkeypatch.setattr(admin_metadata, "log_audit_event", AsyncMock())
current_user = _user(role="admin", is_superuser=True)
response = await admin_metadata.create_admin_project(
AdminProjectCreateRequest(
@@ -231,12 +232,16 @@ async def test_create_project_audits_metadata_admin_change(monkeypatch):
map_extent={"bbox": [1, 2, 3, 4]},
status="active",
),
current_user=_user(role="admin", is_superuser=True),
current_user=current_user,
metadata_repo=repo,
)
assert response.project_id == project.id
repo.create_project.assert_awaited_once()
assert (
repo.create_project.await_args.kwargs["creator_user_id"]
== current_user.id
)
admin_metadata.log_audit_event.assert_awaited_once()
@@ -482,7 +487,7 @@ async def test_update_project_member_role_audits_change(monkeypatch):
membership = _membership(
user_id=user_id,
project_id=project_id,
project_role="admin",
project_role="member",
)
repo = SimpleNamespace(
session=object(),
@@ -492,16 +497,16 @@ async def test_update_project_member_role_audits_change(monkeypatch):
monkeypatch.setattr(admin_metadata, "log_audit_event", AsyncMock())
response = await admin_metadata.update_project_member(
ProjectMemberUpdateRequest(project_role="admin"),
ProjectMemberUpdateRequest(project_role="member"),
project_id=project_id,
user_id=user_id,
current_user=_user(role="admin", is_superuser=True),
metadata_repo=repo,
)
assert response.project_role == "admin"
assert response.project_role == "member"
repo.update_project_member_role.assert_awaited_once_with(
project_id, user_id, "admin"
project_id, user_id, "member"
)
admin_metadata.log_audit_event.assert_awaited_once()
@@ -519,7 +524,7 @@ async def test_update_project_member_rejects_self_membership_change(monkeypatch)
with pytest.raises(HTTPException) as exc:
await admin_metadata.update_project_member(
ProjectMemberUpdateRequest(project_role="admin"),
ProjectMemberUpdateRequest(project_role="member"),
project_id=project_id,
user_id=current_user.id,
current_user=current_user,