Skip to content

Commit 217092b

Browse files
committed
refactor(rbac): drop usage of old membership roles in rbac api
1 parent 9e5f245 commit 217092b

File tree

9 files changed

+100
-212
lines changed

9 files changed

+100
-212
lines changed

frontend/src/client/schemas.gen.ts

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10536,7 +10536,15 @@ export const $OrgMemberRead = {
1053610536
title: "Email",
1053710537
},
1053810538
role: {
10539-
$ref: "#/components/schemas/OrgRole",
10539+
anyOf: [
10540+
{
10541+
type: "string",
10542+
},
10543+
{
10544+
type: "null",
10545+
},
10546+
],
10547+
title: "Role",
1054010548
},
1054110549
is_active: {
1054210550
type: "boolean",
@@ -14512,9 +14520,9 @@ export const $ScopeRead = {
1451214520

1451314521
export const $ScopeSource = {
1451414522
type: "string",
14515-
enum: ["system", "registry", "custom"],
14523+
enum: ["platform", "custom"],
1451614524
title: "ScopeSource",
14517-
description: "Source of a scope definition.",
14525+
description: "Source/ownership of a scope definition.",
1451814526
} as const
1451914527

1452014528
export const $SecretCreate = {
@@ -18171,38 +18179,6 @@ export const $UserScopesRead = {
1817118179
title: "Scopes",
1817218180
description: "List of effective scope strings for the user",
1817318181
},
18174-
org_role_scopes: {
18175-
items: {
18176-
type: "string",
18177-
},
18178-
type: "array",
18179-
title: "Org Role Scopes",
18180-
description: "Scopes from organization role",
18181-
},
18182-
workspace_role_scopes: {
18183-
items: {
18184-
type: "string",
18185-
},
18186-
type: "array",
18187-
title: "Workspace Role Scopes",
18188-
description: "Scopes from workspace role",
18189-
},
18190-
group_scopes: {
18191-
items: {
18192-
type: "string",
18193-
},
18194-
type: "array",
18195-
title: "Group Scopes",
18196-
description: "Scopes from group memberships",
18197-
},
18198-
user_role_scopes: {
18199-
items: {
18200-
type: "string",
18201-
},
18202-
type: "array",
18203-
title: "User Role Scopes",
18204-
description: "Scopes from direct user role assignments",
18205-
},
1820618182
},
1820718183
type: "object",
1820818184
required: ["scopes"],

frontend/src/client/services.gen.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import type {
4040
AdminListOrgRepositoriesResponse,
4141
AdminListOrgRepositoryVersionsData,
4242
AdminListOrgRepositoryVersionsResponse,
43+
AdminListOrgTiersData,
44+
AdminListOrgTiersResponse,
4345
AdminListTiersData,
4446
AdminListTiersResponse,
4547
AdminListUsersResponse,
@@ -4363,6 +4365,29 @@ export const adminCreateTier = (
43634365
})
43644366
}
43654367

4368+
/**
4369+
* List Org Tiers
4370+
* List tier assignments for organizations.
4371+
* @param data The data for the request.
4372+
* @param data.orgIds Optional list of organization IDs to filter results
4373+
* @returns OrganizationTierRead Successful Response
4374+
* @throws ApiError
4375+
*/
4376+
export const adminListOrgTiers = (
4377+
data: AdminListOrgTiersData = {}
4378+
): CancelablePromise<AdminListOrgTiersResponse> => {
4379+
return __request(OpenAPI, {
4380+
method: "GET",
4381+
url: "/admin/tiers/organizations",
4382+
query: {
4383+
org_ids: data.orgIds,
4384+
},
4385+
errors: {
4386+
422: "Validation Error",
4387+
},
4388+
})
4389+
}
4390+
43664391
/**
43674392
* Get Tier
43684393
* Get tier by ID.
@@ -8424,13 +8449,8 @@ export const vcsGetGithubAppCredentialsStatus =
84248449
* Get My Scopes
84258450
* Get the current user's effective scopes.
84268451
*
8427-
* Returns a breakdown of scopes by source:
8428-
* - org_role_scopes: From org membership role (OWNER/ADMIN/MEMBER)
8429-
* - workspace_role_scopes: From workspace membership role (if in workspace context)
8430-
* - group_scopes: From group memberships and their role assignments (EE only)
8431-
* - user_role_scopes: From direct user role assignments (EE only)
8432-
*
8433-
* The combined `scopes` list is what's actually used for authorization.
8452+
* Scopes are computed from DB-driven role assignments during auth
8453+
* (UserRoleAssignment + GroupRoleAssignment → Role → RoleScope → Scope).
84348454
* @returns UserScopesRead Successful Response
84358455
* @throws ApiError
84368456
*/

frontend/src/client/types.gen.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3389,7 +3389,7 @@ export type OrgMemberRead = {
33893389
first_name: string | null
33903390
last_name: string | null
33913391
email: string
3392-
role: OrgRole
3392+
role: string | null
33933393
is_active: boolean
33943394
is_superuser: boolean
33953395
is_verified: boolean
@@ -4601,9 +4601,9 @@ export type ScopeRead = {
46014601
}
46024602

46034603
/**
4604-
* Source of a scope definition.
4604+
* Source/ownership of a scope definition.
46054605
*/
4606-
export type ScopeSource = "system" | "registry" | "custom"
4606+
export type ScopeSource = "platform" | "custom"
46074607

46084608
/**
46094609
* Create a new secret.
@@ -5761,22 +5761,6 @@ export type UserScopesRead = {
57615761
* List of effective scope strings for the user
57625762
*/
57635763
scopes: Array<string>
5764-
/**
5765-
* Scopes from organization role
5766-
*/
5767-
org_role_scopes?: Array<string>
5768-
/**
5769-
* Scopes from workspace role
5770-
*/
5771-
workspace_role_scopes?: Array<string>
5772-
/**
5773-
* Scopes from group memberships
5774-
*/
5775-
group_scopes?: Array<string>
5776-
/**
5777-
* Scopes from direct user role assignments
5778-
*/
5779-
user_role_scopes?: Array<string>
57805764
}
57815765

57825766
export type UserUpdate = {
@@ -7717,6 +7701,15 @@ export type AdminCreateTierData = {
77177701

77187702
export type AdminCreateTierResponse = TierRead
77197703

7704+
export type AdminListOrgTiersData = {
7705+
/**
7706+
* Optional list of organization IDs to filter results
7707+
*/
7708+
orgIds?: Array<string> | null
7709+
}
7710+
7711+
export type AdminListOrgTiersResponse = Array<OrganizationTierRead>
7712+
77207713
export type AdminGetTierData = {
77217714
tierId: string
77227715
}
@@ -11169,6 +11162,21 @@ export type $OpenApiTs = {
1116911162
}
1117011163
}
1117111164
}
11165+
"/admin/tiers/organizations": {
11166+
get: {
11167+
req: AdminListOrgTiersData
11168+
res: {
11169+
/**
11170+
* Successful Response
11171+
*/
11172+
200: Array<OrganizationTierRead>
11173+
/**
11174+
* Validation Error
11175+
*/
11176+
422: HTTPValidationError
11177+
}
11178+
}
11179+
}
1117211180
"/admin/tiers/{tier_id}": {
1117311181
get: {
1117411182
req: AdminGetTierData

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

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from tracecat.authz.enums import ScopeSource
1313
from tracecat.db.models import (
1414
Group,
15-
GroupAssignment,
1615
GroupMember,
16+
GroupRoleAssignment,
1717
OrganizationMembership,
1818
RoleScope,
1919
Scope,
@@ -256,7 +256,7 @@ async def delete_role(self, role_id: UserID) -> None:
256256
raise TracecatAuthorizationError("Cannot delete system roles")
257257

258258
# Check if role is in use by any group assignments
259-
stmt = select(func.count()).where(GroupAssignment.role_id == role_id)
259+
stmt = select(func.count()).where(GroupRoleAssignment.role_id == role_id)
260260
result = await self.session.execute(stmt)
261261
group_count = result.scalar() or 0
262262
if group_count > 0:
@@ -438,38 +438,38 @@ async def list_assignments(
438438
*,
439439
group_id: UserID | None = None,
440440
workspace_id: WorkspaceID | None = None,
441-
) -> Sequence[GroupAssignment]:
441+
) -> Sequence[GroupRoleAssignment]:
442442
"""List group assignments for the organization."""
443443
stmt = (
444-
select(GroupAssignment)
445-
.where(GroupAssignment.organization_id == self.organization_id)
444+
select(GroupRoleAssignment)
445+
.where(GroupRoleAssignment.organization_id == self.organization_id)
446446
.options(
447-
selectinload(GroupAssignment.group),
448-
selectinload(GroupAssignment.role),
449-
selectinload(GroupAssignment.workspace),
447+
selectinload(GroupRoleAssignment.group),
448+
selectinload(GroupRoleAssignment.role),
449+
selectinload(GroupRoleAssignment.workspace),
450450
)
451451
)
452452

453453
if group_id is not None:
454-
stmt = stmt.where(GroupAssignment.group_id == group_id)
454+
stmt = stmt.where(GroupRoleAssignment.group_id == group_id)
455455
if workspace_id is not None:
456-
stmt = stmt.where(GroupAssignment.workspace_id == workspace_id)
456+
stmt = stmt.where(GroupRoleAssignment.workspace_id == workspace_id)
457457

458458
result = await self.session.execute(stmt)
459459
return result.scalars().all()
460460

461-
async def get_assignment(self, assignment_id: UserID) -> GroupAssignment:
461+
async def get_assignment(self, assignment_id: UserID) -> GroupRoleAssignment:
462462
"""Get a group assignment by ID."""
463463
stmt = (
464-
select(GroupAssignment)
464+
select(GroupRoleAssignment)
465465
.where(
466-
GroupAssignment.id == assignment_id,
467-
GroupAssignment.organization_id == self.organization_id,
466+
GroupRoleAssignment.id == assignment_id,
467+
GroupRoleAssignment.organization_id == self.organization_id,
468468
)
469469
.options(
470-
selectinload(GroupAssignment.group),
471-
selectinload(GroupAssignment.role),
472-
selectinload(GroupAssignment.workspace),
470+
selectinload(GroupRoleAssignment.group),
471+
selectinload(GroupRoleAssignment.role),
472+
selectinload(GroupRoleAssignment.workspace),
473473
)
474474
)
475475
result = await self.session.execute(stmt)
@@ -485,7 +485,7 @@ async def create_assignment(
485485
group_id: UserID,
486486
role_id: UserID,
487487
workspace_id: WorkspaceID | None = None,
488-
) -> GroupAssignment:
488+
) -> GroupRoleAssignment:
489489
"""Create a group assignment.
490490
491491
Args:
@@ -494,7 +494,7 @@ async def create_assignment(
494494
workspace_id: Workspace for workspace-level assignment (None for org-wide)
495495
496496
Returns:
497-
Created GroupAssignment
497+
Created GroupRoleAssignment
498498
"""
499499
# Verify group exists
500500
await self.get_group(group_id)
@@ -512,7 +512,7 @@ async def create_assignment(
512512
if result.scalar_one_or_none() is None:
513513
raise TracecatNotFoundError("Workspace not found")
514514

515-
assignment = GroupAssignment(
515+
assignment = GroupRoleAssignment(
516516
organization_id=self.organization_id,
517517
group_id=group_id,
518518
role_id=role_id,
@@ -534,7 +534,7 @@ async def update_assignment(
534534
assignment_id: UserID,
535535
*,
536536
role_id: UserID,
537-
) -> GroupAssignment:
537+
) -> GroupRoleAssignment:
538538
"""Update a group assignment (change role)."""
539539
assignment = await self.get_assignment(assignment_id)
540540

@@ -759,13 +759,13 @@ async def get_group_scopes(
759759
Frozenset of scope name strings
760760
"""
761761
# Query to get all scope names from user's group memberships and role assignments
762-
# This joins: GroupMember -> Group -> GroupAssignment -> Role -> RoleScope -> Scope
762+
# This joins: GroupMember -> Group -> GroupRoleAssignment -> Role -> RoleScope -> Scope
763763
stmt = (
764764
select(Scope.name)
765765
.select_from(GroupMember)
766766
.join(Group, GroupMember.group_id == Group.id)
767-
.join(GroupAssignment, GroupAssignment.group_id == Group.id)
768-
.join(RoleModel, GroupAssignment.role_id == RoleModel.id)
767+
.join(GroupRoleAssignment, GroupRoleAssignment.group_id == Group.id)
768+
.join(RoleModel, GroupRoleAssignment.role_id == RoleModel.id)
769769
.join(RoleScope, RoleScope.role_id == RoleModel.id)
770770
.join(Scope, RoleScope.scope_id == Scope.id)
771771
.where(
@@ -779,12 +779,12 @@ async def get_group_scopes(
779779
# - Workspace-specific assignments only apply if requesting that workspace
780780
if workspace_id is not None:
781781
stmt = stmt.where(
782-
(GroupAssignment.workspace_id.is_(None))
783-
| (GroupAssignment.workspace_id == workspace_id)
782+
(GroupRoleAssignment.workspace_id.is_(None))
783+
| (GroupRoleAssignment.workspace_id == workspace_id)
784784
)
785785
else:
786786
# Only org-wide assignments
787-
stmt = stmt.where(GroupAssignment.workspace_id.is_(None))
787+
stmt = stmt.where(GroupRoleAssignment.workspace_id.is_(None))
788788

789789
result = await self.session.execute(stmt)
790790
scope_names = result.scalars().all()

tests/unit/test_rbac_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async def seeded_scopes(session: AsyncSession) -> list[Scope]:
7878
"""Seed system scopes and return them."""
7979
await seed_system_scopes(session)
8080
result = await session.execute(
81-
select(Scope).where(Scope.source == ScopeSource.SYSTEM)
81+
select(Scope).where(Scope.source == ScopeSource.PLATFORM)
8282
)
8383
return list(result.scalars().all())
8484

@@ -124,9 +124,9 @@ async def test_list_scopes_filter_by_source(
124124
"""List scopes can filter by source."""
125125
service = RBACService(session, role=role)
126126
scopes = await service.list_scopes(
127-
include_system=True, source=ScopeSource.SYSTEM
127+
include_system=True, source=ScopeSource.PLATFORM
128128
)
129-
assert all(s.source == ScopeSource.SYSTEM for s in scopes)
129+
assert all(s.source == ScopeSource.PLATFORM for s in scopes)
130130

131131
async def test_create_custom_scope(
132132
self,

0 commit comments

Comments
 (0)