test_web_presign_permissions.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """预签约 Web 入口使用真实角色范围查询,在越权时不得调用任何读写服务。"""
  2. import asyncio
  3. from datetime import datetime
  4. from unittest.mock import Mock
  5. import pytest
  6. from sqlalchemy import create_engine
  7. from sqlalchemy.orm import sessionmaker
  8. from app.api import presign_order as api
  9. from app.models.base import Base
  10. from app.models.models import (
  11. BizCollectionProtocol, BizPresignOrder, BizPresignTransfer, SysRole, SysRoleDataScopeDetail,
  12. SysUser, SysUserRole,
  13. )
  14. USER = {'user_id': 'u1', 'role': 'admin', 'tenant_no': 'tenant-a', 'dept_id': 'dept-a'}
  15. @pytest.fixture
  16. def db():
  17. engine = create_engine('sqlite:///:memory:')
  18. Base.metadata.create_all(engine)
  19. session = sessionmaker(bind=engine)()
  20. common = dict(tenant_no='tenant-a', create_dept_id='dept-a', create_time=datetime.now(), del_flag=0)
  21. session.add_all([
  22. SysUser(id='u1', user_no='u1', nick_name='用户', password='', dept_id='dept-a', **common),
  23. SysRole(id='self', role_name='普通人员', data_scope='4', status='1', **common),
  24. SysUserRole(id='ur', user_id='u1', role_id='self', **common),
  25. ])
  26. for owner in ('u1', 'u2'):
  27. session.add_all([
  28. BizCollectionProtocol(id='cp-' + owner, protocol_no='CP-' + owner, create_by=owner, **common),
  29. BizPresignOrder(id='p-' + owner, protocol_no='P-' + owner, order_status='active', create_by=owner, **common),
  30. BizPresignTransfer(id='t-' + owner, presign_order_id='p-' + owner, pay_status='jfqrz', transfer_amount=10, create_by=owner, **common),
  31. ])
  32. session.commit()
  33. yield session
  34. session.close()
  35. engine.dispose()
  36. @pytest.mark.parametrize('action', [
  37. 'get', 'update', 'delete', 'convert', 'create_transfer',
  38. 'update_transfer', 'submit', 'withdraw', 'confirm', 'reject',
  39. ])
  40. @pytest.mark.parametrize('access', ['denied', 'self', 'union'])
  41. def test_presign_endpoint_enforces_union_data_scope(db, monkeypatch, action, access):
  42. if access == 'union':
  43. common = dict(tenant_no='tenant-a', create_dept_id='', del_flag=0)
  44. db.add_all([
  45. SysRole(id='custom', role_name='自定义', data_scope='5', status='1', **common),
  46. SysUserRole(id='ur2', user_id='u1', role_id='custom', **common),
  47. SysRoleDataScopeDetail(id='detail', role_id='custom', scope_type='user', scope_value='u2', **common),
  48. ])
  49. db.commit()
  50. owner = 'u1' if access == 'self' else 'u2'
  51. cases = {
  52. 'get': (api.get_presign_order, 'get_presign_order', {'order_id': 'p-' + owner}),
  53. 'update': (api.update_presign_order, 'update_presign_order', {'order_id': 'p-' + owner, 'body': api.PresignOrderBody(remark='test')}),
  54. 'delete': (api.delete_presign_order, 'soft_delete_presign_order', {'order_id': 'p-' + owner}),
  55. 'convert': (api.convert_presign_order, 'convert_to_protocol', {'order_id': 'p-' + owner}),
  56. 'create_transfer': (api.create_presign_transfer, 'add_transfer', {'order_id': 'p-' + owner, 'body': api.PresignTransferBody(transferAmount=10)}),
  57. 'update_transfer': (api.update_presign_transfer, 'update_transfer_draft', {'transfer_id': 't-' + owner, 'body': api.PresignTransferBody(transferAmount=10)}),
  58. 'submit': (api.submit_presign_transfer_finance, 'submit_receipt_finance', {'transfer_id': 't-' + owner}),
  59. 'withdraw': (api.withdraw_presign_transfer, 'withdraw_receipt', {'transfer_id': 't-' + owner}),
  60. 'confirm': (api.confirm_presign_transfer, 'confirm_receipt', {'transfer_id': 't-' + owner, 'body': None}),
  61. 'reject': (api.reject_presign_transfer, 'reject_receipt', {'transfer_id': 't-' + owner}),
  62. }
  63. endpoint, service_name, kwargs = cases[action]
  64. service = Mock(return_value={'id': 'result'})
  65. monkeypatch.setattr(api.po_svc, service_name, service)
  66. monkeypatch.setattr(api.po_svc, 'order_to_dict', lambda *args, **kwargs: {'id': 'result'})
  67. monkeypatch.setattr(api.po_svc, '_transfer_to_dict', lambda *args, **kwargs: {'id': 'result'})
  68. result = asyncio.run(endpoint(db=db, user=USER, **kwargs))
  69. if access == 'denied':
  70. assert result['code'] == 403
  71. service.assert_not_called()
  72. else:
  73. assert result['code'] == 200
  74. service.assert_called_once()
  75. def test_collection_protocol_audit_rejects_outside_scope(db, monkeypatch):
  76. from app.api import collection_protocol
  77. audit = Mock()
  78. monkeypatch.setattr(collection_protocol.cp_svc, 'create_audit', audit)
  79. result = asyncio.run(collection_protocol.submit_collection_protocol_audit(
  80. protocol_id='cp-u2',
  81. body=collection_protocol.AuditCreateBody(auditStatus='approved'),
  82. db=db, user=USER,
  83. ))
  84. assert result['code'] == 403
  85. audit.assert_not_called()
  86. @pytest.mark.parametrize('action', ['create', 'update', 'transfer', 'convert'])
  87. def test_web_presign_writes_use_server_department(db, monkeypatch, action):
  88. cases = {
  89. 'create': (api.create_presign_order, 'create_presign_order', {'body': api.PresignOrderBody(motherName='客户')}),
  90. 'update': (api.update_presign_order, 'update_presign_order', {'order_id': 'p-u1', 'body': api.PresignOrderBody(remark='test')}),
  91. 'transfer': (api.create_presign_transfer, 'add_transfer', {'order_id': 'p-u1', 'body': api.PresignTransferBody(transferAmount=10)}),
  92. 'convert': (api.convert_presign_order, 'convert_to_protocol', {'order_id': 'p-u1'}),
  93. }
  94. endpoint, service_name, kwargs = cases[action]
  95. service = Mock(return_value={'id': 'result'})
  96. monkeypatch.setattr(api.po_svc, service_name, service)
  97. monkeypatch.setattr(api.po_svc, 'order_to_dict', lambda *args, **kwargs: {'id': 'result'})
  98. monkeypatch.setattr(api.po_svc, '_transfer_to_dict', lambda *args, **kwargs: {'id': 'result'})
  99. result = asyncio.run(endpoint(db=db, user={**USER, 'dept_id': 'untrusted-dept'}, **kwargs))
  100. assert result['code'] == 200
  101. assert service.call_args.kwargs['create_dept_id'] == 'dept-a'
  102. def test_collection_protocol_create_uses_server_department(db, monkeypatch):
  103. from types import SimpleNamespace
  104. from app.api import collection_protocol
  105. create = Mock(return_value=SimpleNamespace(id='new-protocol', payment_model_version=2))
  106. monkeypatch.setattr(collection_protocol.cp_svc, 'create_collection_protocol', create)
  107. monkeypatch.setattr(collection_protocol.cp_svc, '_protocol_to_dict', lambda obj: {'id': obj.id})
  108. monkeypatch.setattr(collection_protocol, '_persist_protocol_sub_resources', Mock())
  109. result = asyncio.run(collection_protocol.create_collection_protocol(
  110. body=collection_protocol.ProtocolCreateBody(), db=db,
  111. user={**USER, 'dept_id': 'untrusted-dept'},
  112. ))
  113. assert result['code'] == 200
  114. assert create.call_args.kwargs['create_dept_id'] == 'dept-a'