| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- """需求16 矩阵判定融合测试(authorization_service)。
- 覆盖:
- - OWN/ASSIGNED/OTHER 部门三分类矩阵判定
- - 矩阵与单文档 ACL 的 OR 叠加
- - 密级前置与状态前置不可被矩阵绕过
- - 无角色→默认角色→无默认则贡献为空
- - 显式角色停用后矩阵贡献为空且不回退默认
- - plan_allowed_actions 的矩阵操作位叠加(子方案过滤授权/挂载位)
- """
- from __future__ import annotations
- import pytest
- from dms.common.enums import RoleCode, SecurityLevel
- from dms.extensions import db
- from dms.models import Document, Organization, Role, RolePermission, User
- from dms.security.auth_context import AuthContext
- from dms.services.authorization_service import (
- evaluate_plan_access,
- plan_allowed_actions,
- )
- def _make_document(name: str, *, org: Organization, security: str = "SECRET",
- visibility: str = "ORGANIZATION", status: str = "PUBLISHED",
- document_type: str = "MAIN") -> Document:
- item = Document(
- document_name=name,
- summary=f"{name}摘要",
- document_type=document_type,
- document_status=status,
- security_level=security,
- visibility_type=visibility,
- organization_id=org.id,
- organization_name=org.org_name,
- tags=["矩阵测试"],
- original_file_name=f"{name}.docx",
- file_relative_path=f"original/MATRIX_{name}.docx",
- file_extension="docx",
- mime_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- file_size=128,
- file_hash="b" * 64,
- search_text=f"{name} 矩阵测试",
- created_by_name="系统管理员",
- updated_by_name="系统管理员",
- )
- db.session.add(item)
- db.session.flush()
- return item
- def _make_role(name: str, *, is_default: bool = False,
- status: str = "ENABLED", actor_id: int | None = None) -> Role:
- role = Role(
- role_name=name,
- sort_no=10,
- status=status,
- is_default=is_default,
- created_by=actor_id,
- updated_by=actor_id,
- )
- db.session.add(role)
- db.session.flush()
- return role
- def _matrix_row(role_id: int, level: str, scope: str, **flags) -> None:
- db.session.add(
- RolePermission(
- role_id=role_id,
- security_level=level,
- dept_scope=scope,
- **flags,
- )
- )
- def _user_context(user: User) -> AuthContext:
- return AuthContext(
- user_id=user.id,
- username=user.username,
- real_name=user.real_name,
- organization_id=user.organization_id,
- organization_name=user.organization_name,
- role_code=RoleCode(user.role_code),
- security_level=SecurityLevel(user.security_level),
- auth_version=user.auth_version,
- )
- @pytest.fixture()
- def matrix_env(b2_app, role_cleanup):
- """在 b2_app 基础上构造矩阵判定数据:机构、用户、文档。"""
- with b2_app.app_context():
- ops = db.session.scalar(
- db.select(Organization).where(Organization.org_code == "ORG_OPS")
- )
- comms = db.session.scalar(
- db.select(Organization).where(Organization.org_code == "ORG_COMMS")
- )
- user = db.session.scalar(
- db.select(User).where(User.username == "user")
- )
- assert ops and comms and user
- db.session.commit()
- ids = {"ops": ops.id, "comms": comms.id, "user": user.id}
- return b2_app, ids
- @pytest.mark.integration
- def test_own_scope_matrix_grants_view_when_acl_denies(matrix_env):
- app, ids = matrix_env
- with app.app_context():
- ops = db.session.get(Organization, ids["ops"])
- user = db.session.get(User, ids["user"])
- role = _make_role("作战部角色", actor_id=user.id)
- _matrix_row(
- role.id, "SECRET", "OWN", can_view=True, can_download=True
- )
- # ORGANIZATION 可见性且无匹配 ACL 行:纯矩阵放行
- doc = _make_document("矩阵本部门主案", org=ops, visibility="ORGANIZATION")
- db.session.commit()
- context = _user_context(user)
- access = evaluate_plan_access(doc, context)
- assert access.allowed is True
- assert access.can_download is True
- assert "VIEW" in access.matrix_actions
- assert "DOWNLOAD" in access.matrix_actions
- @pytest.mark.integration
- def test_other_scope_without_matrix_denies(matrix_env):
- app, ids = matrix_env
- with app.app_context():
- comms = db.session.get(Organization, ids["comms"])
- user = db.session.get(User, ids["user"])
- # 文档挂通信部:对作战部用户是 OTHER,且未配置部门
- doc = _make_document("矩阵其他部门主案", org=comms, visibility="ORGANIZATION")
- db.session.commit()
- context = _user_context(user)
- access = evaluate_plan_access(doc, context)
- assert access.allowed is False
- assert access.reason == "ACL"
- @pytest.mark.integration
- def test_assigned_scope_via_role_departments(matrix_env):
- from dms.models import RoleDepartment
- app, ids = matrix_env
- with app.app_context():
- comms = db.session.get(Organization, ids["comms"])
- user = db.session.get(User, ids["user"])
- role = _make_role("跨部门角色", actor_id=user.id)
- _matrix_row(role.id, "SECRET", "ASSIGNED", can_view=True)
- db.session.add(
- RoleDepartment(
- role_id=role.id,
- organization_id=comms.id,
- )
- )
- doc = _make_document("矩阵已配置部门主案", org=comms, visibility="ORGANIZATION")
- db.session.commit()
- context = _user_context(user)
- access = evaluate_plan_access(doc, context)
- assert access.allowed is True
- assert "VIEW" in access.matrix_actions
- @pytest.mark.integration
- def test_security_and_status_precedence_over_matrix(matrix_env):
- app, ids = matrix_env
- with app.app_context():
- ops = db.session.get(Organization, ids["ops"])
- user = db.session.get(User, ids["user"]) # 密级 SECRET
- role = _make_role("越权角色", actor_id=user.id)
- _matrix_row(role.id, "TOP_SECRET", "OWN", can_view=True)
- top_secret_doc = _make_document(
- "矩阵绝密主案", org=ops, security="TOP_SECRET"
- )
- draft_doc = _make_document(
- "矩阵草稿主案", org=ops, security="SECRET", status="DRAFT"
- )
- db.session.commit()
- context = _user_context(user)
- assert evaluate_plan_access(top_secret_doc, context).reason == "SECURITY"
- assert evaluate_plan_access(draft_doc, context).reason == "STATUS"
- @pytest.mark.integration
- def test_default_role_applies_and_disabled_role_yields_nothing(matrix_env):
- app, ids = matrix_env
- with app.app_context():
- ops = db.session.get(Organization, ids["ops"])
- user = db.session.get(User, ids["user"]) # 未挂角色
- default_role = _make_role("默认角色", is_default=True, actor_id=user.id)
- _matrix_row(default_role.id, "SECRET", "OWN", can_view=True)
- doc = _make_document("默认角色主案", org=ops, visibility="ORGANIZATION")
- db.session.commit()
- context = _user_context(user)
- assert evaluate_plan_access(doc, context).allowed is True
- # 默认角色停用:无显式角色且无默认 → 矩阵贡献为空
- default_role.status = "DISABLED"
- db.session.commit()
- denied = evaluate_plan_access(doc, context)
- assert denied.allowed is False
- # 显式挂停用角色:不回退默认角色
- disabled_role = _make_role(
- "停用角色", status="DISABLED", actor_id=user.id
- )
- _matrix_row(disabled_role.id, "SECRET", "OWN", can_view=True)
- user.role_id = disabled_role.id
- db.session.commit()
- assert evaluate_plan_access(doc, context).allowed is False
- @pytest.mark.integration
- def test_matrix_actions_expand_allowed_actions(matrix_env):
- app, ids = matrix_env
- with app.app_context():
- ops = db.session.get(Organization, ids["ops"])
- user = db.session.get(User, ids["user"])
- role = _make_role("完整权限角色", actor_id=user.id)
- _matrix_row(
- role.id,
- "SECRET",
- "OWN",
- can_view=True,
- can_download=True,
- can_edit=True,
- can_delete=True,
- can_config_permission=True,
- can_bind_attachment=True,
- can_unbind_attachment=True,
- )
- main_doc = _make_document("矩阵操作位主案", org=ops)
- sub_doc = _make_document(
- "矩阵操作位子案",
- org=ops,
- document_type="SUB_PLAN",
- )
- sub_doc.root_document_id = main_doc.id
- sub_doc.parent_document_id = main_doc.id
- db.session.commit()
- context = _user_context(user)
- main_actions = plan_allowed_actions(
- main_doc, context, evaluate_plan_access(main_doc, context)
- )
- assert set(main_actions) >= {
- "VIEW",
- "DOWNLOAD",
- "EDIT",
- "DELETE",
- "CONFIG_PERMISSION",
- }
- sub_actions = plan_allowed_actions(
- sub_doc, context, evaluate_plan_access(sub_doc, context)
- )
- # 子方案过滤授权/挂载/解除挂载
- assert "CONFIG_PERMISSION" not in sub_actions
- assert "BIND_ATTACHMENT" not in sub_actions
- assert "UNBIND_ATTACHMENT" not in sub_actions
- assert {"VIEW", "DOWNLOAD", "EDIT", "DELETE"} <= set(sub_actions)
|