diff --git a/changes/10029.feature.md b/changes/10029.feature.md new file mode 100644 index 00000000000..b857589fa0e --- /dev/null +++ b/changes/10029.feature.md @@ -0,0 +1 @@ +Apply RBAC validators to project (group) action processors for proper permission enforcement \ No newline at end of file diff --git a/src/ai/backend/manager/services/group/actions/base.py b/src/ai/backend/manager/services/group/actions/base.py index 2ab78041d05..595bd815096 100644 --- a/src/ai/backend/manager/services/group/actions/base.py +++ b/src/ai/backend/manager/services/group/actions/base.py @@ -41,3 +41,29 @@ def field_data(self) -> FieldData | None: class GroupSingleEntityActionResult(BaseSingleEntityActionResult): pass + + +class ProjectScopeAction(BaseScopeAction): + @override + @classmethod + def entity_type(cls) -> EntityType: + return EntityType.PROJECT + + +class ProjectScopeActionResult(BaseScopeActionResult): + pass + + +class ProjectSingleEntityAction(BaseSingleEntityAction): + @override + @classmethod + def entity_type(cls) -> EntityType: + return EntityType.PROJECT + + @override + def field_data(self) -> FieldData | None: + return None + + +class ProjectSingleEntityActionResult(BaseSingleEntityActionResult): + pass diff --git a/src/ai/backend/manager/services/group/actions/search_projects.py b/src/ai/backend/manager/services/group/actions/search_projects.py index 4bc438ffd53..ced71d19ca3 100644 --- a/src/ai/backend/manager/services/group/actions/search_projects.py +++ b/src/ai/backend/manager/services/group/actions/search_projects.py @@ -18,10 +18,10 @@ ) from ai.backend.manager.services.group.actions.base import ( GroupAction, - GroupScopeAction, - GroupScopeActionResult, - GroupSingleEntityAction, - GroupSingleEntityActionResult, + ProjectScopeAction, + ProjectScopeActionResult, + ProjectSingleEntityAction, + ProjectSingleEntityActionResult, ) @@ -42,7 +42,7 @@ def operation_type(cls) -> ActionOperationType: @dataclass -class SearchProjectsByDomainAction(GroupScopeAction): +class SearchProjectsByDomainAction(ProjectScopeAction): """Search projects within a domain.""" scope: DomainProjectSearchScope @@ -67,7 +67,7 @@ def target_element(self) -> RBACElementRef: @dataclass -class SearchProjectsByUserAction(GroupScopeAction): +class SearchProjectsByUserAction(ProjectScopeAction): """Search projects a user is member of.""" scope: UserProjectSearchScope @@ -92,7 +92,7 @@ def target_element(self) -> RBACElementRef: @dataclass -class GetProjectAction(GroupSingleEntityAction): +class GetProjectAction(ProjectSingleEntityAction): """Get a single project by UUID.""" project_id: UUID @@ -129,7 +129,7 @@ def entity_id(self) -> str | None: @dataclass -class ScopedSearchProjectsActionResult(GroupScopeActionResult): +class ScopedSearchProjectsActionResult(ProjectScopeActionResult): """Result from searching projects within a scope.""" items: list[GroupData] @@ -149,7 +149,7 @@ def scope_id(self) -> str: @dataclass -class GetProjectActionResult(GroupSingleEntityActionResult): +class GetProjectActionResult(ProjectSingleEntityActionResult): """Result from getting a single project.""" data: GroupData diff --git a/src/ai/backend/manager/services/group/processors.py b/src/ai/backend/manager/services/group/processors.py index 51c0e6c1719..73a02dddd4e 100644 --- a/src/ai/backend/manager/services/group/processors.py +++ b/src/ai/backend/manager/services/group/processors.py @@ -64,10 +64,20 @@ def __init__( action_monitors: list[ActionMonitor], validators: ActionValidators, ) -> None: - self.create_group = ScopeActionProcessor(group_service.create_group, action_monitors) - self.modify_group = SingleEntityActionProcessor(group_service.modify_group, action_monitors) - self.delete_group = SingleEntityActionProcessor(group_service.delete_group, action_monitors) - self.purge_group = SingleEntityActionProcessor(group_service.purge_group, action_monitors) + rbac_scope_validators = [validators.rbac.scope] + rbac_single_entity_validators = [validators.rbac.single_entity] + self.create_group = ScopeActionProcessor( + group_service.create_group, action_monitors, validators=rbac_scope_validators + ) + self.modify_group = SingleEntityActionProcessor( + group_service.modify_group, action_monitors, validators=rbac_single_entity_validators + ) + self.delete_group = SingleEntityActionProcessor( + group_service.delete_group, action_monitors, validators=rbac_single_entity_validators + ) + self.purge_group = SingleEntityActionProcessor( + group_service.purge_group, action_monitors, validators=rbac_single_entity_validators + ) self.usage_per_month = ActionProcessor(group_service.usage_per_month, action_monitors) self.usage_per_period = ActionProcessor(group_service.usage_per_period, action_monitors) self.search_projects = ActionProcessor(group_service.search_projects, action_monitors) @@ -77,7 +87,9 @@ def __init__( self.search_projects_by_user = ScopeActionProcessor( group_service.search_projects_by_user, action_monitors ) - self.get_project = SingleEntityActionProcessor(group_service.get_project, action_monitors) + self.get_project = SingleEntityActionProcessor( + group_service.get_project, action_monitors, validators=rbac_single_entity_validators + ) @override def supported_actions(self) -> list[ActionSpec]: