abs-auth-rbac-core 0.1.8__py3-none-any.whl → 0.3.18__py3-none-any.whl
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.
- abs_auth_rbac_core/auth/__init__.py +13 -1
- abs_auth_rbac_core/auth/auth_functions.py +4 -1
- abs_auth_rbac_core/auth/middleware.py +197 -3
- abs_auth_rbac_core/models/gov_casbin_rule.py +9 -0
- abs_auth_rbac_core/models/user.py +1 -0
- abs_auth_rbac_core/rbac/service.py +425 -139
- abs_auth_rbac_core/repository/__init__.py +4 -0
- abs_auth_rbac_core/repository/permission_repository.py +12 -0
- abs_auth_rbac_core/repository/role_repository.py +18 -0
- abs_auth_rbac_core/service/__init__.py +4 -0
- abs_auth_rbac_core/service/permission_service.py +15 -0
- abs_auth_rbac_core/service/role_service.py +18 -0
- abs_auth_rbac_core/util/permission_constants.py +850 -16
- abs_auth_rbac_core-0.3.18.dist-info/METADATA +727 -0
- {abs_auth_rbac_core-0.1.8.dist-info → abs_auth_rbac_core-0.3.18.dist-info}/RECORD +16 -10
- abs_auth_rbac_core-0.1.8.dist-info/METADATA +0 -233
- {abs_auth_rbac_core-0.1.8.dist-info → abs_auth_rbac_core-0.3.18.dist-info}/WHEEL +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from typing import List, Optional, Callable,Any,Tuple
|
|
2
2
|
import os
|
|
3
|
+
from pydantic import BaseModel
|
|
3
4
|
import casbin
|
|
4
5
|
from casbin_sqlalchemy_adapter import Adapter
|
|
5
|
-
from
|
|
6
|
+
from casbin_redis_watcher import RedisWatcher, WatcherOptions,new_watcher
|
|
7
|
+
from sqlalchemy import and_, or_, select
|
|
6
8
|
from sqlalchemy.orm import Session, joinedload
|
|
7
9
|
from ..schema import CreatePermissionSchema
|
|
8
10
|
from ..models import (
|
|
@@ -13,7 +15,9 @@ from ..models import (
|
|
|
13
15
|
Permission,
|
|
14
16
|
UserPermission
|
|
15
17
|
)
|
|
18
|
+
from abs_utils.logger import setup_logger
|
|
16
19
|
|
|
20
|
+
logger = setup_logger(__name__)
|
|
17
21
|
from abs_exception_core.exceptions import (
|
|
18
22
|
DuplicatedError,
|
|
19
23
|
NotFoundError,
|
|
@@ -21,9 +25,17 @@ from abs_exception_core.exceptions import (
|
|
|
21
25
|
)
|
|
22
26
|
|
|
23
27
|
from ..models.gov_casbin_rule import GovCasbinRule
|
|
28
|
+
from redis import Redis
|
|
29
|
+
|
|
30
|
+
class RedisWatcherSchema(BaseModel):
|
|
31
|
+
host: str
|
|
32
|
+
port: int
|
|
33
|
+
channel: str
|
|
34
|
+
ssl: Optional[bool] = False
|
|
35
|
+
password: Optional[str] = None
|
|
24
36
|
|
|
25
37
|
class RBACService:
|
|
26
|
-
def __init__(self, session: Callable[...,Session]):
|
|
38
|
+
def __init__(self, session: Callable[...,Session],redis_config:Optional[RedisWatcherSchema]=None):
|
|
27
39
|
"""
|
|
28
40
|
Service For Managing the RBAC
|
|
29
41
|
Args:
|
|
@@ -31,9 +43,48 @@ class RBACService:
|
|
|
31
43
|
"""
|
|
32
44
|
self.db = session
|
|
33
45
|
self.enforcer = None
|
|
34
|
-
self._initialize_casbin()
|
|
46
|
+
self._initialize_casbin(redis_config)
|
|
47
|
+
self.watcher = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _save_policy_if_watcher_active(self):
|
|
51
|
+
"""
|
|
52
|
+
Helper method to save policy only if Redis watcher is active.
|
|
53
|
+
This ensures distributed systems stay in sync while avoiding
|
|
54
|
+
unnecessary load_policy calls on the current instance.
|
|
55
|
+
"""
|
|
56
|
+
if self.is_watcher_active():
|
|
57
|
+
self.enforcer.save_policy()
|
|
58
|
+
|
|
59
|
+
def _bulk_operation_context(self):
|
|
60
|
+
"""
|
|
61
|
+
Context manager for bulk Casbin operations.
|
|
62
|
+
Temporarily disables auto_save, executes operations, then saves once at the end.
|
|
63
|
+
This significantly reduces overhead for bulk policy changes.
|
|
35
64
|
|
|
36
|
-
|
|
65
|
+
Usage:
|
|
66
|
+
with self._bulk_operation_context():
|
|
67
|
+
self.enforcer.add_policies(policies)
|
|
68
|
+
"""
|
|
69
|
+
class BulkOperationContext:
|
|
70
|
+
def __init__(self, service):
|
|
71
|
+
self.service = service
|
|
72
|
+
self.original_auto_save = None
|
|
73
|
+
|
|
74
|
+
def __enter__(self):
|
|
75
|
+
self.original_auto_save = self.service.enforcer.auto_save
|
|
76
|
+
self.service.enforcer.enable_auto_save(False)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
80
|
+
if exc_type is None: # Only save if no exception occurred
|
|
81
|
+
self.service._save_policy_if_watcher_active()
|
|
82
|
+
self.service.enforcer.enable_auto_save(self.original_auto_save)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
return BulkOperationContext(self)
|
|
86
|
+
|
|
87
|
+
def _initialize_casbin(self,redis_config:Optional[RedisWatcherSchema]=None):
|
|
37
88
|
"""
|
|
38
89
|
Initiates the casbin policy using the default rules
|
|
39
90
|
"""
|
|
@@ -51,36 +102,82 @@ class RBACService:
|
|
|
51
102
|
self.enforcer = casbin.Enforcer(
|
|
52
103
|
policy_path, adapter
|
|
53
104
|
)
|
|
54
|
-
|
|
55
|
-
|
|
105
|
+
self.enforcer.enable_auto_save(True)
|
|
106
|
+
|
|
107
|
+
if redis_config:
|
|
108
|
+
try:
|
|
109
|
+
redis_client = Redis(
|
|
110
|
+
host=redis_config.host,
|
|
111
|
+
port=redis_config.port,
|
|
112
|
+
password=redis_config.password if hasattr(redis_config, 'password') else None,
|
|
113
|
+
ssl=redis_config.ssl, # This is crucial for azure redis
|
|
114
|
+
ssl_cert_reqs=None, # This is crucial for azure redis (Should be none for azure redis)
|
|
115
|
+
ssl_check_hostname=False,
|
|
116
|
+
socket_connect_timeout=10, # Only socket_connect_timeout is required for azure redis watcher
|
|
117
|
+
decode_responses=True,
|
|
118
|
+
retry_on_timeout=True,
|
|
119
|
+
health_check_interval=30 # Required for open connection
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# Test Redis connection
|
|
124
|
+
redis_client.ping()
|
|
125
|
+
|
|
126
|
+
# Create Watcher and Options
|
|
127
|
+
option = WatcherOptions()
|
|
128
|
+
option.host = redis_config.host
|
|
129
|
+
option.port = redis_config.port
|
|
130
|
+
option.password = redis_config.password
|
|
131
|
+
option.ssl = redis_config.ssl
|
|
132
|
+
option.channel = redis_config.channel
|
|
133
|
+
option.optional_update_callback = lambda _: self.enforcer.load_policy()
|
|
134
|
+
|
|
135
|
+
option.init_config()
|
|
136
|
+
|
|
137
|
+
watcher = RedisWatcher()
|
|
138
|
+
|
|
139
|
+
watcher.sub_client = redis_client.pubsub()
|
|
140
|
+
watcher.pub_client = redis_client
|
|
141
|
+
watcher.init_config(option)
|
|
142
|
+
watcher.close = False
|
|
143
|
+
watcher.subscribe_thread.start()
|
|
144
|
+
watcher.subscribe_event.wait(timeout=10)
|
|
145
|
+
|
|
146
|
+
self.enforcer.set_watcher(watcher)
|
|
147
|
+
self.watcher = watcher
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Failed to initialize Redis watcher: {e}")
|
|
150
|
+
self.watcher = None
|
|
151
|
+
else:
|
|
152
|
+
logger.info("Redis watcher not configured - Casbin will work without real-time policy updates")
|
|
56
153
|
|
|
57
154
|
def add_policy(self,role:str,resource:str,action:str,module:str):
|
|
58
155
|
"""
|
|
59
|
-
Add a policy to the casbin enforcer
|
|
156
|
+
Add a policy to the casbin enforcer (optimized for distributed systems)
|
|
60
157
|
"""
|
|
61
|
-
self.
|
|
62
|
-
|
|
158
|
+
with self._bulk_operation_context():
|
|
159
|
+
self.enforcer.add_policy(role,resource,action,module)
|
|
63
160
|
|
|
64
161
|
def remove_policy(self,role:str,resource:str,action:str,module:str):
|
|
65
162
|
"""
|
|
66
|
-
Remove a policy from the casbin enforcer
|
|
163
|
+
Remove a policy from the casbin enforcer (optimized for distributed systems)
|
|
67
164
|
"""
|
|
68
|
-
self.
|
|
69
|
-
|
|
165
|
+
with self._bulk_operation_context():
|
|
166
|
+
self.enforcer.remove_policy(role,resource,action,module)
|
|
70
167
|
|
|
71
168
|
def add_policies(self,policies:List[Tuple[str,str,str,str]]):
|
|
72
169
|
"""
|
|
73
|
-
Add a list of policies to the casbin enforcer
|
|
170
|
+
Add a list of policies to the casbin enforcer (optimized for distributed systems)
|
|
74
171
|
"""
|
|
75
|
-
self.
|
|
76
|
-
|
|
172
|
+
with self._bulk_operation_context():
|
|
173
|
+
self.enforcer.add_policies(policies)
|
|
77
174
|
|
|
78
175
|
def remove_policies(self,policies:List[List[str]]):
|
|
79
176
|
"""
|
|
80
|
-
Remove a list of policies from the casbin enforcer
|
|
177
|
+
Remove a list of policies from the casbin enforcer (optimized for distributed systems)
|
|
81
178
|
"""
|
|
82
|
-
self.
|
|
83
|
-
|
|
179
|
+
with self._bulk_operation_context():
|
|
180
|
+
self.enforcer.remove_policies(policies)
|
|
84
181
|
|
|
85
182
|
def enforce_policy(self,role:str,resource:str,action:str,module:str):
|
|
86
183
|
"""
|
|
@@ -90,13 +187,13 @@ class RBACService:
|
|
|
90
187
|
|
|
91
188
|
def remove_filter_policy(self,index:int,value:str):
|
|
92
189
|
"""
|
|
93
|
-
Remove a policy by filtering the policy
|
|
190
|
+
Remove a policy by filtering the policy (optimized for distributed systems)
|
|
94
191
|
Args:
|
|
95
192
|
index: The index of the policy to remove
|
|
96
193
|
value: The value of the policy to remove
|
|
97
194
|
"""
|
|
98
|
-
self.
|
|
99
|
-
|
|
195
|
+
with self._bulk_operation_context():
|
|
196
|
+
self.enforcer.remove_filtered_policy(index,value)
|
|
100
197
|
|
|
101
198
|
async def bulk_create_permissions(self,permissions:List[CreatePermissionSchema]):
|
|
102
199
|
"""
|
|
@@ -104,40 +201,77 @@ class RBACService:
|
|
|
104
201
|
"""
|
|
105
202
|
with self.db() as session:
|
|
106
203
|
try:
|
|
107
|
-
|
|
108
|
-
|
|
204
|
+
if not permissions:
|
|
205
|
+
return []
|
|
206
|
+
|
|
207
|
+
if hasattr(permissions[0],'model_dump'):
|
|
208
|
+
add_permissions = [Permission(**permission.model_dump()) for permission in permissions]
|
|
209
|
+
else:
|
|
210
|
+
add_permissions = [Permission(**permission) for permission in permissions]
|
|
211
|
+
|
|
212
|
+
session.bulk_save_objects(add_permissions)
|
|
109
213
|
session.commit()
|
|
110
|
-
return
|
|
214
|
+
return add_permissions
|
|
111
215
|
except Exception as e:
|
|
112
216
|
raise e
|
|
113
217
|
|
|
114
|
-
|
|
218
|
+
def build_filter(self,cond: dict):
|
|
219
|
+
if "and" in cond:
|
|
220
|
+
return and_(*[self.build_filter(c) for c in cond["and"]])
|
|
221
|
+
elif "or" in cond:
|
|
222
|
+
return or_(*[self.build_filter(c) for c in cond["or"]])
|
|
223
|
+
else:
|
|
224
|
+
# Multiple simple field=value pairs in the same dict
|
|
225
|
+
return and_(*[
|
|
226
|
+
getattr(Permission, field) == value
|
|
227
|
+
for field, value in cond.items()
|
|
228
|
+
])
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
async def get_permissions_by_condition(self, condition: dict):
|
|
115
232
|
"""
|
|
116
|
-
Get permission(s) based on
|
|
117
|
-
|
|
233
|
+
Get permission(s) based on nested logical conditions.
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
{
|
|
237
|
+
"and": [
|
|
238
|
+
{"entity_id": "123"},
|
|
239
|
+
{"or": [
|
|
240
|
+
{"user_id": "456"},
|
|
241
|
+
{"group_id": "789"}
|
|
242
|
+
]}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
118
245
|
"""
|
|
119
246
|
with self.db() as session:
|
|
120
247
|
try:
|
|
121
|
-
query = session.query(Permission)
|
|
122
|
-
if use_filter_by:
|
|
123
|
-
query = query.filter_by(**condition)
|
|
124
|
-
else:
|
|
125
|
-
filters = [getattr(Permission, k) == v for k, v in condition.items()]
|
|
126
|
-
query = query.filter(*filters)
|
|
248
|
+
query = session.query(Permission).filter(self.build_filter(condition))
|
|
127
249
|
return query.all()
|
|
128
250
|
except Exception as e:
|
|
129
251
|
raise e
|
|
130
|
-
|
|
252
|
+
|
|
131
253
|
async def delete_permission_by_uuids(self,permission_uuids:List[str]):
|
|
132
254
|
"""
|
|
133
|
-
Delete
|
|
255
|
+
Delete permissions by uuids (optimized for distributed systems)
|
|
256
|
+
Handles cascade deletion in proper order:
|
|
257
|
+
1. Delete UserPermission associations
|
|
258
|
+
2. Delete RolePermission associations
|
|
259
|
+
3. Delete Permissions
|
|
134
260
|
"""
|
|
135
261
|
with self.db() as session:
|
|
136
262
|
try:
|
|
263
|
+
# Step 1: Delete user_permissions associations
|
|
264
|
+
user_permissions = session.query(UserPermission).filter(UserPermission.permission_uuid.in_(permission_uuids)).delete(synchronize_session=False)
|
|
265
|
+
|
|
266
|
+
# Step 2: Delete role_permissions associations (CRITICAL for FK constraint)
|
|
267
|
+
role_permissions = session.query(RolePermission).filter(RolePermission.permission_uuid.in_(permission_uuids)).delete(synchronize_session=False)
|
|
268
|
+
|
|
269
|
+
# Step 3: Delete permissions
|
|
137
270
|
permissions = session.query(Permission).filter(Permission.uuid.in_(permission_uuids))
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
271
|
+
|
|
272
|
+
with self._bulk_operation_context():
|
|
273
|
+
for permission in permissions:
|
|
274
|
+
self.enforcer.remove_filtered_policy(1,permission.resource)
|
|
141
275
|
|
|
142
276
|
permissions.delete(synchronize_session=False)
|
|
143
277
|
session.commit()
|
|
@@ -150,63 +284,161 @@ class RBACService:
|
|
|
150
284
|
Assign permissions to a user
|
|
151
285
|
"""
|
|
152
286
|
with self.db() as session:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
287
|
+
try:
|
|
288
|
+
current_permissions = session.query(UserPermission).filter(UserPermission.user_uuid==user_uuid).all()
|
|
289
|
+
current_permission_uuids = [permission.permission_uuid for permission in current_permissions]
|
|
290
|
+
remove_permissions = set(current_permission_uuids) - set(permission_uuids)
|
|
291
|
+
add_permissions = set(permission_uuids) - set(current_permission_uuids)
|
|
292
|
+
if remove_permissions:
|
|
293
|
+
self.revoke_user_permissions(user_uuid,list(remove_permissions))
|
|
294
|
+
if add_permissions:
|
|
295
|
+
self.attach_permissions_to_user(user_uuid,list(add_permissions))
|
|
296
|
+
return self.get_user_only_permissions(user_uuid)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise e
|
|
162
299
|
|
|
163
300
|
def attach_permissions_to_user(self, user_uuid: str, permission_uuids: List[str]):
|
|
301
|
+
"""
|
|
302
|
+
Attach permissions to user (optimized for distributed systems)
|
|
303
|
+
"""
|
|
164
304
|
with self.db() as session:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
305
|
+
try:
|
|
306
|
+
# Use bulk_insert_mappings for better performance
|
|
307
|
+
user_permissions_data = [
|
|
308
|
+
{"user_uuid": user_uuid, "permission_uuid": permission_uuid}
|
|
309
|
+
for permission_uuid in permission_uuids
|
|
310
|
+
]
|
|
311
|
+
session.bulk_insert_mappings(UserPermission, user_permissions_data)
|
|
312
|
+
session.commit()
|
|
171
313
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
self.enforcer.save_policy()
|
|
179
|
-
return user_permissions
|
|
314
|
+
# Fetch permissions and build policies
|
|
315
|
+
permissions = session.query(Permission).filter(Permission.uuid.in_(permission_uuids)).all()
|
|
316
|
+
policies = [
|
|
317
|
+
[f"user:{user_uuid}", permission.resource, permission.action, permission.module]
|
|
318
|
+
for permission in permissions
|
|
319
|
+
]
|
|
180
320
|
|
|
321
|
+
# Use context manager for optimized bulk operation
|
|
322
|
+
with self._bulk_operation_context():
|
|
323
|
+
self.enforcer.add_policies(policies)
|
|
324
|
+
|
|
325
|
+
return self.get_user_only_permissions(user_uuid)
|
|
326
|
+
except Exception as e:
|
|
327
|
+
raise e
|
|
181
328
|
|
|
182
329
|
def revoke_user_permissions(self, user_uuid: str, permission_uuids: List[str]):
|
|
330
|
+
"""
|
|
331
|
+
Revoke permissions from user (optimized for distributed systems)
|
|
332
|
+
"""
|
|
183
333
|
with self.db() as session:
|
|
184
|
-
|
|
185
|
-
UserPermission.
|
|
186
|
-
|
|
187
|
-
|
|
334
|
+
try:
|
|
335
|
+
user_permissions = session.query(UserPermission).filter(
|
|
336
|
+
UserPermission.user_uuid == user_uuid,
|
|
337
|
+
UserPermission.permission_uuid.in_(permission_uuids)
|
|
338
|
+
)
|
|
188
339
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
340
|
+
permissions = session.query(Permission).filter(Permission.uuid.in_(permission_uuids)).all()
|
|
341
|
+
policies = [
|
|
342
|
+
[f"user:{user_uuid}", permission.resource, permission.action, permission.module]
|
|
343
|
+
for permission in permissions
|
|
344
|
+
]
|
|
345
|
+
with self._bulk_operation_context():
|
|
346
|
+
self.enforcer.remove_policies(policies)
|
|
347
|
+
|
|
348
|
+
user_permissions.delete(synchronize_session=False)
|
|
349
|
+
session.commit()
|
|
350
|
+
return self.get_user_only_permissions(user_uuid)
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise e
|
|
353
|
+
|
|
354
|
+
def revoke_all_user_access(self, user_uuid: str) -> dict:
|
|
355
|
+
"""
|
|
356
|
+
Revoke all roles and permissions from a user in a single operation.
|
|
357
|
+
This is typically used when soft-deleting a user.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
user_uuid: The UUID of the user to revoke all access from
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
dict: Summary of revoked roles and permissions
|
|
364
|
+
"""
|
|
365
|
+
with self.db() as session:
|
|
366
|
+
try:
|
|
367
|
+
if not session.is_active:
|
|
368
|
+
session.begin()
|
|
369
|
+
|
|
370
|
+
# Get all user roles
|
|
371
|
+
user_roles = (
|
|
372
|
+
session.query(UserRole)
|
|
373
|
+
.options(joinedload(UserRole.role))
|
|
374
|
+
.filter(UserRole.user_uuid == user_uuid)
|
|
375
|
+
.all()
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
role_uuids = [ur.role.uuid for ur in user_roles] if user_roles else []
|
|
379
|
+
|
|
380
|
+
# Get all direct user permissions
|
|
381
|
+
user_permissions = (
|
|
382
|
+
session.query(UserPermission)
|
|
383
|
+
.options(joinedload(UserPermission.permission))
|
|
384
|
+
.filter(UserPermission.user_uuid == user_uuid)
|
|
385
|
+
.all()
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
permission_uuids = [up.permission.uuid for up in user_permissions] if user_permissions else []
|
|
389
|
+
|
|
390
|
+
# Revoke all roles
|
|
391
|
+
if role_uuids:
|
|
392
|
+
session.query(UserRole).filter(
|
|
393
|
+
UserRole.user_uuid == user_uuid,
|
|
394
|
+
UserRole.role_uuid.in_(role_uuids)
|
|
395
|
+
).delete(synchronize_session=False)
|
|
396
|
+
|
|
397
|
+
# Revoke all direct permissions and remove from Casbin
|
|
398
|
+
if permission_uuids:
|
|
399
|
+
permissions = session.query(Permission).filter(
|
|
400
|
+
Permission.uuid.in_(permission_uuids)
|
|
401
|
+
).all()
|
|
402
|
+
|
|
403
|
+
# Remove Casbin policies (optimized)
|
|
404
|
+
policies = [
|
|
405
|
+
[f"user:{user_uuid}", permission.resource, permission.action, permission.module]
|
|
406
|
+
for permission in permissions
|
|
407
|
+
]
|
|
408
|
+
with self._bulk_operation_context():
|
|
409
|
+
self.enforcer.remove_policies(policies)
|
|
410
|
+
|
|
411
|
+
# Delete from database
|
|
412
|
+
session.query(UserPermission).filter(
|
|
413
|
+
UserPermission.user_uuid == user_uuid,
|
|
414
|
+
UserPermission.permission_uuid.in_(permission_uuids)
|
|
415
|
+
).delete(synchronize_session=False)
|
|
416
|
+
|
|
417
|
+
session.commit()
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
"user_uuid": user_uuid,
|
|
421
|
+
"roles_revoked": len(role_uuids),
|
|
422
|
+
"permissions_revoked": len(permission_uuids),
|
|
423
|
+
"role_uuids": role_uuids,
|
|
424
|
+
"permission_uuids": permission_uuids
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
except Exception as e:
|
|
428
|
+
raise e
|
|
196
429
|
|
|
197
|
-
user_permissions.delete(synchronize_session=False)
|
|
198
|
-
session.commit()
|
|
199
|
-
return True
|
|
200
|
-
|
|
201
430
|
def list_roles(self) -> Any:
|
|
202
431
|
"""
|
|
203
432
|
Get the list of all roles
|
|
204
433
|
"""
|
|
205
434
|
with self.db() as session:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
435
|
+
try:
|
|
436
|
+
"""List all roles"""
|
|
437
|
+
total = session.query(Role).count()
|
|
438
|
+
roles = session.query(Role).all()
|
|
439
|
+
return {"roles": roles, "total": total}
|
|
440
|
+
except Exception as e:
|
|
441
|
+
raise e
|
|
210
442
|
|
|
211
443
|
def create_role(
|
|
212
444
|
self,
|
|
@@ -278,13 +510,13 @@ class RBACService:
|
|
|
278
510
|
]
|
|
279
511
|
session.bulk_insert_mappings(RolePermission, role_permissions)
|
|
280
512
|
|
|
281
|
-
# Batch add Casbin policies
|
|
513
|
+
# Batch add Casbin policies (optimized)
|
|
282
514
|
policies = [
|
|
283
515
|
[role.uuid, permission.resource, permission.action, permission.module]
|
|
284
516
|
for permission in existing_permissions
|
|
285
517
|
]
|
|
286
|
-
self.
|
|
287
|
-
|
|
518
|
+
with self._bulk_operation_context():
|
|
519
|
+
self.enforcer.add_policies(policies)
|
|
288
520
|
|
|
289
521
|
# Commit transaction
|
|
290
522
|
session.commit()
|
|
@@ -308,7 +540,7 @@ class RBACService:
|
|
|
308
540
|
if not role:
|
|
309
541
|
raise NotFoundError(detail="Requested role does not exist")
|
|
310
542
|
|
|
311
|
-
return role
|
|
543
|
+
return role
|
|
312
544
|
|
|
313
545
|
def update_role_permissions(
|
|
314
546
|
self,
|
|
@@ -318,6 +550,7 @@ class RBACService:
|
|
|
318
550
|
description: Optional[str] = None,
|
|
319
551
|
) -> Any:
|
|
320
552
|
"""Update role permissions by replacing all existing permissions with new ones"""
|
|
553
|
+
|
|
321
554
|
with self.db() as session:
|
|
322
555
|
try:
|
|
323
556
|
if not session.is_active:
|
|
@@ -354,50 +587,45 @@ class RBACService:
|
|
|
354
587
|
role.description = description
|
|
355
588
|
|
|
356
589
|
if permissions is not None:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
[role_uuid, permission.resource, permission.action, permission.module]
|
|
397
|
-
for permission in permissions_objs
|
|
398
|
-
]
|
|
399
|
-
self.enforcer.add_policies(policies)
|
|
400
|
-
self.enforcer.save_policy()
|
|
590
|
+
# Update permissions with optimized Casbin operations
|
|
591
|
+
with self._bulk_operation_context():
|
|
592
|
+
# Remove ALL existing policies for this role from Casbin
|
|
593
|
+
self.enforcer.remove_filtered_policy(0, str(role_uuid))
|
|
594
|
+
|
|
595
|
+
# Delete existing role permissions from database
|
|
596
|
+
session.query(RolePermission).filter(
|
|
597
|
+
RolePermission.role_uuid == role_uuid
|
|
598
|
+
).delete(synchronize_session=False)
|
|
599
|
+
|
|
600
|
+
# Add new permissions if provided
|
|
601
|
+
if permissions:
|
|
602
|
+
# Fetch all permissions in a single query
|
|
603
|
+
permissions_objs = (
|
|
604
|
+
session.query(Permission)
|
|
605
|
+
.filter(Permission.uuid.in_(permissions))
|
|
606
|
+
.all()
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
found_permission_ids = {p.uuid for p in permissions_objs}
|
|
610
|
+
missing_permission_ids = set(permissions) - found_permission_ids
|
|
611
|
+
if missing_permission_ids:
|
|
612
|
+
raise NotFoundError(
|
|
613
|
+
detail=f"Permissions with UUIDs '{', '.join(missing_permission_ids)}' not found"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# Bulk insert role permissions
|
|
617
|
+
role_permissions = [
|
|
618
|
+
{"role_uuid": role_uuid, "permission_uuid": permission.uuid}
|
|
619
|
+
for permission in permissions_objs
|
|
620
|
+
]
|
|
621
|
+
session.bulk_insert_mappings(RolePermission, role_permissions)
|
|
622
|
+
|
|
623
|
+
# Add new Casbin policies
|
|
624
|
+
policies = [
|
|
625
|
+
[role_uuid, permission.resource, permission.action, permission.module]
|
|
626
|
+
for permission in permissions_objs
|
|
627
|
+
]
|
|
628
|
+
self.enforcer.add_policies(policies)
|
|
401
629
|
|
|
402
630
|
session.commit()
|
|
403
631
|
|
|
@@ -435,12 +663,11 @@ class RBACService:
|
|
|
435
663
|
[role.uuid, permission.resource, permission.action, permission.module]
|
|
436
664
|
for permission in role.permissions
|
|
437
665
|
]
|
|
438
|
-
|
|
439
|
-
# Remove all policies at once
|
|
440
|
-
if remove_policies:
|
|
441
666
|
|
|
442
|
-
|
|
443
|
-
|
|
667
|
+
# Remove all policies at once (optimized)
|
|
668
|
+
if remove_policies:
|
|
669
|
+
with self._bulk_operation_context():
|
|
670
|
+
self.enforcer.remove_policies(remove_policies)
|
|
444
671
|
|
|
445
672
|
# Delete role (cascade will handle role_permissions and user_roles)
|
|
446
673
|
session.delete(role)
|
|
@@ -561,13 +788,13 @@ class RBACService:
|
|
|
561
788
|
)
|
|
562
789
|
).delete(synchronize_session=False)
|
|
563
790
|
|
|
564
|
-
# Remove Casbin policies
|
|
791
|
+
# Remove Casbin policies (optimized)
|
|
565
792
|
policies_to_remove = [
|
|
566
793
|
[role.uuid, permission.resource, permission.action, permission.module]
|
|
567
794
|
for permission in permissions_to_revoke
|
|
568
795
|
]
|
|
569
|
-
self.
|
|
570
|
-
|
|
796
|
+
with self._bulk_operation_context():
|
|
797
|
+
self.enforcer.remove_policies(policies_to_remove)
|
|
571
798
|
|
|
572
799
|
session.commit()
|
|
573
800
|
|
|
@@ -629,13 +856,13 @@ class RBACService:
|
|
|
629
856
|
]
|
|
630
857
|
session.bulk_insert_mappings(RolePermission, role_permissions)
|
|
631
858
|
|
|
632
|
-
# Add Casbin policies
|
|
859
|
+
# Add Casbin policies (optimized)
|
|
633
860
|
policies_to_add = [
|
|
634
861
|
[role.uuid, permission.resource, permission.action, permission.module]
|
|
635
862
|
for permission in new_permissions
|
|
636
863
|
]
|
|
637
|
-
self.
|
|
638
|
-
|
|
864
|
+
with self._bulk_operation_context():
|
|
865
|
+
self.enforcer.add_policies(policies_to_add)
|
|
639
866
|
|
|
640
867
|
session.commit()
|
|
641
868
|
|
|
@@ -816,6 +1043,8 @@ class RBACService:
|
|
|
816
1043
|
# Try with module first
|
|
817
1044
|
if self.enforcer.enforce(role.uuid, resource, action, module):
|
|
818
1045
|
return True
|
|
1046
|
+
if self.enforcer.enforce(role.name, resource, action, module):
|
|
1047
|
+
return True
|
|
819
1048
|
return False
|
|
820
1049
|
|
|
821
1050
|
def check_permission_by_role(
|
|
@@ -825,6 +1054,11 @@ class RBACService:
|
|
|
825
1054
|
if self.enforcer.enforce(role_uuid, resource, action, module):
|
|
826
1055
|
return True
|
|
827
1056
|
return False
|
|
1057
|
+
|
|
1058
|
+
def check_permission_by_user(self,user_uuid:str,resource:str,action:str,module:str) -> bool:
|
|
1059
|
+
if self.enforcer.enforce(f"user:{user_uuid}", resource, action, module):
|
|
1060
|
+
return True
|
|
1061
|
+
return False
|
|
828
1062
|
|
|
829
1063
|
def get_role(self, role_uuid: str,session: Optional[Session] = None) -> Any:
|
|
830
1064
|
"""Get role by uuid"""
|
|
@@ -839,3 +1073,55 @@ class RBACService:
|
|
|
839
1073
|
else:
|
|
840
1074
|
with self.db() as session:
|
|
841
1075
|
return query_role(session)
|
|
1076
|
+
|
|
1077
|
+
def is_watcher_active(self) -> bool:
|
|
1078
|
+
"""Check if Redis watcher is active and connected"""
|
|
1079
|
+
return self.watcher is not None and hasattr(self.watcher, 'pub_client') and self.watcher.pub_client is not None
|
|
1080
|
+
|
|
1081
|
+
def get_watcher_status(self) -> dict:
|
|
1082
|
+
"""Get detailed watcher status information"""
|
|
1083
|
+
if not self.is_watcher_active():
|
|
1084
|
+
return {
|
|
1085
|
+
"active": False,
|
|
1086
|
+
"message": "Watcher not initialized or not active"
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
try:
|
|
1090
|
+
# Test Redis connection
|
|
1091
|
+
self.watcher.pub_client.ping()
|
|
1092
|
+
return {
|
|
1093
|
+
"active": True,
|
|
1094
|
+
"host": self.watcher.pub_client.connection_pool.connection_kwargs.get('host'),
|
|
1095
|
+
"port": self.watcher.pub_client.connection_pool.connection_kwargs.get('port'),
|
|
1096
|
+
"channel": getattr(self.watcher, 'channel', 'unknown'),
|
|
1097
|
+
"connected": True
|
|
1098
|
+
}
|
|
1099
|
+
except Exception as e:
|
|
1100
|
+
return {
|
|
1101
|
+
"active": False,
|
|
1102
|
+
"error": str(e),
|
|
1103
|
+
"message": "Watcher exists but Redis connection failed"
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
def reload_policies(self) -> bool:
|
|
1107
|
+
"""Manually reload policies from database"""
|
|
1108
|
+
try:
|
|
1109
|
+
self.enforcer.load_policy()
|
|
1110
|
+
return True
|
|
1111
|
+
except Exception as e:
|
|
1112
|
+
logger.error(f"Failed to reload policies: {e}")
|
|
1113
|
+
return False
|
|
1114
|
+
|
|
1115
|
+
def get_policy_count(self) -> int:
|
|
1116
|
+
"""Get the total number of policies in the enforcer"""
|
|
1117
|
+
return len(self.enforcer.get_policy())
|
|
1118
|
+
|
|
1119
|
+
def clear_all_policies(self) -> bool:
|
|
1120
|
+
"""Clear all policies from the enforcer (use with caution)"""
|
|
1121
|
+
try:
|
|
1122
|
+
self.enforcer.clear_policy()
|
|
1123
|
+
self.enforcer.save_policy()
|
|
1124
|
+
return True
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
logger.error(f"Failed to clear policies: {e}")
|
|
1127
|
+
return False
|