Skip to content
Open
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/9630.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `ActionValidators` and `RBACValidators` dataclasses to inject RBAC validators through the dependency layer into processors
8 changes: 8 additions & 0 deletions src/ai/backend/manager/actions/validators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dataclasses import dataclass

from .rbac import RBACValidators


@dataclass
class ActionValidators:
rbac: RBACValidators
12 changes: 12 additions & 0 deletions src/ai/backend/manager/actions/validators/rbac/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dataclasses import dataclass

from ai.backend.manager.actions.validators.rbac.scope import ScopeActionRBACValidator
from ai.backend.manager.actions.validators.rbac.single_entity import (
SingleEntityActionRBACValidator,
)


@dataclass
class RBACValidators:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure define dataclass in __init__.py. I think init.py should only be used for importing things to make importing more convenient. What do others think? @jopemachine @HyeockJinKim

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to keep things like types.py in a separate file.

Copy link
Member Author

@fregataa fregataa Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types.py is not a good idea because usually types modules should be the one that other modules depend on, not the one that depends on other modules

scope: ScopeActionRBACValidator
single_entity: SingleEntityActionRBACValidator
13 changes: 13 additions & 0 deletions src/ai/backend/manager/dependencies/processing/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
from ai.backend.manager.actions.monitors.audit_log import AuditLogMonitor
from ai.backend.manager.actions.monitors.prometheus import PrometheusMonitor
from ai.backend.manager.actions.monitors.reporter import ReporterMonitor
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.actions.validators.rbac import RBACValidators
from ai.backend.manager.actions.validators.rbac.scope import ScopeActionRBACValidator
from ai.backend.manager.actions.validators.rbac.single_entity import (
SingleEntityActionRBACValidator,
)
from ai.backend.manager.agent_cache import AgentRPCCache
from ai.backend.manager.clients.agent.pool import AgentClientPool
from ai.backend.manager.clients.appproxy.client import AppProxyClientPool
Expand Down Expand Up @@ -240,13 +246,20 @@ async def compose(
registry_quota_service=setup_input.registry_quota_service,
)

permission_controller_repository = setup_input.repositories.permission_controller.repository
rbac_validators = RBACValidators(
scope=ScopeActionRBACValidator(permission_controller_repository),
single_entity=SingleEntityActionRBACValidator(permission_controller_repository),
)

processors = await stack.enter_dependency(
ProcessorsDependency(),
ProcessorsProviderInput(
service_args=service_args,
action_monitors=[reporter_monitor, prometheus_monitor, audit_log_monitor],
event_hub=setup_input.event_hub,
event_fetcher=setup_input.event_fetcher,
validators=ActionValidators(rbac=rbac_validators),
),
)

Expand Down
3 changes: 3 additions & 0 deletions src/ai/backend/manager/dependencies/processing/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ai.backend.common.events.fetcher import EventFetcher
from ai.backend.common.events.hub.hub import EventHub
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.processors import ProcessorArgs, Processors, ServiceArgs


Expand All @@ -19,6 +20,7 @@ class ProcessorsProviderInput:
action_monitors: list[ActionMonitor]
event_hub: EventHub
event_fetcher: EventFetcher
validators: ActionValidators


class ProcessorsDependency(NonMonitorableDependencyProvider[ProcessorsProviderInput, Processors]):
Expand All @@ -41,5 +43,6 @@ async def provide(self, setup_input: ProcessorsProviderInput) -> AsyncIterator[P
event_fetcher=setup_input.event_fetcher,
),
setup_input.action_monitors,
setup_input.validators,
)
yield processors
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/agent/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.agent.actions.get_total_resources import (
GetTotalResourcesAction,
GetTotalResourcesActionResult,
Expand Down Expand Up @@ -86,7 +87,12 @@ class AgentProcessors(AbstractProcessorPackage):
LoadContainerCountsAction, LoadContainerCountsActionResult
]

