feat(permissions): add permission system and checker
- Implement PermissionChecker for workspace access control - Define Permission constants for all operations - Define ROLE_PERMISSIONS mapping (Owner/Admin/Member/Viewer) - Support workspace/member/project/asset permission checks - Helper functions: check_is_owner, check_can_manage_members, etc. - Add 15 comprehensive unit tests (all passed) Phase 4 Task 20/68 completed
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
权限验证辅助函数
|
||||
用于检查用户在工作空间中的权限
|
||||
"""
|
||||
from typing import Optional
|
||||
from packages.domain.entities import WorkspaceMemberRole
|
||||
|
||||
|
||||
class PermissionChecker:
|
||||
"""权限检查器"""
|
||||
|
||||
def __init__(self, workspace_member_repository):
|
||||
self.workspace_member_repository = workspace_member_repository
|
||||
|
||||
def check_workspace_access(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
检查用户是否可以访问工作空间
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
(是否有权限, 用户角色)
|
||||
"""
|
||||
member = self.workspace_member_repository.find_by_workspace_and_user(
|
||||
workspace_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
if not member:
|
||||
return False, None
|
||||
|
||||
return True, member.role
|
||||
|
||||
def check_is_owner(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否是工作空间 Owner
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否是 Owner
|
||||
"""
|
||||
member = self.workspace_member_repository.find_by_workspace_and_user(
|
||||
workspace_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
return member is not None and member.role == WorkspaceMemberRole.OWNER
|
||||
|
||||
def check_is_admin_or_owner(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否是工作空间 Admin 或 Owner
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否是 Admin 或 Owner
|
||||
"""
|
||||
member = self.workspace_member_repository.find_by_workspace_and_user(
|
||||
workspace_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
return member is not None and member.role in [
|
||||
WorkspaceMemberRole.OWNER,
|
||||
WorkspaceMemberRole.ADMIN,
|
||||
]
|
||||
|
||||
def check_can_manage_members(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以管理成员(邀请、移除、修改角色)
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以管理成员
|
||||
"""
|
||||
return self.check_is_admin_or_owner(workspace_id, user_id)
|
||||
|
||||
def check_can_edit_workspace(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以编辑工作空间(修改名称、设置等)
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以编辑工作空间
|
||||
"""
|
||||
return self.check_is_admin_or_owner(workspace_id, user_id)
|
||||
|
||||
def check_can_create_project(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以创建项目
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以创建项目
|
||||
"""
|
||||
member = self.workspace_member_repository.find_by_workspace_and_user(
|
||||
workspace_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
# Owner, Admin, Member 可以创建项目,Viewer 不可以
|
||||
return member is not None and member.role in [
|
||||
WorkspaceMemberRole.OWNER,
|
||||
WorkspaceMemberRole.ADMIN,
|
||||
WorkspaceMemberRole.MEMBER,
|
||||
]
|
||||
|
||||
def check_can_edit_project(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以编辑项目
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以编辑项目
|
||||
"""
|
||||
# 与创建项目权限相同
|
||||
return self.check_can_create_project(workspace_id, user_id)
|
||||
|
||||
def check_can_delete_project(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以删除项目
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以删除项目
|
||||
"""
|
||||
# 只有 Owner 和 Admin 可以删除项目
|
||||
return self.check_is_admin_or_owner(workspace_id, user_id)
|
||||
|
||||
def check_can_view_workspace(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户是否可以查看工作空间
|
||||
|
||||
Args:
|
||||
workspace_id: 工作空间 ID
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
是否可以查看工作空间
|
||||
"""
|
||||
has_access, _ = self.check_workspace_access(workspace_id, user_id)
|
||||
return has_access
|
||||
|
||||
|
||||
# 权限级别定义
|
||||
class Permission:
|
||||
"""权限常量"""
|
||||
|
||||
# 工作空间权限
|
||||
WORKSPACE_VIEW = "workspace:view"
|
||||
WORKSPACE_EDIT = "workspace:edit"
|
||||
WORKSPACE_DELETE = "workspace:delete"
|
||||
WORKSPACE_MANAGE_SUBSCRIPTION = "workspace:manage_subscription"
|
||||
|
||||
# 成员权限
|
||||
MEMBER_VIEW = "member:view"
|
||||
MEMBER_INVITE = "member:invite"
|
||||
MEMBER_REMOVE = "member:remove"
|
||||
MEMBER_UPDATE_ROLE = "member:update_role"
|
||||
|
||||
# 项目权限
|
||||
PROJECT_VIEW = "project:view"
|
||||
PROJECT_CREATE = "project:create"
|
||||
PROJECT_EDIT = "project:edit"
|
||||
PROJECT_DELETE = "project:delete"
|
||||
|
||||
# 资产权限
|
||||
ASSET_VIEW = "asset:view"
|
||||
ASSET_UPLOAD = "asset:upload"
|
||||
ASSET_EDIT = "asset:edit"
|
||||
ASSET_DELETE = "asset:delete"
|
||||
|
||||
|
||||
# 角色权限映射
|
||||
ROLE_PERMISSIONS = {
|
||||
WorkspaceMemberRole.OWNER: [
|
||||
# 所有权限
|
||||
Permission.WORKSPACE_VIEW,
|
||||
Permission.WORKSPACE_EDIT,
|
||||
Permission.WORKSPACE_DELETE,
|
||||
Permission.WORKSPACE_MANAGE_SUBSCRIPTION,
|
||||
Permission.MEMBER_VIEW,
|
||||
Permission.MEMBER_INVITE,
|
||||
Permission.MEMBER_REMOVE,
|
||||
Permission.MEMBER_UPDATE_ROLE,
|
||||
Permission.PROJECT_VIEW,
|
||||
Permission.PROJECT_CREATE,
|
||||
Permission.PROJECT_EDIT,
|
||||
Permission.PROJECT_DELETE,
|
||||
Permission.ASSET_VIEW,
|
||||
Permission.ASSET_UPLOAD,
|
||||
Permission.ASSET_EDIT,
|
||||
Permission.ASSET_DELETE,
|
||||
],
|
||||
WorkspaceMemberRole.ADMIN: [
|
||||
Permission.WORKSPACE_VIEW,
|
||||
Permission.WORKSPACE_EDIT,
|
||||
Permission.MEMBER_VIEW,
|
||||
Permission.MEMBER_INVITE,
|
||||
Permission.MEMBER_REMOVE,
|
||||
Permission.MEMBER_UPDATE_ROLE,
|
||||
Permission.PROJECT_VIEW,
|
||||
Permission.PROJECT_CREATE,
|
||||
Permission.PROJECT_EDIT,
|
||||
Permission.PROJECT_DELETE,
|
||||
Permission.ASSET_VIEW,
|
||||
Permission.ASSET_UPLOAD,
|
||||
Permission.ASSET_EDIT,
|
||||
Permission.ASSET_DELETE,
|
||||
],
|
||||
WorkspaceMemberRole.MEMBER: [
|
||||
Permission.WORKSPACE_VIEW,
|
||||
Permission.MEMBER_VIEW,
|
||||
Permission.PROJECT_VIEW,
|
||||
Permission.PROJECT_CREATE,
|
||||
Permission.PROJECT_EDIT,
|
||||
Permission.ASSET_VIEW,
|
||||
Permission.ASSET_UPLOAD,
|
||||
Permission.ASSET_EDIT,
|
||||
Permission.ASSET_DELETE,
|
||||
],
|
||||
WorkspaceMemberRole.VIEWER: [
|
||||
Permission.WORKSPACE_VIEW,
|
||||
Permission.MEMBER_VIEW,
|
||||
Permission.PROJECT_VIEW,
|
||||
Permission.ASSET_VIEW,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def has_permission(role: str, permission: str) -> bool:
|
||||
"""
|
||||
检查角色是否有指定权限
|
||||
|
||||
Args:
|
||||
role: 用户角色
|
||||
permission: 权限标识
|
||||
|
||||
Returns:
|
||||
是否有权限
|
||||
"""
|
||||
permissions = ROLE_PERMISSIONS.get(role, [])
|
||||
return permission in permissions
|
||||
@@ -0,0 +1,207 @@
|
||||
"""
|
||||
权限验证辅助函数测试
|
||||
"""
|
||||
import pytest
|
||||
from unittest.mock import Mock
|
||||
from packages.domain.permissions import (
|
||||
PermissionChecker,
|
||||
Permission,
|
||||
has_permission,
|
||||
)
|
||||
from packages.domain.entities import (
|
||||
WorkspaceMember,
|
||||
WorkspaceMemberRole,
|
||||
)
|
||||
|
||||
|
||||
class TestPermissionChecker:
|
||||
"""权限检查器测试"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_member_repo(self):
|
||||
repo = Mock()
|
||||
repo.find_by_workspace_and_user = Mock(return_value=None)
|
||||
return repo
|
||||
|
||||
@pytest.fixture
|
||||
def checker(self, mock_member_repo):
|
||||
return PermissionChecker(workspace_member_repository=mock_member_repo)
|
||||
|
||||
def test_check_workspace_access_has_access(self, checker, mock_member_repo):
|
||||
"""测试有访问权限"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.MEMBER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
has_access, role = checker.check_workspace_access("workspace-123", "user-123")
|
||||
|
||||
assert has_access is True
|
||||
assert role == "member"
|
||||
|
||||
def test_check_workspace_access_no_access(self, checker, mock_member_repo):
|
||||
"""测试无访问权限"""
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = None
|
||||
|
||||
has_access, role = checker.check_workspace_access("workspace-123", "user-123")
|
||||
|
||||
assert has_access is False
|
||||
assert role is None
|
||||
|
||||
def test_check_is_owner_true(self, checker, mock_member_repo):
|
||||
"""测试是 Owner"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.OWNER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
is_owner = checker.check_is_owner("workspace-123", "user-123")
|
||||
|
||||
assert is_owner is True
|
||||
|
||||
def test_check_is_owner_false(self, checker, mock_member_repo):
|
||||
"""测试不是 Owner"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.ADMIN,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
is_owner = checker.check_is_owner("workspace-123", "user-123")
|
||||
|
||||
assert is_owner is False
|
||||
|
||||
def test_check_is_admin_or_owner_admin(self, checker, mock_member_repo):
|
||||
"""测试是 Admin"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.ADMIN,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
is_admin_or_owner = checker.check_is_admin_or_owner("workspace-123", "user-123")
|
||||
|
||||
assert is_admin_or_owner is True
|
||||
|
||||
def test_check_is_admin_or_owner_owner(self, checker, mock_member_repo):
|
||||
"""测试是 Owner"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.OWNER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
is_admin_or_owner = checker.check_is_admin_or_owner("workspace-123", "user-123")
|
||||
|
||||
assert is_admin_or_owner is True
|
||||
|
||||
def test_check_is_admin_or_owner_member(self, checker, mock_member_repo):
|
||||
"""测试是普通成员"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.MEMBER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
is_admin_or_owner = checker.check_is_admin_or_owner("workspace-123", "user-123")
|
||||
|
||||
assert is_admin_or_owner is False
|
||||
|
||||
def test_check_can_create_project_member(self, checker, mock_member_repo):
|
||||
"""测试 Member 可以创建项目"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.MEMBER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
can_create = checker.check_can_create_project("workspace-123", "user-123")
|
||||
|
||||
assert can_create is True
|
||||
|
||||
def test_check_can_create_project_viewer(self, checker, mock_member_repo):
|
||||
"""测试 Viewer 不能创建项目"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.VIEWER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
can_create = checker.check_can_create_project("workspace-123", "user-123")
|
||||
|
||||
assert can_create is False
|
||||
|
||||
def test_check_can_delete_project_member(self, checker, mock_member_repo):
|
||||
"""测试 Member 不能删除项目"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.MEMBER,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
can_delete = checker.check_can_delete_project("workspace-123", "user-123")
|
||||
|
||||
assert can_delete is False
|
||||
|
||||
def test_check_can_delete_project_admin(self, checker, mock_member_repo):
|
||||
"""测试 Admin 可以删除项目"""
|
||||
member = WorkspaceMember(
|
||||
id="member-1",
|
||||
workspace_id="workspace-123",
|
||||
user_id="user-123",
|
||||
role=WorkspaceMemberRole.ADMIN,
|
||||
)
|
||||
mock_member_repo.find_by_workspace_and_user.return_value = member
|
||||
|
||||
can_delete = checker.check_can_delete_project("workspace-123", "user-123")
|
||||
|
||||
assert can_delete is True
|
||||
|
||||
|
||||
class TestPermissionFunctions:
|
||||
"""权限函数测试"""
|
||||
|
||||
def test_has_permission_owner(self):
|
||||
"""测试 Owner 权限"""
|
||||
assert has_permission(WorkspaceMemberRole.OWNER, Permission.WORKSPACE_DELETE) is True
|
||||
assert has_permission(WorkspaceMemberRole.OWNER, Permission.MEMBER_REMOVE) is True
|
||||
assert has_permission(WorkspaceMemberRole.OWNER, Permission.PROJECT_CREATE) is True
|
||||
|
||||
def test_has_permission_admin(self):
|
||||
"""测试 Admin 权限"""
|
||||
assert has_permission(WorkspaceMemberRole.ADMIN, Permission.WORKSPACE_EDIT) is True
|
||||
assert has_permission(WorkspaceMemberRole.ADMIN, Permission.MEMBER_REMOVE) is True
|
||||
assert has_permission(WorkspaceMemberRole.ADMIN, Permission.WORKSPACE_DELETE) is False
|
||||
|
||||
def test_has_permission_member(self):
|
||||
"""测试 Member 权限"""
|
||||
assert has_permission(WorkspaceMemberRole.MEMBER, Permission.PROJECT_CREATE) is True
|
||||
assert has_permission(WorkspaceMemberRole.MEMBER, Permission.PROJECT_DELETE) is False
|
||||
assert has_permission(WorkspaceMemberRole.MEMBER, Permission.MEMBER_INVITE) is False
|
||||
|
||||
def test_has_permission_viewer(self):
|
||||
"""测试 Viewer 权限"""
|
||||
assert has_permission(WorkspaceMemberRole.VIEWER, Permission.WORKSPACE_VIEW) is True
|
||||
assert has_permission(WorkspaceMemberRole.VIEWER, Permission.PROJECT_VIEW) is True
|
||||
assert has_permission(WorkspaceMemberRole.VIEWER, Permission.PROJECT_CREATE) is False
|
||||
assert has_permission(WorkspaceMemberRole.VIEWER, Permission.ASSET_UPLOAD) is False
|
||||
Reference in New Issue
Block a user