mwpermission 0.1.25__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mwpermission-0.2.0/PKG-INFO +26 -0
- mwpermission-0.2.0/mwpermission/__init__.py +4 -0
- mwpermission-0.2.0/mwpermission/permission-fastapi.py +257 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/mwpermission/permission.py +3 -0
- mwpermission-0.2.0/mwpermission.egg-info/PKG-INFO +26 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/mwpermission.egg-info/SOURCES.txt +1 -0
- mwpermission-0.2.0/mwpermission.egg-info/requires.txt +4 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/setup.py +8 -2
- mwpermission-0.1.25/PKG-INFO +0 -15
- mwpermission-0.1.25/mwpermission/__init__.py +0 -1
- mwpermission-0.1.25/mwpermission.egg-info/PKG-INFO +0 -15
- mwpermission-0.1.25/mwpermission.egg-info/requires.txt +0 -1
- {mwpermission-0.1.25 → mwpermission-0.2.0}/CHANGES.txt +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/LICENSE.txt +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/MANIFEST.in +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/README.rst +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/mwpermission.egg-info/dependency_links.txt +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/mwpermission.egg-info/top_level.txt +0 -0
- {mwpermission-0.1.25 → mwpermission-0.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: mwpermission
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: maxwin permission
|
|
5
|
+
Home-page: https://bitbucket.org/maxwin-inc/mwpermission/src
|
|
6
|
+
Author: cxhjet
|
|
7
|
+
Author-email: cxhjet@qq.com
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
License-File: LICENSE.txt
|
|
17
|
+
Requires-Dist: flask>=0.11.1
|
|
18
|
+
Requires-Dist: mwsdk>=0.3.3
|
|
19
|
+
Requires-Dist: requests>=2.25.1
|
|
20
|
+
Requires-Dist: fastapi>=0.128.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from functools import wraps, partial
|
|
2
|
+
from typing import Callable, Any
|
|
3
|
+
from fastapi import Request, HTTPException, status
|
|
4
|
+
from mwsdk import Rightmanage_inner
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
# 类型别名
|
|
9
|
+
AsyncDependency = Callable[[Request], Any]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PermissionError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FastAPIPermission:
|
|
17
|
+
def __init__(self, sysname: str | None = None, version: str = 'v1.0'):
|
|
18
|
+
'''
|
|
19
|
+
:param sysname: 系统名称,如:maxguideweb
|
|
20
|
+
:param version: 权限的版本,通过url取permission
|
|
21
|
+
'''
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.version: str = version
|
|
24
|
+
self.sysname: str | None = sysname
|
|
25
|
+
|
|
26
|
+
def _get_system_name(self, request: Request) -> str:
|
|
27
|
+
'''从请求中获取系统名称(支持从 state 或 config 读取)'''
|
|
28
|
+
# 如果初始化时指定了 sysname,直接使用
|
|
29
|
+
if self.sysname:
|
|
30
|
+
return self.sysname
|
|
31
|
+
|
|
32
|
+
# 否则从应用配置中读取
|
|
33
|
+
if hasattr(request.app.state, 'SYSTEM_NAME'):
|
|
34
|
+
return request.app.state.SYSTEM_NAME
|
|
35
|
+
elif hasattr(request.app, 'config') and hasattr(request.app.config, 'SYSTEM_NAME'):
|
|
36
|
+
return request.app.config.SYSTEM_NAME
|
|
37
|
+
|
|
38
|
+
# 都没有则返回空字符串
|
|
39
|
+
return ''
|
|
40
|
+
|
|
41
|
+
def _is_development(self, request: Request) -> bool:
|
|
42
|
+
'''检查是否为开发环境'''
|
|
43
|
+
if hasattr(request.app.state, 'DEVELOPMENT'):
|
|
44
|
+
return request.app.state.DEVELOPMENT
|
|
45
|
+
elif hasattr(request.app, 'config') and hasattr(request.app.config, 'DEVELOPMENT'):
|
|
46
|
+
return request.app.config.DEVELOPMENT
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
def _get_user_id(self, request: Request) -> int | None:
|
|
50
|
+
'''从请求中获取用户ID'''
|
|
51
|
+
return getattr(request.state, 'user_id', None)
|
|
52
|
+
|
|
53
|
+
def _get_user_name(self, request: Request) -> str:
|
|
54
|
+
'''从请求中获取用户名'''
|
|
55
|
+
return getattr(request.state, 'user_name', 'unknown')
|
|
56
|
+
|
|
57
|
+
def _should_skip_permission_check(self, request: Request) -> bool:
|
|
58
|
+
'''判断是否应该跳过权限检查(开发模式或非appuser用户)'''
|
|
59
|
+
# 开发环境跳过
|
|
60
|
+
if self._is_development(request):
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
# 非appuser类型用户跳过权限检查
|
|
64
|
+
if hasattr(request.state, 'current_user'):
|
|
65
|
+
current_user = request.state.current_user
|
|
66
|
+
if hasattr(current_user, 'type') and current_user.type != 'appuser':
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
async def _get_permission(self, request: Request) -> dict[str, Any]:
|
|
72
|
+
'''获取用户权限(异步包装同步SDK调用)'''
|
|
73
|
+
# 开发模式下返回空权限(跳过检查)
|
|
74
|
+
if self._is_development(request):
|
|
75
|
+
return {}
|
|
76
|
+
|
|
77
|
+
user_id = self._get_user_id(request)
|
|
78
|
+
if not user_id:
|
|
79
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not authenticated")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# 使用 run_in_executor 在线程池中执行同步SDK调用,避免阻塞事件循环
|
|
83
|
+
loop = asyncio.get_running_loop()
|
|
84
|
+
# 使用 partial 正确传递多个参数
|
|
85
|
+
func = partial(Rightmanage_inner().permissions, self._get_system_name(request), user_id, self.version)
|
|
86
|
+
_, result = await loop.run_in_executor(None, func)
|
|
87
|
+
return result
|
|
88
|
+
except HTTPException:
|
|
89
|
+
raise
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logging.error(f'获取权限服务出错,错误:{str(e)}')
|
|
92
|
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Permission service unavailable")
|
|
93
|
+
|
|
94
|
+
def check(
|
|
95
|
+
self,
|
|
96
|
+
subsystem3s: str | list[str],
|
|
97
|
+
actions: list[str]
|
|
98
|
+
) -> AsyncDependency:
|
|
99
|
+
'''
|
|
100
|
+
创建权限检查的依赖项,用于FastAPI路由
|
|
101
|
+
:param subsystem3s: 权限名称,比如:"vehicle" or "vehicle,car" or ["vehicle","car"] 等
|
|
102
|
+
:param actions: 是list,内容为 ['insert','edit','delete',...]
|
|
103
|
+
:return: FastAPI依赖函数
|
|
104
|
+
|
|
105
|
+
使用示例:
|
|
106
|
+
@app.get("/employees")
|
|
107
|
+
async def get_employees(
|
|
108
|
+
_: None = Depends(permission.check('employee', ['view']))
|
|
109
|
+
):
|
|
110
|
+
pass
|
|
111
|
+
'''
|
|
112
|
+
async def check_permission(request: Request):
|
|
113
|
+
# 开发环境或特殊用户跳过权限检查
|
|
114
|
+
if self._should_skip_permission_check(request):
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# 处理subsystem3s参数
|
|
118
|
+
if isinstance(subsystem3s, str):
|
|
119
|
+
subsystem3list = subsystem3s.split(',')
|
|
120
|
+
elif isinstance(subsystem3s, list):
|
|
121
|
+
subsystem3list = subsystem3s
|
|
122
|
+
else:
|
|
123
|
+
raise HTTPException(
|
|
124
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
125
|
+
detail=f'the subsystem {subsystem3s} is not support, example:"vehicle" or "vehicle,car" or ["vehicle","car"]'
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# 获取权限(带重试机制)
|
|
129
|
+
try:
|
|
130
|
+
permissions = await self._get_permission(request)
|
|
131
|
+
except HTTPException as e:
|
|
132
|
+
# 避免第一次访问时出错,再次重试访问权限
|
|
133
|
+
if e.status_code == status.HTTP_503_SERVICE_UNAVAILABLE:
|
|
134
|
+
logging.error(f'第一次访问权限服务出错,进行重试')
|
|
135
|
+
try:
|
|
136
|
+
permissions = await self._get_permission(request)
|
|
137
|
+
except Exception as retry_error:
|
|
138
|
+
# 避免出500错误,返回403
|
|
139
|
+
logging.error(f'第二次访问权限服务出错,错误:{str(retry_error)}')
|
|
140
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied")
|
|
141
|
+
else:
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
# 没有指定 subsystem 和 actions,表示允许访问
|
|
145
|
+
if permissions and not subsystem3list and not actions:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
# 检查权限
|
|
149
|
+
acts_sup = set()
|
|
150
|
+
sys_name = self._get_system_name(request)
|
|
151
|
+
for subsystem3 in subsystem3list:
|
|
152
|
+
permission = permissions.get(subsystem3)
|
|
153
|
+
if permission is None:
|
|
154
|
+
# 支持同时检测多个权限后,有些权限可能不会在当前项目中
|
|
155
|
+
logging.warning(f'the subsystem({subsystem3}) is not in {sys_name}')
|
|
156
|
+
continue
|
|
157
|
+
# 获取权限的操作
|
|
158
|
+
ops = permission.get('ops', [])
|
|
159
|
+
acts_sup.update({act for act in actions if act in ops})
|
|
160
|
+
|
|
161
|
+
# 权限没有操作时,则拒绝
|
|
162
|
+
if not acts_sup:
|
|
163
|
+
raise HTTPException(
|
|
164
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
165
|
+
detail=f"Permission denied: missing required actions {actions} for {subsystem3s}"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return check_permission
|
|
169
|
+
|
|
170
|
+
async def check_permission(self, subsystem3: str, action: str, msg: str = '', request: Request | None = None) -> bool:
|
|
171
|
+
'''
|
|
172
|
+
检查权限的方法,用于在函数内部调用(异步包装同步SDK调用)
|
|
173
|
+
:param subsystem3: 子系统名称
|
|
174
|
+
:param action: 操作类型
|
|
175
|
+
:param msg: 自定义错误消息
|
|
176
|
+
:param request: FastAPI Request对象
|
|
177
|
+
|
|
178
|
+
使用示例:
|
|
179
|
+
@app.get("/employee/check")
|
|
180
|
+
async def check_employee_auth(request: Request):
|
|
181
|
+
# 检查是否有看到身份证的权限
|
|
182
|
+
await permission.check_permission('employee', 'see_ID', request=request)
|
|
183
|
+
'''
|
|
184
|
+
if not request:
|
|
185
|
+
raise HTTPException(
|
|
186
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
187
|
+
detail="Request object is required for permission check"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
user_id = self._get_user_id(request)
|
|
191
|
+
if not user_id:
|
|
192
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not authenticated")
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
# 使用 run_in_executor 在线程池中执行同步SDK调用
|
|
196
|
+
loop = asyncio.get_running_loop()
|
|
197
|
+
func = partial(Rightmanage_inner().permissions, self._get_system_name(request), user_id)
|
|
198
|
+
_, permissions_js = await loop.run_in_executor(None, func)
|
|
199
|
+
permission = permissions_js.get(subsystem3)
|
|
200
|
+
except HTTPException:
|
|
201
|
+
raise
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logging.error(f'获取权限服务出错,错误:{str(e)}')
|
|
204
|
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Permission service unavailable")
|
|
205
|
+
|
|
206
|
+
sys_name = self._get_system_name(request)
|
|
207
|
+
if permission is None:
|
|
208
|
+
raise HTTPException(
|
|
209
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
210
|
+
detail=f'the subsystem({subsystem3}) is not exist in {sys_name}'
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
ops = permission.get('ops', [])
|
|
214
|
+
if action not in ops:
|
|
215
|
+
if not msg:
|
|
216
|
+
user_name = self._get_user_name(request)
|
|
217
|
+
msg = f'The user({user_name}) have no this ({action}) right!'
|
|
218
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg)
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
def create_decorator(self, subsystem3s: str | list[str], actions: list[str]) -> Callable[..., Any]:
|
|
222
|
+
'''
|
|
223
|
+
创建装饰器风格的权限检查(与Flask版本API兼容)
|
|
224
|
+
:param subsystem3s: 权限名称
|
|
225
|
+
:param actions: 操作列表
|
|
226
|
+
|
|
227
|
+
使用示例:
|
|
228
|
+
@permission.create_decorator('employee', ['view'])
|
|
229
|
+
async def get_employees():
|
|
230
|
+
pass
|
|
231
|
+
'''
|
|
232
|
+
def decorate(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
233
|
+
@wraps(func)
|
|
234
|
+
async def wrapper(*args: Any, **kwargs: Any):
|
|
235
|
+
# 获取request对象(假设在第一个参数或通过其他方式)
|
|
236
|
+
request = None
|
|
237
|
+
if args and isinstance(args[0], Request):
|
|
238
|
+
request = args[0]
|
|
239
|
+
elif 'request' in kwargs:
|
|
240
|
+
request = kwargs['request']
|
|
241
|
+
|
|
242
|
+
if request:
|
|
243
|
+
dependency = self.check(subsystem3s, actions)
|
|
244
|
+
await dependency(request)
|
|
245
|
+
else:
|
|
246
|
+
raise HTTPException(
|
|
247
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
248
|
+
detail="Request object not found for permission check"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# 处理同步和异步函数
|
|
252
|
+
if asyncio.iscoroutinefunction(func):
|
|
253
|
+
return await func(*args, **kwargs)
|
|
254
|
+
else:
|
|
255
|
+
return func(*args, **kwargs)
|
|
256
|
+
return wrapper
|
|
257
|
+
return decorate
|
|
@@ -65,6 +65,9 @@ class Permission(object):
|
|
|
65
65
|
# 避免出500錯誤,返回403
|
|
66
66
|
logging.error(f'第二次訪問權限服務出錯,錯誤:{str(e)}')
|
|
67
67
|
permissions = {}
|
|
68
|
+
# 没有指定 subsystem 和actions ,表示允许访问,但没有指定任何权限时,则报403
|
|
69
|
+
if permissions and not subsystem3list and not actions:
|
|
70
|
+
return func(*args, **kwargs)
|
|
68
71
|
acts_sup = set()
|
|
69
72
|
for subsysntem3 in subsystem3list:
|
|
70
73
|
permission = permissions.get(subsysntem3)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: mwpermission
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: maxwin permission
|
|
5
|
+
Home-page: https://bitbucket.org/maxwin-inc/mwpermission/src
|
|
6
|
+
Author: cxhjet
|
|
7
|
+
Author-email: cxhjet@qq.com
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
License-File: LICENSE.txt
|
|
17
|
+
Requires-Dist: flask>=0.11.1
|
|
18
|
+
Requires-Dist: mwsdk>=0.3.3
|
|
19
|
+
Requires-Dist: requests>=2.25.1
|
|
20
|
+
Requires-Dist: fastapi>=0.128.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
@@ -8,7 +8,7 @@ def read(f):
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name='mwpermission',
|
|
11
|
-
version='0.
|
|
11
|
+
version='0.2.0',
|
|
12
12
|
description='maxwin permission ',
|
|
13
13
|
# long_description= '\n\n'.join((read('README.rst'), read('CHANGES.txt'))),
|
|
14
14
|
url='https://bitbucket.org/maxwin-inc/mwpermission/src', # Optional
|
|
@@ -24,8 +24,14 @@ setup(
|
|
|
24
24
|
'Operating System :: OS Independent',
|
|
25
25
|
'Programming Language :: Python :: 3.5',
|
|
26
26
|
'Programming Language :: Python :: 3.6',
|
|
27
|
+
'Programming Language :: Python :: 3.7',
|
|
28
|
+
'Programming Language :: Python :: 3.11',
|
|
29
|
+
'Programming Language :: Python :: 3.12'
|
|
27
30
|
],
|
|
28
31
|
install_requires=[
|
|
29
|
-
'flask>=0.11.1'
|
|
32
|
+
'flask>=0.11.1',
|
|
33
|
+
'mwsdk>=0.3.3',
|
|
34
|
+
'requests>=2.25.1',
|
|
35
|
+
'fastapi>=0.128.0'
|
|
30
36
|
]
|
|
31
37
|
)
|
mwpermission-0.1.25/PKG-INFO
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.1
|
|
2
|
-
Name: mwpermission
|
|
3
|
-
Version: 0.1.25
|
|
4
|
-
Summary: maxwin permission
|
|
5
|
-
Home-page: https://bitbucket.org/maxwin-inc/mwpermission/src
|
|
6
|
-
Author: cxhjet
|
|
7
|
-
Author-email: cxhjet@qq.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Description: UNKNOWN
|
|
10
|
-
Platform: UNKNOWN
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.1
|
|
2
|
-
Name: mwpermission
|
|
3
|
-
Version: 0.1.25
|
|
4
|
-
Summary: maxwin permission
|
|
5
|
-
Home-page: https://bitbucket.org/maxwin-inc/mwpermission/src
|
|
6
|
-
Author: cxhjet
|
|
7
|
-
Author-email: cxhjet@qq.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Description: UNKNOWN
|
|
10
|
-
Platform: UNKNOWN
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
flask>=0.11.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|