def __init__(self, service: AgentService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: AgentService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.sync_agent_registry = ActionProcessor(service.sync_agent_registry, action_monitors)
self.get_watcher_status = ActionProcessor(service.get_watcher_status, action_monitors)
self.watcher_agent_start = ActionProcessor(service.watcher_agent_start, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/app_config/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators

from .actions import (
DeleteDomainConfigAction,
Expand Down Expand Up @@ -43,7 +44,12 @@ class AppConfigProcessors(AbstractProcessorPackage):
# Merged config processor
get_merged_config: ActionProcessor[GetMergedAppConfigAction, GetMergedAppConfigActionResult]

def __init__(self, service: AppConfigService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: AppConfigService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
# Domain config processors
self.get_domain_config = ActionProcessor(service.get_domain_config, action_monitors)
self.upsert_domain_config = ActionProcessor(service.upsert_domain_config, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/artifact/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.artifact.actions.delegate_scan import (
DelegateScanArtifactsAction,
DelegateScanArtifactsActionResult,
Expand Down Expand Up @@ -81,7 +82,12 @@ class ArtifactProcessors(AbstractProcessorPackage):

delegate_scan: ActionProcessor[DelegateScanArtifactsAction, DelegateScanArtifactsActionResult]

def __init__(self, service: ArtifactService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: ArtifactService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
# TODO: Move scan action to ArtifactRegistryService
self.scan = ActionProcessor(service.scan, action_monitors)
self.get = ActionProcessor(service.get, action_monitors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.artifact_registry.actions.common.get_meta import (
GetArtifactRegistryMetaAction,
GetArtifactRegistryMetaActionResult,
Expand Down Expand Up @@ -129,7 +130,10 @@ class ArtifactRegistryProcessors(AbstractProcessorPackage):
]

def __init__(
self, service: ArtifactRegistryService, action_monitors: list[ActionMonitor]
self,
service: ArtifactRegistryService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.create_huggingface_registry = ActionProcessor(
service.create_huggingface_registry, action_monitors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.artifact_revision.actions.approve import (
ApproveArtifactRevisionAction,
ApproveArtifactRevisionActionResult,
Expand Down Expand Up @@ -91,7 +92,10 @@ class ArtifactRevisionProcessors(AbstractProcessorPackage):
]

def __init__(
self, service: ArtifactRevisionService, action_monitors: list[ActionMonitor]
self,
service: ArtifactRevisionService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.get = ActionProcessor(service.get, action_monitors)
self.get_readme = ActionProcessor(service.get_readme, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/audit_log/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.audit_log.actions.create import (
CreateAuditLogAction,
CreateAuditLogActionResult,
Expand All @@ -20,7 +21,12 @@ class AuditLogProcessors(AbstractProcessorPackage):
create: ActionProcessor[CreateAuditLogAction, CreateAuditLogActionResult]
search: ActionProcessor[SearchAuditLogsAction, SearchAuditLogsActionResult]

def __init__(self, service: AuditLogService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: AuditLogService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.create = ActionProcessor(service.create, action_monitors)
self.search = ActionProcessor(service.search, action_monitors)

Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/auth/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.auth.actions.authorize import (
AuthorizeAction,
AuthorizeActionResult,
Expand Down Expand Up @@ -63,7 +64,12 @@ class AuthProcessors(AbstractProcessorPackage):
]
resolve_user_scope: ActionProcessor[ResolveUserScopeAction, ResolveUserScopeResult]

def __init__(self, service: AuthService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: AuthService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.signout = ActionProcessor(service.signout, action_monitors)
self.update_full_name = ActionProcessor(service.update_full_name, action_monitors)
self.get_ssh_keypair = ActionProcessor(service.get_ssh_keypair, action_monitors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.container_registry.actions.clear_images import (
ClearImagesAction,
ClearImagesActionResult,
Expand Down Expand Up @@ -101,7 +102,10 @@ class ContainerRegistryProcessors(AbstractProcessorPackage):
]

def __init__(
self, service: ContainerRegistryService, action_monitors: list[ActionMonitor]
self,
service: ContainerRegistryService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.rescan_images = ActionProcessor(service.rescan_images, action_monitors)
self.clear_images = ActionProcessor(service.clear_images, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/deployment/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.deployment.actions.access_token.create_access_token import (
CreateAccessTokenAction,
CreateAccessTokenActionResult,
Expand Down Expand Up @@ -169,7 +170,12 @@ class DeploymentProcessors(AbstractProcessorPackage):
create_access_token: ActionProcessor[CreateAccessTokenAction, CreateAccessTokenActionResult]
search_access_tokens: ActionProcessor[SearchAccessTokensAction, SearchAccessTokensActionResult]

def __init__(self, service: DeploymentService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: DeploymentService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
# Deployment CRUD
self.create_deployment = ActionProcessor(service.create_deployment, action_monitors)
self.create_legacy_deployment = ActionProcessor(
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/domain/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators
from ai.backend.manager.services.domain.actions.create_domain import (
CreateDomainAction,
CreateDomainActionResult,
Expand Down Expand Up @@ -54,7 +55,12 @@ class DomainProcessors(AbstractProcessorPackage):
search_domains: ActionProcessor[SearchDomainsAction, SearchDomainsActionResult]
search_rg_domains: ActionProcessor[SearchRGDomainsAction, SearchRGDomainsActionResult]

def __init__(self, service: DomainService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: DomainService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.create_domain_node = ActionProcessor(service.create_domain_node, action_monitors)
self.modify_domain_node = ActionProcessor(service.modify_domain_node, action_monitors)
self.create_domain = ActionProcessor(service.create_domain, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/dotfile/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators

from .actions.check_group_membership import (
CheckGroupMembershipAction,
Expand Down Expand Up @@ -38,7 +39,12 @@ class DotfileProcessors(AbstractProcessorPackage):
UpdateBootstrapScriptAction, UpdateBootstrapScriptActionResult
]

def __init__(self, service: DotfileService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: DotfileService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.create = ActionProcessor(service.create_dotfile, action_monitors)
self.list_or_get = ActionProcessor(service.list_or_get_dotfiles, action_monitors)
self.update = ActionProcessor(service.update_dotfile, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/error_log/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators

from .actions import CreateErrorLogAction, CreateErrorLogActionResult
from .actions.list import ListErrorLogsAction, ListErrorLogsActionResult
Expand All @@ -23,7 +24,12 @@ class ErrorLogProcessors(AbstractProcessorPackage):
list_logs: ActionProcessor[ListErrorLogsAction, ListErrorLogsActionResult]
mark_cleared: ActionProcessor[MarkClearedErrorLogAction, MarkClearedErrorLogActionResult]

def __init__(self, service: ErrorLogService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: ErrorLogService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.create = ActionProcessor(service.create, action_monitors)
self.search = ActionProcessor(service.search, action_monitors)
self.list_logs = ActionProcessor(service.list_logs, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/etcd_config/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators

from .actions.delete_config import DeleteConfigAction, DeleteConfigActionResult
from .actions.get_config import GetConfigAction, GetConfigActionResult
Expand Down Expand Up @@ -32,7 +33,12 @@ class EtcdConfigProcessors(AbstractProcessorPackage):
set_config: ActionProcessor[SetConfigAction, SetConfigActionResult]
delete_config: ActionProcessor[DeleteConfigAction, DeleteConfigActionResult]

def __init__(self, service: EtcdConfigService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: EtcdConfigService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.get_resource_slots = ActionProcessor(service.get_resource_slots, action_monitors)
self.get_resource_metadata = ActionProcessor(service.get_resource_metadata, action_monitors)
self.get_vfolder_types = ActionProcessor(service.get_vfolder_types, action_monitors)
Expand Down
8 changes: 7 additions & 1 deletion src/ai/backend/manager/services/export/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ai.backend.manager.actions.monitors.monitor import ActionMonitor
from ai.backend.manager.actions.processor import ActionProcessor
from ai.backend.manager.actions.types import AbstractProcessorPackage, ActionSpec
from ai.backend.manager.actions.validators import ActionValidators

from .actions import (
ExportAuditLogsCSVAction,
Expand Down Expand Up @@ -46,7 +47,12 @@ class ExportProcessors(AbstractProcessorPackage):
export_keypairs_csv: ActionProcessor[ExportKeypairsCSVAction, ExportKeypairsCSVActionResult]
export_audit_logs_csv: ActionProcessor[ExportAuditLogsCSVAction, ExportAuditLogsCSVActionResult]

def __init__(self, service: ExportService, action_monitors: list[ActionMonitor]) -> None:
def __init__(
self,
service: ExportService,
action_monitors: list[ActionMonitor],
validators: ActionValidators,
) -> None:
self.list_reports = ActionProcessor(service.list_reports, action_monitors)
self.get_report = ActionProcessor(service.get_report, action_monitors)
self.export_users_csv = ActionProcessor(service.export_users_csv, action_monitors)
Expand Down
Loading
Loading