feat: права расширены
This commit is contained in:
@@ -23,7 +23,7 @@ class Workspace(TimestampedModel):
|
|||||||
class WorkspaceUserStatus(str, enum.Enum):
|
class WorkspaceUserStatus(str, enum.Enum):
|
||||||
ACTIVE = 'active'
|
ACTIVE = 'active'
|
||||||
INVITED = 'invited'
|
INVITED = 'invited'
|
||||||
REMOVED = 'removed'
|
BLOCKED = 'blocked'
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceUser(TimestampedModel):
|
class WorkspaceUser(TimestampedModel):
|
||||||
@@ -77,17 +77,29 @@ class WorkspaceInvite(TimestampedModel):
|
|||||||
|
|
||||||
|
|
||||||
class PermissionKey(enum.StrEnum):
|
class PermissionKey(enum.StrEnum):
|
||||||
|
ADMIN_FULL = 'admin_full'
|
||||||
|
|
||||||
PROJECTS_READ = 'projects_read'
|
PROJECTS_READ = 'projects_read'
|
||||||
PROJECTS_WRITE = 'projects_write'
|
PROJECTS_WRITE = 'projects_write'
|
||||||
|
|
||||||
|
CREATIVES_READ = 'creatives_read'
|
||||||
|
CREATIVES_WRITE = 'creatives_write'
|
||||||
|
|
||||||
PLACEMENTS_READ = 'placements_read'
|
PLACEMENTS_READ = 'placements_read'
|
||||||
PLACEMENTS_WRITE = 'placements_write'
|
PLACEMENTS_WRITE = 'placements_write'
|
||||||
|
|
||||||
ANALYTICS_READ = 'analytics_read'
|
ANALYTICS_READ = 'analytics_read'
|
||||||
ADMIN_FULL = 'admin_full'
|
ANALYTICS_WITHOUT_CLICKS = 'analytics_without_clicks'
|
||||||
|
|
||||||
|
CHANNELS_READ = 'channels_read'
|
||||||
|
CHANNELS_WRITE = 'channels_write'
|
||||||
|
|
||||||
|
|
||||||
class PermissionScopeType(enum.StrEnum):
|
class PermissionScopeType(enum.StrEnum):
|
||||||
WORKSPACE = 'workspace'
|
|
||||||
PROJECT = 'project'
|
PROJECT = 'project'
|
||||||
|
CREATIVE = 'creative'
|
||||||
|
PLACEMENT = 'placement'
|
||||||
|
CHANNEL = 'channel'
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceUserPermission(TimestampedModel):
|
class WorkspaceUserPermission(TimestampedModel):
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ class WorkspacePermissions:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_membership(cls, membership: WorkspaceUser) -> WorkspacePermissions:
|
def from_membership(cls, membership: WorkspaceUser) -> WorkspacePermissions:
|
||||||
global_permissions = {
|
global_permissions = {permission.permission for permission in getattr(membership, 'permissions', []) or []}
|
||||||
permission.permission
|
|
||||||
for permission in getattr(membership, 'permissions', []) or []
|
|
||||||
}
|
|
||||||
|
|
||||||
scoped_permissions: dict[tuple[PermissionKey, PermissionScopeType], set[UUID]] = {}
|
scoped_permissions: dict[tuple[PermissionKey, PermissionScopeType], set[UUID]] = {}
|
||||||
for scope in getattr(membership, 'permission_scopes', []) or []:
|
for scope in getattr(membership, 'permission_scopes', []) or []:
|
||||||
@@ -46,6 +43,42 @@ class WorkspacePermissions:
|
|||||||
|
|
||||||
return allowed
|
return allowed
|
||||||
|
|
||||||
|
def allowed_creative_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
if self.has_global(permission):
|
||||||
|
return None
|
||||||
|
|
||||||
|
allowed: set[UUID] = set()
|
||||||
|
for key in (permission, PermissionKey.ADMIN_FULL):
|
||||||
|
creative_scope = self.scoped_permissions.get((key, PermissionScopeType.CREATIVE))
|
||||||
|
if creative_scope:
|
||||||
|
allowed.update(creative_scope)
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
|
||||||
|
def allowed_placement_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
if self.has_global(permission):
|
||||||
|
return None
|
||||||
|
|
||||||
|
allowed: set[UUID] = set()
|
||||||
|
for key in (permission, PermissionKey.ADMIN_FULL):
|
||||||
|
placement_scope = self.scoped_permissions.get((key, PermissionScopeType.PLACEMENT))
|
||||||
|
if placement_scope:
|
||||||
|
allowed.update(placement_scope)
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
|
||||||
|
def allowed_channel_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
if self.has_global(permission):
|
||||||
|
return None
|
||||||
|
|
||||||
|
allowed: set[UUID] = set()
|
||||||
|
for key in (permission, PermissionKey.ADMIN_FULL):
|
||||||
|
channel_scope = self.scoped_permissions.get((key, PermissionScopeType.CHANNEL))
|
||||||
|
if channel_scope:
|
||||||
|
allowed.update(channel_scope)
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
|
||||||
def has_permission(
|
def has_permission(
|
||||||
self,
|
self,
|
||||||
permission: PermissionKey,
|
permission: PermissionKey,
|
||||||
@@ -56,8 +89,18 @@ class WorkspacePermissions:
|
|||||||
if self.has_global(permission):
|
if self.has_global(permission):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if scope_type == PermissionScopeType.PROJECT and scope_id is not None:
|
if scope_type is not None and scope_id is not None:
|
||||||
allowed = self.allowed_project_ids(permission)
|
if scope_type == PermissionScopeType.PROJECT:
|
||||||
|
allowed = self.allowed_project_ids(permission)
|
||||||
|
elif scope_type == PermissionScopeType.CREATIVE:
|
||||||
|
allowed = self.allowed_creative_ids(permission)
|
||||||
|
elif scope_type == PermissionScopeType.PLACEMENT:
|
||||||
|
allowed = self.allowed_placement_ids(permission)
|
||||||
|
elif scope_type == PermissionScopeType.CHANNEL:
|
||||||
|
allowed = self.allowed_channel_ids(permission)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
if allowed is None:
|
if allowed is None:
|
||||||
return True
|
return True
|
||||||
return scope_id in allowed
|
return scope_id in allowed
|
||||||
@@ -80,6 +123,15 @@ class WorkspacePermissionContext:
|
|||||||
def allowed_project_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
def allowed_project_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
return self.permissions.allowed_project_ids(permission)
|
return self.permissions.allowed_project_ids(permission)
|
||||||
|
|
||||||
|
def allowed_creative_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
return self.permissions.allowed_creative_ids(permission)
|
||||||
|
|
||||||
|
def allowed_placement_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
return self.permissions.allowed_placement_ids(permission)
|
||||||
|
|
||||||
|
def allowed_channel_ids(self, permission: PermissionKey) -> set[UUID] | None:
|
||||||
|
return self.permissions.allowed_channel_ids(permission)
|
||||||
|
|
||||||
def ensure_project_permission(self, permission: PermissionKey, project_id: UUID) -> None:
|
def ensure_project_permission(self, permission: PermissionKey, project_id: UUID) -> None:
|
||||||
if not self.permissions.has_permission(
|
if not self.permissions.has_permission(
|
||||||
permission,
|
permission,
|
||||||
@@ -88,6 +140,30 @@ class WorkspacePermissionContext:
|
|||||||
):
|
):
|
||||||
raise WorkspaceAccessDenied(self.workspace.id)
|
raise WorkspaceAccessDenied(self.workspace.id)
|
||||||
|
|
||||||
|
def ensure_creative_permission(self, permission: PermissionKey, creative_id: UUID) -> None:
|
||||||
|
if not self.permissions.has_permission(
|
||||||
|
permission,
|
||||||
|
scope_type=PermissionScopeType.CREATIVE,
|
||||||
|
scope_id=creative_id,
|
||||||
|
):
|
||||||
|
raise WorkspaceAccessDenied(self.workspace.id)
|
||||||
|
|
||||||
|
def ensure_placement_permission(self, permission: PermissionKey, placement_id: UUID) -> None:
|
||||||
|
if not self.permissions.has_permission(
|
||||||
|
permission,
|
||||||
|
scope_type=PermissionScopeType.PLACEMENT,
|
||||||
|
scope_id=placement_id,
|
||||||
|
):
|
||||||
|
raise WorkspaceAccessDenied(self.workspace.id)
|
||||||
|
|
||||||
|
def ensure_channel_permission(self, permission: PermissionKey, channel_id: UUID) -> None:
|
||||||
|
if not self.permissions.has_permission(
|
||||||
|
permission,
|
||||||
|
scope_type=PermissionScopeType.CHANNEL,
|
||||||
|
scope_id=channel_id,
|
||||||
|
):
|
||||||
|
raise WorkspaceAccessDenied(self.workspace.id)
|
||||||
|
|
||||||
|
|
||||||
def build_workspace_permission_context(membership: WorkspaceUser) -> WorkspacePermissionContext:
|
def build_workspace_permission_context(membership: WorkspaceUser) -> WorkspacePermissionContext:
|
||||||
if membership.workspace is None:
|
if membership.workspace is None:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async def create_workspace_invite(
|
|||||||
raise domain.UserByUsernameNotFound(username)
|
raise domain.UserByUsernameNotFound(username)
|
||||||
|
|
||||||
existing_membership = await self.database.get_workspace_membership(workspace_id, invited_user.id)
|
existing_membership = await self.database.get_workspace_membership(workspace_id, invited_user.id)
|
||||||
if existing_membership and existing_membership.status != domain.WorkspaceUserStatus.REMOVED:
|
if existing_membership and existing_membership.status != domain.WorkspaceUserStatus.BLOCKED:
|
||||||
raise domain.WorkspaceMemberAlreadyExists()
|
raise domain.WorkspaceMemberAlreadyExists()
|
||||||
|
|
||||||
existing_invite = await self.database.get_workspace_invite_by_user(workspace_id, invited_user.id)
|
existing_invite = await self.database.get_workspace_invite_by_user(workspace_id, invited_user.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user