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.
@@ -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 sqlalchemy import and_, select
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
- def _initialize_casbin(self):
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
- # Load policies
55
- self.enforcer.load_policy()
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.enforcer.add_policy(role,resource,action,module)
62
- self.enforcer.save_policy()
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.enforcer.remove_policy(role,resource,action,module)
69
- self.enforcer.save_policy()
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.enforcer.add_policies(policies)
76
- self.enforcer.save_policy()
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.enforcer.remove_policies(policies)
83
- self.enforcer.save_policy()
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.enforcer.remove_filtered_policy(index,value)
99
- self.enforcer.save_policy()
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
- permission_objs = [Permission(**permission.model_dump()) for permission in permissions]
108
- session.bulk_save_objects(permission_objs)
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 permission_objs
214
+ return add_permissions
111
215
  except Exception as e:
112
216
  raise e
113
217
 
114
- async def get_permissions_by_condition(self, condition: dict, use_filter_by: bool = True):
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 a condition dict.
117
- If use_filter_by is True, assumes all conditions are `==`.
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 a permission by uuids
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
- for permission in permissions:
139
- self.enforcer.remove_filtered_policy(1,permission.resource)
140
- self.enforcer.save_policy()
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
- current_permissions = session.query(UserPermission).filter(UserPermission.user_uuid==user_uuid).all()
154
- current_permission_uuids = [permission.permission_uuid for permission in current_permissions]
155
- remove_permissions = set(current_permission_uuids) - set(permission_uuids)
156
- add_permissions = set(permission_uuids) - set(current_permission_uuids)
157
- if remove_permissions:
158
- self.revoke_user_permissions(user_uuid,list(remove_permissions))
159
- if add_permissions:
160
- self.attach_permissions_to_user(user_uuid,list(add_permissions))
161
- return True
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
- user_permissions = [
166
- UserPermission(user_uuid=user_uuid, permission_uuid=permission_uuid)
167
- for permission_uuid in permission_uuids
168
- ]
169
- session.bulk_save_objects(user_permissions)
170
- session.commit()
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
- permissions = session.query(Permission).filter(Permission.uuid.in_(permission_uuids)).all()
173
- policies = [
174
- [f"user:{user_uuid}", permission.resource, permission.action, permission.module]
175
- for permission in permissions
176
- ]
177
- self.enforcer.add_policies(policies)
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
- user_permissions = session.query(UserPermission).filter(
185
- UserPermission.user_uuid == user_uuid,
186
- UserPermission.permission_uuid.in_(permission_uuids)
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
- permissions = session.query(Permission).filter(Permission.uuid.in_(permission_uuids)).all()
190
- policies = [
191
- [f"user:{user_uuid}", permission.resource, permission.action, permission.module]
192
- for permission in permissions
193
- ]
194
- self.enforcer.remove_policies(policies)
195
- self.enforcer.save_policy()
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
- """List all roles"""
207
- total = session.query(Role).count()
208
- roles = session.query(Role).all()
209
- return {"roles": roles, "total": total}
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.enforcer.add_policies(policies)
287
- self.enforcer.save_policy()
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
- existing_permissions = role.permissions
358
-
359
- # Remove Casbin policies for existing permissions
360
- remove_policies = [
361
- [role_uuid, existing_permission.resource, existing_permission.action, existing_permission.module]
362
- for existing_permission in existing_permissions
363
- ]
364
- self.enforcer.remove_policies(remove_policies)
365
- self.enforcer.save_policy()
366
-
367
- # Delete existing role permissions
368
- session.query(RolePermission).filter(
369
- RolePermission.role_uuid == role_uuid
370
- ).delete(synchronize_session=False)
371
-
372
- if permissions:
373
- # Fetch all permissions in a single query
374
- permissions_objs = (
375
- session.query(Permission)
376
- .filter(Permission.uuid.in_(permissions))
377
- .all()
378
- )
379
-
380
- found_permission_ids = {p.uuid for p in permissions_objs}
381
- missing_permission_ids = set(permissions) - found_permission_ids
382
- if missing_permission_ids:
383
- raise NotFoundError(
384
- detail=f"Permissions with UUIDs '{', '.join(missing_permission_ids)}' not found"
385
- )
386
-
387
- # Bulk insert role permissions
388
- role_permissions = [
389
- {"role_uuid": role_uuid, "permission_uuid": permission.uuid}
390
- for permission in permissions_objs
391
- ]
392
- session.bulk_insert_mappings(RolePermission, role_permissions)
393
-
394
- # Add Casbin policies
395
- policies = [
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
- self.enforcer.remove_policies(remove_policies)
443
- self.enforcer.save_policy()
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.enforcer.remove_policies(policies_to_remove)
570
- self.enforcer.save_policy()
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.enforcer.add_policies(policies_to_add)
638
- self.enforcer.save_policy()
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