fix(tenancy): close isolation and permission gaps

This commit is contained in:
Junyan Qin
2026-07-20 04:02:24 +08:00
parent 18bcb41e9e
commit 90a9774882
30 changed files with 3294 additions and 1764 deletions
+4 -4
View File
@@ -231,7 +231,7 @@ class TestBotLogsEndpoint:
assert 'total_count' in data['data']
@pytest.mark.asyncio
async def test_viewer_cannot_read_message_logs(self, quart_test_client, fake_bot_app):
async def test_viewer_can_read_ordinary_bot_logs(self, quart_test_client, fake_bot_app):
access = fake_bot_app.workspace_collaboration_service.resolve_account_workspace.return_value
original_role = access.membership.role
access.membership.role = 'viewer'
@@ -245,9 +245,9 @@ class TestBotLogsEndpoint:
finally:
access.membership.role = original_role
assert response.status_code == 403
assert (await response.get_json())['code'] == 'permission_denied'
fake_bot_app.bot_service.list_event_logs.assert_not_awaited()
assert response.status_code == 200
assert (await response.get_json())['code'] == 0
fake_bot_app.bot_service.list_event_logs.assert_awaited_once()
@pytest.mark.usefixtures('mock_circular_import_chain')
+28
View File
@@ -155,6 +155,34 @@ class TestMonitoringOverviewEndpoint:
data = await response.get_json()
assert data['code'] == 0
@pytest.mark.asyncio
async def test_viewer_can_read_monitoring_but_cannot_export(
self,
quart_test_client,
fake_monitoring_app,
):
"""Ordinary monitoring is resource.view; export remains data.export."""
membership = (
fake_monitoring_app.workspace_collaboration_service.resolve_account_workspace.return_value.membership
)
original_role = membership.role
membership.role = 'viewer'
try:
response = await quart_test_client.get(
'/api/v1/monitoring/overview',
headers={'Authorization': 'Bearer test_token'},
)
assert response.status_code == 200
export_response = await quart_test_client.get(
'/api/v1/monitoring/export?type=messages',
headers={'Authorization': 'Bearer test_token'},
)
assert export_response.status_code == 403
assert (await export_response.get_json())['code'] == 'permission_denied'
finally:
membership.role = original_role
@pytest.mark.usefixtures('mock_circular_import_chain')
class TestMonitoringMessagesEndpoint:
+26
View File
@@ -233,6 +233,22 @@ async def test_owner_invites_second_account_and_secret_is_not_persisted(workspac
assert (await forbidden_invite.get_json())['code'] == 'permission_denied'
async def test_invitation_accept_rejects_invalid_bearer_as_authentication_failure(workspace_api):
_, client, _, _ = workspace_api
response = await client.post(
'/api/v1/invitations/accept',
headers={'Authorization': 'Bearer definitely-not-a-jwt'},
json={'token': 'lbi_not-a-real-invitation'},
)
assert response.status_code == 401
assert await response.get_json() == {
'code': 'invalid_authentication',
'msg': 'Invalid authentication credentials',
}
async def test_workspace_selector_and_path_cannot_escape_membership(workspace_api):
_, client, _, owner_token = workspace_api
@@ -412,6 +428,16 @@ async def test_cloud_projection_is_selected_explicitly_and_directory_writes_use_
assert by_uuid[singleton_uuid]['permissions']
assert by_uuid[cloud_workspace_uuid]['placement_generation'] == 12
list_response = await client.get(
'/api/v1/workspaces',
headers=_auth(owner_token, singleton_uuid),
)
assert list_response.status_code == 200
assert {workspace['uuid'] for workspace in (await list_response.get_json())['data']['workspaces']} == {
singleton_uuid,
cloud_workspace_uuid,
}
current_response = await client.get(
'/api/v1/workspaces/current',
headers=_auth(owner_token, cloud_workspace_uuid),