Skip to content

Commit 6dfd08f

Browse files
fregataaclaudelablup-octodog
authored andcommitted
feat(BA-5613): implement blind role invitation data layer and service (#11181)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: octodog <mu001@lablup.com>
1 parent d241410 commit 6dfd08f

25 files changed

Lines changed: 1515 additions & 13 deletions

File tree

changes/11181.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add blind role invitation data layer: model, migration, RBAC actions, and service for inviting users to project roles by email

docs/manager/graphql-reference/supergraph.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13818,6 +13818,7 @@ enum RBACElementType
1381813818
DEPLOYMENT_POLICY @join__enumValue(graph: STRAWBERRY)
1381913819
DEPLOYMENT_REVISION @join__enumValue(graph: STRAWBERRY)
1382013820
IMAGE_ALIAS @join__enumValue(graph: STRAWBERRY)
13821+
ROLE_ASSIGNMENT @join__enumValue(graph: STRAWBERRY)
1382113822
ARTIFACT_REVISION @join__enumValue(graph: STRAWBERRY)
1382213823
}
1382313824

docs/manager/graphql-reference/v2-schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8970,6 +8970,7 @@ enum RBACElementType {
89708970
DEPLOYMENT_POLICY
89718971
DEPLOYMENT_REVISION
89728972
IMAGE_ALIAS
8973+
ROLE_ASSIGNMENT
89738974
ARTIFACT_REVISION
89748975
}
89758976

src/ai/backend/common/data/permission/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ class RBACElementType(enum.StrEnum):
412412
DEPLOYMENT_POLICY = "deployment:policy"
413413
DEPLOYMENT_REVISION = "deployment:revision"
414414
IMAGE_ALIAS = "image:alias"
415+
ROLE_ASSIGNMENT = "role:assignment"
415416

416417
# === Entity-level scopes (for entity-scope permissions) ===
417418
ARTIFACT_REVISION = "artifact_revision"

src/ai/backend/common/dto/manager/v2/rbac/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class RBACElementTypeDTO(StrEnum):
116116
DEPLOYMENT_POLICY = "deployment:policy"
117117
DEPLOYMENT_REVISION = "deployment:revision"
118118
IMAGE_ALIAS = "image:alias"
119+
ROLE_ASSIGNMENT = "role:assignment"
119120

120121
# Entity-level scopes
121122
ARTIFACT_REVISION = "artifact_revision"

src/ai/backend/common/exception.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class ErrorDomain(enum.StrEnum):
184184
PROMETHEUS_QUERY_PRESET = "prometheus-query-preset"
185185
PROMETHEUS_QUERY_PRESET_CATEGORY = "prometheus-query-preset-category"
186186
RUNTIME_VARIANT = "runtime-variant"
187+
ROLE_INVITATION = "role-invitation"
187188

188189
EXTERNAL_SYSTEM = "external-system" # Errors from external systems
189190

src/ai/backend/manager/actions/action/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
ProjectSoftDeleteRBACAction,
3434
ProjectUpdateRBACAction,
3535
)
36+
from .rbac_role_invitation import (
37+
AcceptRoleInvitationAction,
38+
CancelRoleInvitationAction,
39+
CreateRoleInvitationByEmailAction,
40+
RejectRoleInvitationAction,
41+
RoleInvitationReadRBACAction,
42+
)
3643
from .rbac_session import (
3744
SessionCreateRBACAction,
3845
SessionGetRBACAction,
@@ -104,6 +111,11 @@
104111
VFolderGrantUpdateRBACAction,
105112
VFolderGrantSoftDeleteRBACAction,
106113
VFolderGrantHardDeleteRBACAction,
114+
CreateRoleInvitationByEmailAction,
115+
RoleInvitationReadRBACAction,
116+
AcceptRoleInvitationAction,
117+
RejectRoleInvitationAction,
118+
CancelRoleInvitationAction,
107119
)
108120

109121
__all__ = (
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""RBAC action declarations for role invitation operations.
2+
3+
Each class is both an RBAC permission declaration (registered in
4+
RBAC_ACTION_REGISTRY) and a data-carrying action that the service layer
5+
receives.
6+
"""
7+
8+
from dataclasses import dataclass, field
9+
from typing import override
10+
from uuid import UUID
11+
12+
from ai.backend.common.data.permission.types import OperationType, RBACElementType
13+
from ai.backend.manager.actions.action.rbac import (
14+
BaseRBACAction,
15+
RBACActionName,
16+
RBACRequiredPermission,
17+
)
18+
from ai.backend.manager.data.role_invitation.types import RoleInvitationData
19+
20+
21+
@dataclass
22+
class CreateRoleInvitationByEmailAction(BaseRBACAction):
23+
"""Project admin creates role invitations by invitee emails."""
24+
25+
invitee_emails: list[str]
26+
inviter_user_id: UUID
27+
role_id: UUID
28+
29+
@classmethod
30+
@override
31+
def action_name(cls) -> RBACActionName:
32+
return RBACActionName.CREATE
33+
34+
@classmethod
35+
@override
36+
def required_permission(cls) -> RBACRequiredPermission:
37+
return RBACRequiredPermission(RBACElementType.ROLE_ASSIGNMENT, OperationType.CREATE)
38+
39+
@classmethod
40+
@override
41+
def permission_scope(cls) -> RBACElementType:
42+
return RBACElementType.PROJECT
43+
44+
45+
@dataclass
46+
class CreateRoleInvitationResult:
47+
"""Result of creating role invitations."""
48+
49+
created: list[RoleInvitationData] = field(default_factory=list)
50+
51+
52+
class RoleInvitationReadRBACAction(BaseRBACAction):
53+
"""Read / search role invitations."""
54+
55+
@classmethod
56+
@override
57+
def action_name(cls) -> RBACActionName:
58+
return RBACActionName.SEARCH
59+
60+
@classmethod
61+
@override
62+
def required_permission(cls) -> RBACRequiredPermission:
63+
return RBACRequiredPermission(RBACElementType.ROLE_ASSIGNMENT, OperationType.READ)
64+
65+
@classmethod
66+
@override
67+
def permission_scope(cls) -> RBACElementType:
68+
return RBACElementType.PROJECT
69+
70+
71+
@dataclass
72+
class AcceptRoleInvitationAction(BaseRBACAction):
73+
"""Invitee accepts a role invitation."""
74+
75+
invitation_id: UUID
76+
77+
@classmethod
78+
@override
79+
def action_name(cls) -> RBACActionName:
80+
return RBACActionName.UPDATE
81+
82+
@classmethod
83+
@override
84+
def required_permission(cls) -> RBACRequiredPermission:
85+
return RBACRequiredPermission(RBACElementType.ROLE_ASSIGNMENT, OperationType.UPDATE)
86+
87+
@classmethod
88+
@override
89+
def permission_scope(cls) -> RBACElementType:
90+
return RBACElementType.USER
91+
92+
93+
@dataclass
94+
class RejectRoleInvitationAction(BaseRBACAction):
95+
"""Invitee rejects a role invitation."""
96+
97+
invitation_id: UUID
98+
99+
@classmethod
100+
@override
101+
def action_name(cls) -> RBACActionName:
102+
return RBACActionName.UPDATE
103+
104+
@classmethod
105+
@override
106+
def required_permission(cls) -> RBACRequiredPermission:
107+
return RBACRequiredPermission(RBACElementType.ROLE_ASSIGNMENT, OperationType.UPDATE)
108+
109+
@classmethod
110+
@override
111+
def permission_scope(cls) -> RBACElementType:
112+
return RBACElementType.USER
113+
114+
115+
@dataclass
116+
class CancelRoleInvitationAction(BaseRBACAction):
117+
"""Project admin cancels a role invitation."""
118+
119+
invitation_id: UUID
120+
121+
@classmethod
122+
@override
123+
def action_name(cls) -> RBACActionName:
124+
return RBACActionName.SOFT_DELETE
125+
126+
@classmethod
127+
@override
128+
def required_permission(cls) -> RBACRequiredPermission:
129+
return RBACRequiredPermission(RBACElementType.ROLE_ASSIGNMENT, OperationType.SOFT_DELETE)
130+
131+
@classmethod
132+
@override
133+
def permission_scope(cls) -> RBACElementType:
134+
return RBACElementType.PROJECT

src/ai/backend/manager/api/gql/rbac/types/permission.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ async def scope(
188188
| RBACElementType.MODEL_CARD
189189
| RBACElementType.PROJECT_ADMIN_PAGE
190190
| RBACElementType.DOMAIN_ADMIN_PAGE
191+
| RBACElementType.ROLE_ASSIGNMENT
191192
):
192193
return None
193194

src/ai/backend/manager/data/role_invitation/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)