Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/10335.enhance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unify ScopeType/EntityType to RBACElementType in permission controller repository, service, and their direct GQL/REST callers
21 changes: 11 additions & 10 deletions src/ai/backend/manager/api/adapters/rbac.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,9 +690,9 @@ async def create_permission(self, input: CreatePermissionInputDTO) -> Permission
creator: Creator[PermissionRow] = Creator(
spec=PermissionCreatorSpec(
role_id=input.role_id,
scope_type=RBACElementType(input.scope_type).to_scope_type(),
scope_type=RBACElementType(input.scope_type),
scope_id=input.scope_id,
entity_type=RBACElementType(input.entity_type).to_entity_type(),
entity_type=RBACElementType(input.entity_type),
operation=InternalOperationType(input.operation),
)
)
Expand All @@ -707,7 +707,7 @@ async def update_permission(self, input: UpdatePermissionInputDTO) -> Permission
"""Update an existing scoped permission."""
spec = PermissionUpdaterSpec(
scope_type=(
OptionalState.update(RBACElementType(input.scope_type).to_scope_type())
OptionalState.update(RBACElementType(input.scope_type))
if input.scope_type is not None
else OptionalState.nop()
),
Expand All @@ -717,7 +717,7 @@ async def update_permission(self, input: UpdatePermissionInputDTO) -> Permission
else OptionalState.nop()
),
entity_type=(
OptionalState.update(RBACElementType(input.entity_type).to_entity_type())
OptionalState.update(RBACElementType(input.entity_type))
if input.entity_type is not None
else OptionalState.nop()
),
Expand Down Expand Up @@ -878,11 +878,13 @@ def _convert_permission_filter(self, f: PermissionFilterDTO) -> list[QueryCondit
if f.role_id is not None:
conditions.append(ScopedPermissionConditions.by_role_id(f.role_id))
if f.scope_type is not None:
scope_type = RBACElementType(f.scope_type).to_scope_type()
conditions.append(ScopedPermissionConditions.by_scope_type(scope_type))
conditions.append(
ScopedPermissionConditions.by_scope_type(RBACElementType(f.scope_type))
)
if f.entity_type is not None:
entity_type = RBACElementType(f.entity_type).to_entity_type()
conditions.append(ScopedPermissionConditions.by_entity_type(entity_type))
conditions.append(
ScopedPermissionConditions.by_entity_type(RBACElementType(f.entity_type))
)
if f.AND:
for sub in f.AND:
conditions.extend(self._convert_permission_filter(sub))
Expand Down Expand Up @@ -1112,8 +1114,7 @@ def _convert_assignment_orders(orders: list[RoleAssignmentOrderByDTO]) -> list[Q
def _convert_entity_filter(self, f: EntityFilterDTO) -> list[QueryCondition]:
conditions: list[QueryCondition] = []
if f.entity_type is not None:
entity_type = RBACElementType(f.entity_type).to_entity_type()
conditions.append(EntityScopeConditions.by_entity_type(entity_type))
conditions.append(EntityScopeConditions.by_entity_type(RBACElementType(f.entity_type)))
if f.entity_id is not None:
condition = self.convert_string_filter(
f.entity_id,
Expand Down
10 changes: 5 additions & 5 deletions src/ai/backend/manager/api/rest/rbac/entity_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

from ai.backend.common.data.permission.types import EntityType, ScopeType
from ai.backend.common.data.permission.types import RBACElementType
from ai.backend.common.dto.manager.rbac.request import (
SearchEntitiesRequest,
)
Expand All @@ -26,17 +26,17 @@ class EntityAdapter(BaseFilterAdapter):

def build_querier(
self,
scope_type: ScopeType,
scope_type: RBACElementType,
scope_id: str,
entity_type: EntityType,
entity_type: RBACElementType,
request: SearchEntitiesRequest,
) -> BatchQuerier:
"""Build a BatchQuerier for entity search.

Args:
scope_type: The scope type to search within
scope_type: The RBAC element type of scope to search within
scope_id: The scope ID to search within
entity_type: The type of entity to search
entity_type: The RBAC element type of entity to search
request: The search request containing pagination info

Returns:
Expand Down
28 changes: 23 additions & 5 deletions src/ai/backend/manager/api/rest/rbac/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from http import HTTPStatus

from ai.backend.common.api_handlers import APIResponse, BodyParam, PathParam
from ai.backend.common.data.permission.types import (
EntityType,
ScopeType,
)
from ai.backend.common.dto.manager.rbac import (
AssignRoleRequest,
AssignRoleResponse,
Expand Down Expand Up @@ -44,6 +48,7 @@
SearchEntitiesResponse,
SearchScopesResponse,
)
from ai.backend.common.exception import RBACTypeConversionError
from ai.backend.manager.data.permission.role import UserRoleAssignmentInput, UserRoleRevocationInput
from ai.backend.manager.dto.context import UserContext
from ai.backend.manager.errors.permission import NotEnoughPermission
Expand Down Expand Up @@ -298,7 +303,13 @@ async def get_scope_types(
action_result = await self._permission_controller.get_scope_types.wait_for_complete(
GetScopeTypesAction()
)
resp = GetScopeTypesResponse(items=action_result.scope_types)
scope_types: list[ScopeType] = []
for et in action_result.element_types:
try:
scope_types.append(et.to_scope_type())
except RBACTypeConversionError:
pass
Comment thread
fregataa marked this conversation as resolved.
Comment thread
fregataa marked this conversation as resolved.
resp = GetScopeTypesResponse(items=scope_types)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=resp)

async def search_scopes(
Expand All @@ -312,8 +323,9 @@ async def search_scopes(
raise NotEnoughPermission("Only superadmin can search scopes.")

scope_type = path.parsed.scope_type
element_type = scope_type.to_element()
querier = self._scope_adapter.build_querier(scope_type, body.parsed)
action = SearchScopesAction(scope_type=scope_type, querier=querier)
action = SearchScopesAction(element_type=element_type, querier=querier)
action_result = await self._permission_controller.search_scopes.wait_for_complete(action)
resp = SearchScopesResponse(
items=[self._scope_adapter.convert_to_dto(item) for item in action_result.result.items],
Expand All @@ -338,7 +350,13 @@ async def get_entity_types(
action_result = await self._permission_controller.get_entity_types.wait_for_complete(
GetEntityTypesAction()
)
resp = GetEntityTypesResponse(items=action_result.entity_types)
entity_types: list[EntityType] = []
for et in action_result.element_types:
try:
entity_types.append(et.to_entity_type())
except RBACTypeConversionError:
pass
Comment thread
fregataa marked this conversation as resolved.
resp = GetEntityTypesResponse(items=entity_types)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=resp)

async def search_entities(
Expand All @@ -352,9 +370,9 @@ async def search_entities(
raise NotEnoughPermission("Only superadmin can search entities.")

querier = self._entity_adapter.build_querier(
scope_type=path.parsed.scope_type,
scope_type=path.parsed.scope_type.to_element(),
scope_id=path.parsed.scope_id,
entity_type=path.parsed.entity_type,
entity_type=path.parsed.entity_type.to_element(),
request=body.parsed,
)
action = SearchEntitiesAction(querier=querier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def to_create_object_permission_action(
creator = Creator(
spec=ObjectPermissionCreatorSpec(
role_id=request.role_id,
entity_type=request.entity_type,
entity_type=request.entity_type.to_element(),
entity_id=request.entity_id,
Comment thread
fregataa marked this conversation as resolved.
operation=request.operation,
status=request.status,
Expand Down
4 changes: 2 additions & 2 deletions src/ai/backend/manager/api/rest/rbac/permission_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def to_create_permission_action(request: CreatePermissionRequest) -> CreatePermi
creator = Creator(
spec=PermissionCreatorSpec(
role_id=request.role_id,
scope_type=request.scope_type,
scope_type=request.scope_type.to_element(),
scope_id=request.scope_id,
Comment thread
fregataa marked this conversation as resolved.
entity_type=request.entity_type,
entity_type=request.entity_type.to_element(),
operation=request.operation,
)
)
Expand Down
7 changes: 0 additions & 7 deletions src/ai/backend/manager/api/rest/rbac/scope_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ class ScopeAdapter(BaseFilterAdapter):
def build_querier(self, scope_type: ScopeType, request: SearchScopesRequest) -> BatchQuerier:
"""Build a BatchQuerier based on scope type."""
match scope_type:
case ScopeType.GLOBAL:
return self._build_global_scope_querier(request)
case ScopeType.DOMAIN:
return self._build_domain_scope_querier(request)
case ScopeType.PROJECT:
Expand Down Expand Up @@ -78,11 +76,6 @@ def _build_user_scope_querier(self, request: SearchScopesRequest) -> BatchQuerie

return BatchQuerier(conditions=conditions, orders=orders, pagination=pagination)

def _build_global_scope_querier(self, request: SearchScopesRequest) -> BatchQuerier:
"""Build a BatchQuerier for global scope (no filtering needed)."""
pagination = OffsetPagination(limit=request.limit, offset=request.offset)
return BatchQuerier(conditions=[], orders=[], pagination=pagination)

def _convert_domain_filter(self, filter: ScopeFilter) -> list[QueryCondition]:
"""Convert scope filter to domain query conditions."""
conditions: list[QueryCondition] = []
Expand Down
25 changes: 13 additions & 12 deletions src/ai/backend/manager/models/rbac_models/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sqlalchemy as sa

from ai.backend.common.data.filter_specs import StringMatchSpec
from ai.backend.common.data.permission.types import RBACElementType
from ai.backend.manager.data.permission.id import ObjectId
from ai.backend.manager.data.permission.status import RoleStatus
from ai.backend.manager.data.permission.types import (
Expand Down Expand Up @@ -175,14 +176,14 @@ def inner() -> sa.sql.expression.ColumnElement[bool]:
return inner

@staticmethod
def by_has_permission_for(entity_type: EntityType) -> QueryCondition:
def by_has_permission_for(element_type: RBACElementType) -> QueryCondition:
"""Filter roles having permission for entity type.

Requires JOIN with ObjectPermissionRow.
"""

def inner() -> sa.sql.expression.ColumnElement[bool]:
return ObjectPermissionRow.entity_type == entity_type
return ObjectPermissionRow.entity_type == element_type.to_entity_type()

return inner

Expand Down Expand Up @@ -641,9 +642,9 @@ class EntityScopeConditions:
"""Query conditions for entity scope search."""

@staticmethod
def by_scope_type(scope_type: ScopeType) -> QueryCondition:
def by_scope_type(element_type: RBACElementType) -> QueryCondition:
def inner() -> sa.sql.expression.ColumnElement[bool]:
return AssociationScopesEntitiesRow.scope_type == scope_type
return AssociationScopesEntitiesRow.scope_type == element_type.to_scope_type()

return inner

Expand All @@ -655,9 +656,9 @@ def inner() -> sa.sql.expression.ColumnElement[bool]:
return inner

@staticmethod
def by_entity_type(entity_type: EntityType) -> QueryCondition:
def by_entity_type(element_type: RBACElementType) -> QueryCondition:
def inner() -> sa.sql.expression.ColumnElement[bool]:
return AssociationScopesEntitiesRow.entity_type == entity_type
return AssociationScopesEntitiesRow.entity_type == element_type.to_entity_type()

return inner

Expand Down Expand Up @@ -791,9 +792,9 @@ class ScopedPermissionConditions:
"""Query conditions for scoped permissions."""

@staticmethod
def by_entity_type(entity_type: EntityType) -> QueryCondition:
def by_entity_type(element_type: RBACElementType) -> QueryCondition:
def inner() -> sa.sql.expression.ColumnElement[bool]:
return PermissionRow.entity_type == entity_type
return PermissionRow.entity_type == element_type.to_entity_type()

return inner

Expand Down Expand Up @@ -832,9 +833,9 @@ def inner() -> sa.sql.expression.ColumnElement[bool]:
return inner

@staticmethod
def by_scope_type(scope_type: ScopeType) -> QueryCondition:
def by_scope_type(element_type: RBACElementType) -> QueryCondition:
def inner() -> sa.sql.expression.ColumnElement[bool]:
return PermissionRow.scope_type == scope_type
return PermissionRow.scope_type == element_type.to_scope_type()

return inner

Expand Down Expand Up @@ -864,9 +865,9 @@ def inner() -> sa.sql.expression.ColumnElement[bool]:
return inner

@staticmethod
def by_entity_type(entity_type: EntityType) -> QueryCondition:
def by_entity_type(element_type: RBACElementType) -> QueryCondition:
def inner() -> sa.sql.expression.ColumnElement[bool]:
return ObjectPermissionRow.entity_type == entity_type
return ObjectPermissionRow.entity_type == element_type.to_entity_type()

return inner

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
from dataclasses import dataclass
from typing import override

from ai.backend.common.data.permission.types import RBACElementType
from ai.backend.manager.data.permission.id import ObjectId, ScopeId
from ai.backend.manager.data.permission.status import PermissionStatus, RoleStatus
from ai.backend.manager.data.permission.types import (
EntityType,
OperationType,
RoleSource,
ScopeType,
)
from ai.backend.manager.errors.permission import RoleAlreadyAssigned
from ai.backend.manager.errors.repository import UniqueConstraintViolationError
Expand Down Expand Up @@ -56,18 +55,18 @@ class PermissionCreatorSpec(CreatorSpec[PermissionRow]):
"""CreatorSpec for permissions."""

role_id: uuid.UUID
scope_type: ScopeType
scope_type: RBACElementType
scope_id: str
entity_type: EntityType
entity_type: RBACElementType
Comment thread
fregataa marked this conversation as resolved.
operation: OperationType

@override
def build_row(self) -> PermissionRow:
return PermissionRow(
role_id=self.role_id,
scope_type=self.scope_type,
scope_type=self.scope_type.to_scope_type(),
scope_id=self.scope_id,
entity_type=self.entity_type,
entity_type=self.entity_type.to_entity_type(),
Comment thread
fregataa marked this conversation as resolved.
operation=self.operation,
)

Expand All @@ -77,7 +76,7 @@ class ObjectPermissionCreatorSpec(CreatorSpec[ObjectPermissionRow]):
"""CreatorSpec for object permissions."""

role_id: uuid.UUID
entity_type: EntityType
entity_type: RBACElementType
entity_id: str
operation: OperationType
status: PermissionStatus = PermissionStatus.ACTIVE
Comment thread
fregataa marked this conversation as resolved.
Expand All @@ -86,7 +85,7 @@ class ObjectPermissionCreatorSpec(CreatorSpec[ObjectPermissionRow]):
def build_row(self) -> ObjectPermissionRow:
return ObjectPermissionRow(
role_id=self.role_id,
entity_type=self.entity_type,
entity_type=self.entity_type.to_entity_type(),
entity_id=self.entity_id,
operation=self.operation,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sqlalchemy.orm import contains_eager, selectinload

from ai.backend.common.data.permission.types import (
RBACElementType,
RelationType,
)
from ai.backend.logging.utils import BraceStyleAdapter
Expand Down Expand Up @@ -338,9 +339,9 @@ async def update_role_permissions(
perm_creator = Creator(
spec=PermissionCreatorSpec(
role_id=input_data.role_id,
scope_type=scoped_perm_input.scope_type,
scope_type=RBACElementType(scoped_perm_input.scope_type.value),
scope_id=scoped_perm_input.scope_id,
entity_type=scoped_perm_input.entity_type,
entity_type=RBACElementType(scoped_perm_input.entity_type.value),
operation=scoped_perm_input.operation,
Comment thread
fregataa marked this conversation as resolved.
)
)
Expand All @@ -356,7 +357,7 @@ async def update_role_permissions(
obj_perm_creator = Creator(
spec=ObjectPermissionCreatorSpec(
role_id=input_data.role_id,
entity_type=obj_perm_input.entity_type,
entity_type=RBACElementType(obj_perm_input.entity_type.value),
entity_id=obj_perm_input.entity_id,
operation=obj_perm_input.operation,
status=obj_perm_input.status,
Expand Down
Loading
Loading