Skip to content

Commit afbba18

Browse files
committed
refactor(rbac): drop use of access_level app-wide
1 parent 27bd4b3 commit afbba18

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+864
-445
lines changed

frontend/src/app/auth/saml/acs/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export async function POST(request: NextRequest) {
3939
"x-tracecat-service-key": process.env.TRACECAT__SERVICE_KEY!,
4040
"x-tracecat-role-type": "service",
4141
"x-tracecat-role-service-id": "tracecat-ui",
42-
"x-tracecat-role-access-level": "BASIC",
4342
}
4443
console.log("Headers", headers)
4544
const backendResponse = await fetch(backendUrl.toString(), {

frontend/src/client/schemas.gen.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

33
export const $AccessLevel = {
4-
type: "integer",
5-
enum: [0, 999],
4+
type: "string",
5+
enum: ["BASIC", "ADMIN"],
66
title: "AccessLevel",
7-
description: `Access control levels for roles.
8-
9-
.. deprecated::
10-
Use \`require_org_roles\` in RoleACL or \`@require_org_role\` decorator instead.
11-
This enum will be removed in a future version.`,
127
} as const
138

149
export const $ActionChange = {
@@ -13540,10 +13535,6 @@ export const $Role = {
1354013535
],
1354113536
title: "User Id",
1354213537
},
13543-
access_level: {
13544-
$ref: "#/components/schemas/AccessLevel",
13545-
default: 0,
13546-
},
1354713538
service_id: {
1354813539
type: "string",
1354913540
enum: [
@@ -13562,6 +13553,10 @@ export const $Role = {
1356213553
],
1356313554
title: "Service Id",
1356413555
},
13556+
access_level: {
13557+
$ref: "#/components/schemas/AccessLevel",
13558+
default: "BASIC",
13559+
},
1356513560
is_platform_superuser: {
1356613561
type: "boolean",
1356713562
title: "Is Platform Superuser",
@@ -13734,7 +13729,7 @@ export const $RoleReadWithScopes = {
1373413729
is_system: {
1373513730
type: "boolean",
1373613731
title: "Is System",
13737-
description: "Whether this is a system role (admin, editor, viewer).",
13732+
description: "Whether this is a preset/system role.",
1373813733
readOnly: true,
1373913734
},
1374013735
},

frontend/src/client/types.gen.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

3-
/**
4-
* Access control levels for roles.
5-
*
6-
* .. deprecated::
7-
* Use `require_org_roles` in RoleACL or `@require_org_role` decorator instead.
8-
* This enum will be removed in a future version.
9-
*/
10-
export type AccessLevel = 0 | 999
3+
export type AccessLevel = "BASIC" | "ADMIN"
114

125
/**
136
* Describes a change to an action between two versions.
@@ -4328,7 +4321,6 @@ export type Role = {
43284321
workspace_role?: WorkspaceRole | null
43294322
org_role?: OrgRole | null
43304323
user_id?: string | null
4331-
access_level?: AccessLevel
43324324
service_id:
43334325
| "tracecat-api"
43344326
| "tracecat-bootstrap"
@@ -4342,6 +4334,7 @@ export type Role = {
43424334
| "tracecat-schedule-runner"
43434335
| "tracecat-service"
43444336
| "tracecat-ui"
4337+
access_level?: AccessLevel
43454338
is_platform_superuser?: boolean
43464339
scopes?: Array<string>
43474340
}
@@ -4402,7 +4395,7 @@ export type RoleReadWithScopes = {
44024395
created_by?: string | null
44034396
scopes?: Array<ScopeRead>
44044397
/**
4405-
* Whether this is a system role (admin, editor, viewer).
4398+
* Whether this is a preset/system role.
44064399
*/
44074400
readonly is_system: boolean
44084401
}

packages/tracecat-admin/tracecat_admin/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ async def __aenter__(self) -> AdminClient:
6363
headers={
6464
"x-tracecat-service-key": self._config.service_key,
6565
"x-tracecat-role-service-id": "tracecat-admin-cli",
66-
"x-tracecat-role-access-level": "ADMIN",
6766
"Content-Type": "application/json",
6867
},
6968
timeout=30.0,

packages/tracecat-ee/tracecat_ee/admin/organizations/service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sqlalchemy.exc import IntegrityError
1111
from sqlalchemy.orm import selectinload
1212

13-
from tracecat.auth.types import AccessLevel, Role
13+
from tracecat.auth.types import Role
1414
from tracecat.db.models import (
1515
Organization,
1616
RegistryRepository,
@@ -157,7 +157,6 @@ async def sync_org_repository(
157157
# Create a role for the org
158158
org_role = Role(
159159
type="service",
160-
access_level=AccessLevel.ADMIN,
161160
service_id="tracecat-service",
162161
organization_id=org_id,
163162
)

packages/tracecat-ee/tracecat_ee/rbac/schemas.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
from pydantic import BaseModel, ConfigDict, Field, computed_field
99

1010
from tracecat.authz.enums import ScopeSource
11+
from tracecat.authz.scopes import PRESET_ROLE_SCOPES
12+
13+
SYSTEM_ROLE_SLUGS = frozenset(PRESET_ROLE_SCOPES) | frozenset(
14+
{"admin", "editor", "viewer"}
15+
)
1116

1217
# =============================================================================
1318
# Scope Schemas
@@ -75,8 +80,8 @@ class RoleRead(BaseModel):
7580
@computed_field
7681
@property
7782
def is_system(self) -> bool:
78-
"""Whether this is a system role (admin, editor, viewer)."""
79-
return self.slug in {"admin", "editor", "viewer"}
83+
"""Whether this is a preset/system role."""
84+
return self.slug in SYSTEM_ROLE_SLUGS
8085

8186

8287
class RoleReadWithScopes(RoleRead):

packages/tracecat-ee/tracecat_ee/rbac/service.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from tracecat.audit.logger import audit_log
1111
from tracecat.authz.controls import validate_scope_string
1212
from tracecat.authz.enums import ScopeSource
13+
from tracecat.authz.scopes import PRESET_ROLE_SCOPES
1314
from tracecat.db.models import (
1415
Group,
1516
GroupMember,
@@ -37,6 +38,9 @@ class RBACService(BaseOrgService):
3738
"""Service for managing RBAC entities and computing effective scopes."""
3839

3940
service_name = "rbac"
41+
_PROTECTED_ROLE_SLUGS = frozenset(PRESET_ROLE_SCOPES) | frozenset(
42+
{"admin", "editor", "viewer"}
43+
)
4044

4145
# =========================================================================
4246
# Scope Management
@@ -223,12 +227,12 @@ async def update_role(
223227
) -> RoleModel:
224228
"""Update a role.
225229
226-
System roles (admin, editor, viewer) cannot have their scopes modified.
230+
Preset roles cannot have their scopes modified.
227231
"""
228232
role = await self.get_role(role_id)
229233

230-
# System roles cannot have scopes modified
231-
if role.slug in {"admin", "editor", "viewer"} and scope_ids is not None:
234+
# Preset roles cannot have scopes modified
235+
if role.slug in self._PROTECTED_ROLE_SLUGS and scope_ids is not None:
232236
raise TracecatAuthorizationError("Cannot modify scopes of system roles")
233237

234238
if name is not None:
@@ -247,12 +251,12 @@ async def update_role(
247251
async def delete_role(self, role_id: UserID) -> None:
248252
"""Delete a role.
249253
250-
System roles (admin, editor, viewer) cannot be deleted.
254+
Preset roles cannot be deleted.
251255
"""
252256
role = await self.get_role(role_id)
253257

254-
# System roles cannot be deleted
255-
if role.slug in {"admin", "editor", "viewer"}:
258+
# Preset roles cannot be deleted
259+
if role.slug in self._PROTECTED_ROLE_SLUGS:
256260
raise TracecatAuthorizationError("Cannot delete system roles")
257261

258262
# Check if role is in use by any group assignments

tests/registry/test_cases_characterization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
from tracecat.api.app import app
6161
from tracecat.auth.dependencies import (
6262
ExecutorWorkspaceRole,
63-
OrgAdminUser,
63+
OrgUserRole,
6464
ServiceRole,
6565
WorkspaceUserRole,
6666
)
@@ -149,7 +149,7 @@ def override_role():
149149
WorkspaceUser,
150150
WorkspaceAdminUser,
151151
ServiceRole,
152-
OrgAdminUser,
152+
OrgUserRole,
153153
]
154154
for annotated_type in role_dependencies:
155155
metadata = get_args(annotated_type)

tests/unit/api/conftest.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
)
1414
from tracecat.api.app import app
1515
from tracecat.auth.credentials import SuperuserRole
16-
from tracecat.auth.dependencies import ExecutorWorkspaceRole, WorkspaceUserRole
16+
from tracecat.auth.dependencies import (
17+
ExecutorWorkspaceRole,
18+
OrgUserRole,
19+
WorkspaceUserRole,
20+
)
1721
from tracecat.auth.types import Role
1822
from tracecat.cases.router import WorkspaceUser
1923
from tracecat.contexts import ctx_role
2024
from tracecat.db.engine import get_async_session
2125
from tracecat.secrets.router import (
22-
OrgAdminUser,
2326
WorkspaceAdminUser,
2427
)
2528
from tracecat.secrets.router import (
@@ -63,7 +66,7 @@ def client() -> Generator[TestClient, None, None]:
6366
OrganizationAdminUserRole,
6467
SecretsWorkspaceUser,
6568
WorkspaceAdminUser,
66-
OrgAdminUser,
69+
OrgUserRole,
6770
TablesWorkspaceUser,
6871
TablesWorkspaceEditorUser,
6972
]

tests/unit/test_auth_middleware.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ async def mock_list_user_memberships(user_id):
175175
allow_user=True,
176176
allow_service=False,
177177
require_workspace="yes",
178-
min_access_level=None,
179-
require_workspace_roles=None,
180178
)
181179

182180
assert db_call_count == 1 # First call triggers DB query
@@ -191,8 +189,6 @@ async def mock_list_user_memberships(user_id):
191189
allow_user=True,
192190
allow_service=False,
193191
require_workspace="yes",
194-
min_access_level=None,
195-
require_workspace_roles=None,
196192
)
197193

198194
assert db_call_count == 1 # Still only 1 DB call - used cache!
@@ -275,8 +271,6 @@ async def time_auth_check(user, use_cache=True):
275271
allow_user=True,
276272
allow_service=False,
277273
require_workspace="yes",
278-
min_access_level=None,
279-
require_workspace_roles=None,
280274
)
281275
end = time.perf_counter()
282276

@@ -423,8 +417,6 @@ async def test_cache_user_id_validation():
423417
"allow_user": True,
424418
"allow_service": False,
425419
"require_workspace": "yes",
426-
"min_access_level": None,
427-
"require_workspace_roles": None,
428420
}
429421

430422
# First check with user1 - should populate cache
@@ -516,8 +508,6 @@ async def test_cache_size_limit():
516508
allow_user=True,
517509
allow_service=False,
518510
require_workspace="yes",
519-
min_access_level=None,
520-
require_workspace_roles=None,
521511
)
522512

523513
# Verify cache was NOT populated due to size limit
@@ -586,8 +576,6 @@ async def test_organization_id_populated_when_require_workspace_no(mocker):
586576
allow_user=True,
587577
allow_service=False,
588578
require_workspace="no",
589-
min_access_level=None,
590-
require_workspace_roles=None,
591579
)
592580

593581
# Verify organization_id was inferred from the user's OrganizationMembership
@@ -652,8 +640,6 @@ async def test_role_dependency_infers_org_from_single_membership(
652640
allow_user=True,
653641
allow_service=False,
654642
require_workspace="no",
655-
min_access_level=None,
656-
require_workspace_roles=None,
657643
)
658644

659645
assert role.organization_id == org.id
@@ -738,8 +724,6 @@ async def test_role_dependency_requires_workspace_for_multi_org(
738724
allow_user=True,
739725
allow_service=False,
740726
require_workspace="no",
741-
min_access_level=None,
742-
require_workspace_roles=None,
743727
)
744728

745729
assert excinfo.value.status_code == status.HTTP_400_BAD_REQUEST

0 commit comments

Comments
 (0)