abs-auth-rbac-core 0.3.1__tar.gz → 0.3.13__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.
Files changed (35) hide show
  1. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/PKG-INFO +4 -2
  2. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/auth/__init__.py +15 -0
  3. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/auth/auth_functions.py +4 -1
  4. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/auth/middleware.py +245 -0
  5. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/user.py +1 -0
  6. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/rbac/service.py +145 -51
  7. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/repository/__init__.py +4 -0
  8. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/repository/permission_repository.py +12 -0
  9. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/repository/role_repository.py +18 -0
  10. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/service/__init__.py +4 -0
  11. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/service/permission_service.py +15 -0
  12. abs_auth_rbac_core-0.3.13/abs_auth_rbac_core/service/role_service.py +18 -0
  13. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/util/permission_constants.py +27 -1
  14. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/pyproject.toml +4 -2
  15. abs_auth_rbac_core-0.3.1/abs_auth_rbac_core/auth/__init__.py +0 -3
  16. abs_auth_rbac_core-0.3.1/abs_auth_rbac_core/auth/middleware.py +0 -51
  17. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/README.md +0 -0
  18. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/__init__.py +0 -0
  19. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/auth/jwt_functions.py +0 -0
  20. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/__init__.py +0 -0
  21. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/base_model.py +0 -0
  22. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/gov_casbin_rule.py +0 -0
  23. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/permissions.py +0 -0
  24. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/rbac_model.py +0 -0
  25. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/role_permission.py +0 -0
  26. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/roles.py +0 -0
  27. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/seeder/permission_seeder.py +0 -0
  28. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/user_permission.py +0 -0
  29. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/models/user_role.py +0 -0
  30. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/rbac/__init__.py +0 -0
  31. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/rbac/decorator.py +0 -0
  32. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/rbac/policy.conf +0 -0
  33. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/schema/__init__.py +0 -0
  34. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/schema/permission.py +0 -0
  35. {abs_auth_rbac_core-0.3.1 → abs_auth_rbac_core-0.3.13}/abs_auth_rbac_core/util/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: abs-auth-rbac-core
3
- Version: 0.3.1
3
+ Version: 0.3.13
4
4
  Summary: RBAC and Auth core utilities including JWT token management.
5
5
  License: MIT
6
6
  Author: AutoBridgeSystems
@@ -12,7 +12,9 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: abs-exception-core (>=0.2.0,<0.3.0)
15
- Requires-Dist: abs-utils (>=0.4.0,<0.5.0)
15
+ Requires-Dist: abs-nosql-repository-core (>=0.11.8,<0.12.0)
16
+ Requires-Dist: abs-repository-core (>=0.3.0,<0.4.0)
17
+ Requires-Dist: abs-utils (>=0.4.1,<0.5.0)
16
18
  Requires-Dist: casbin (>=1.41.0,<2.0.0)
17
19
  Requires-Dist: casbin-redis-watcher (>=1.3.0,<2.0.0)
18
20
  Requires-Dist: casbin-sqlalchemy-adapter (>=1.4.0,<2.0.0)
@@ -0,0 +1,15 @@
1
+ from .jwt_functions import JWTFunctions
2
+ from .middleware import (
3
+ CustomHTTPBearer,
4
+ auth_middleware,
5
+ contacts_auth_middleware,
6
+ unified_auth_middleware
7
+ )
8
+
9
+ __all__ = [
10
+ "JWTFunctions",
11
+ "CustomHTTPBearer",
12
+ "auth_middleware",
13
+ "contacts_auth_middleware",
14
+ "unified_auth_middleware"
15
+ ]
@@ -20,7 +20,10 @@ def get_user_by_attribute(db_session: Callable[...,Any],attribute: str, value: s
20
20
  if not hasattr(Users, attribute):
21
21
  raise ValidationError(detail=f"Attribute {attribute} does not exist on the User model")
22
22
 
23
- user = session.query(Users).filter(getattr(Users, attribute) == value).first()
23
+ user = session.query(Users).filter(
24
+ getattr(Users, attribute) == value,
25
+ Users.deleted_at.is_(None) # Filter out soft-deleted users
26
+ ).first()
24
27
 
25
28
  if not user:
26
29
  raise NotFoundError(detail="User not found")
@@ -0,0 +1,245 @@
1
+ from fastapi import Depends, Request
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from fastapi import HTTPException
4
+ import logging
5
+ from typing import Callable, Any, Optional
6
+
7
+ from .jwt_functions import JWTFunctions
8
+ from .auth_functions import get_user_by_attribute
9
+ from abs_exception_core.exceptions import UnauthorizedError, AuthError, NotFoundError
10
+ from abs_nosql_repository_core.repository import BaseRepository
11
+ from fastapi.security.utils import get_authorization_scheme_param
12
+
13
+ class CustomHTTPBearer(HTTPBearer):
14
+ def __init__(self, **kwargs):
15
+ super().__init__(**kwargs)
16
+
17
+ async def __call__(self, request: Request) -> Optional[HTTPAuthorizationCredentials]:
18
+ authorization = request.headers.get("Authorization")
19
+ scheme, credentials = get_authorization_scheme_param(authorization)
20
+
21
+ if not (authorization and scheme and credentials):
22
+ if self.auto_error:
23
+ raise UnauthorizedError(detail="Invalid authentication credentials")
24
+ else:
25
+ return None
26
+
27
+ if scheme.lower() != "bearer":
28
+ if self.auto_error:
29
+ raise UnauthorizedError(detail="Invalid authentication credentials")
30
+ else:
31
+ return None
32
+
33
+ return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
34
+
35
+ security = CustomHTTPBearer()
36
+ # security = HTTPBearer()
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ # Dependency acting like per-route middleware
41
+ def auth_middleware(
42
+ db_session: Callable[...,Any],
43
+ jwt_secret_key:str,
44
+ jwt_algorithm:str
45
+ ):
46
+ """
47
+ This middleware is used for authentication of the user.
48
+ Args:
49
+ db_session: Callable[...,Any]: Session of the SQLAlchemy database engine
50
+ jwt_secret_key: Secret key of the JWT for jwt functions
51
+ jwt_algorithm: Algorithm used for JWT
52
+
53
+ Returns:
54
+ """
55
+ async def get_auth(request: Request, token: HTTPAuthorizationCredentials = Depends(security)):
56
+ jwt_functions = JWTFunctions(secret_key=jwt_secret_key,algorithm=jwt_algorithm)
57
+ try:
58
+ if not token or not token.credentials:
59
+ raise UnauthorizedError(detail="Invalid authentication credentials")
60
+
61
+ payload = jwt_functions.get_data(token=token.credentials)
62
+ uuid = payload.get("uuid")
63
+
64
+ user = get_user_by_attribute(db_session=db_session,attribute="uuid", value=uuid)
65
+
66
+ if not user:
67
+ logger.error(f"Authentication failed: User with id {uuid} not found")
68
+ raise UnauthorizedError(detail="Authentication failed")
69
+
70
+ # Attach user to request state
71
+ request.state.user = user
72
+ return user
73
+
74
+ except UnauthorizedError as e:
75
+ logger.error(e)
76
+ raise
77
+ except Exception as e:
78
+ logger.error(f"Authentication error: {str(e)}", exc_info=True)
79
+ raise UnauthorizedError(detail="Authentication failed")
80
+
81
+ return get_auth
82
+
83
+
84
+ def contacts_auth_middleware(
85
+ entity_records_service,
86
+ jwt_secret_key: str,
87
+ jwt_algorithm: str
88
+ ):
89
+ """
90
+ Contact authentication middleware using EntityRecordsService.
91
+
92
+ This middleware validates contact JWT tokens and attaches contact data to request.state.
93
+
94
+ JWT Payload Structure:
95
+ {
96
+ "sub": "contact@example.com",
97
+ "cid": "contact_record_id", # KEY IDENTIFIER for contacts
98
+ "uuid": "unique-uuid",
99
+ "exp": 1234567890,
100
+ "iat": 1234567890
101
+ }
102
+
103
+ Args:
104
+ entity_records_service: EntityRecordsService for entity operations
105
+ jwt_secret_key: JWT secret key
106
+ jwt_algorithm: JWT algorithm (e.g., HS256)
107
+
108
+ Returns:
109
+ FastAPI dependency function that validates contact authentication
110
+ """
111
+
112
+ async def get_auth(
113
+ request: Request,
114
+ token: HTTPAuthorizationCredentials = Depends(security)
115
+ ):
116
+ jwt_functions = JWTFunctions(
117
+ secret_key=jwt_secret_key,
118
+ algorithm=jwt_algorithm
119
+ )
120
+
121
+ try:
122
+ if not token or not token.credentials:
123
+ raise UnauthorizedError(detail="Invalid authentication credentials")
124
+
125
+ # Decode JWT and extract payload
126
+ payload = jwt_functions.get_data(token=token.credentials)
127
+
128
+ # Extract cid (required for contact authentication)
129
+ cid = payload.get("cid")
130
+ if not cid:
131
+ raise UnauthorizedError(detail="Invalid contact token")
132
+
133
+ # Extract app_id from path parameters
134
+ app_id = request.path_params.get("app_id")
135
+ if not app_id:
136
+ raise UnauthorizedError(detail="App ID is required")
137
+
138
+ logger.info(f"Contact auth: app_id={app_id}, cid={cid}")
139
+
140
+ # Get app configuration (apps collection is not an entity collection)
141
+ # We still need BaseRepository for this until apps get moved to a service
142
+ base_repo = BaseRepository(db=entity_records_service.repository.db)
143
+ app = await base_repo.get_by_attr("id", app_id, collection_name="apps")
144
+ if not app:
145
+ logger.error(f"App not found: {app_id}")
146
+ raise UnauthorizedError(detail="App not found")
147
+
148
+ # Extract contact entity ID from app metadata
149
+ metadata = app.get("metadata", {})
150
+ if not metadata:
151
+ raise UnauthorizedError(detail="App doesn't have support for contacts")
152
+
153
+ features_entities = metadata.get("features_entities", {})
154
+ if "CONTACT" not in features_entities:
155
+ raise UnauthorizedError(detail="App doesn't have support for contacts")
156
+
157
+ contact_config = features_entities.get("CONTACT", {})
158
+ contact_entity_id = contact_config.get("Contacts")
159
+
160
+ if not contact_entity_id:
161
+ raise UnauthorizedError(detail="Contact entity not configured")
162
+
163
+ logger.info(f"Contact entity ID: {contact_entity_id}")
164
+
165
+ # Get contact record using EntityRecordsService
166
+ try:
167
+ contact = await entity_records_service.get_record_by_id(
168
+ entity_id=contact_entity_id,
169
+ record_id=cid,
170
+ convert_ids=False # Keep ObjectIds for internal use
171
+ )
172
+ except NotFoundError:
173
+ logger.error(f"Authentication failed: contact with id {cid} not found")
174
+ raise UnauthorizedError(detail="Authentication failed")
175
+
176
+ logger.info(f"✅ Contact authenticated: {cid}")
177
+
178
+ # Attach contact to request state
179
+ request.state.contact = contact
180
+ request.state.contact_entity_id = contact_entity_id
181
+
182
+ return contact
183
+
184
+ except UnauthorizedError:
185
+ raise
186
+ except Exception as e:
187
+ logger.error(f"Authentication error: {str(e)}", exc_info=True)
188
+ raise UnauthorizedError(detail="Authentication failed")
189
+
190
+ return get_auth
191
+
192
+
193
+ def unified_auth_middleware(
194
+ jwt_secret_key: str,
195
+ jwt_algorithm: str,
196
+ get_user_auth_middleware: Callable,
197
+ get_contact_auth_middleware: Callable,
198
+ ):
199
+ """
200
+ Unified authentication middleware that intelligently routes to the correct
201
+ authentication flow based on JWT payload.
202
+
203
+ Detection Logic:
204
+ - If JWT contains "cid" field → Contact authentication
205
+ - Otherwise → User authentication
206
+
207
+ Benefits:
208
+ - Single entry point for all authentication
209
+ - JWT decoded only once for efficiency
210
+ - Transparent routing based on token type
211
+ - No code changes needed in route handlers
212
+
213
+ Args:
214
+ jwt_secret_key: JWT secret key
215
+ jwt_algorithm: JWT algorithm (e.g., HS256)
216
+ get_user_auth_middleware: User auth middleware callable
217
+ get_contact_auth_middleware: Contact auth middleware callable
218
+
219
+ Returns:
220
+ FastAPI dependency that handles unified authentication
221
+ """
222
+ async def get_auth(
223
+ request: Request,
224
+ token: HTTPAuthorizationCredentials = Depends(security)
225
+ ):
226
+ if not token or not token.credentials:
227
+ raise UnauthorizedError(detail="Invalid authentication credentials")
228
+
229
+ # Decode JWT once to inspect payload
230
+ jwt_functions = JWTFunctions(
231
+ secret_key=jwt_secret_key,
232
+ algorithm=jwt_algorithm
233
+ )
234
+ payload = jwt_functions.get_data(token=token.credentials)
235
+
236
+ # Route based on payload content
237
+ cid = payload.get("cid")
238
+ if cid:
239
+ # Contact flow: JWT contains contact ID
240
+ return await get_contact_auth_middleware(request=request, token=token)
241
+
242
+ # User flow: Standard user authentication
243
+ return await get_user_auth_middleware(request=request, token=token)
244
+
245
+ return get_auth
@@ -12,6 +12,7 @@ class Users(BaseModel):
12
12
  name = Column(String(100), nullable=False)
13
13
  is_active = Column(Boolean, default=True)
14
14
  last_login_at = Column(DateTime, nullable=True)
15
+ deleted_at = Column(DateTime, nullable=True) # Soft delete timestamp
15
16
 
16
17
  # Relationships
17
18
  roles = relationship(
@@ -4,7 +4,7 @@ from pydantic import BaseModel
4
4
  import casbin
5
5
  from casbin_sqlalchemy_adapter import Adapter
6
6
  from casbin_redis_watcher import RedisWatcher, WatcherOptions,new_watcher
7
- from sqlalchemy import and_, select
7
+ from sqlalchemy import and_, or_, select
8
8
  from sqlalchemy.orm import Session, joinedload
9
9
  from ..schema import CreatePermissionSchema
10
10
  from ..models import (
@@ -65,6 +65,7 @@ class RBACService:
65
65
  self.enforcer = casbin.Enforcer(
66
66
  policy_path, adapter
67
67
  )
68
+ self.enforcer.enable_auto_save(True)
68
69
  # Load policies
69
70
  self.enforcer.load_policy()
70
71
 
@@ -181,23 +182,41 @@ class RBACService:
181
182
  except Exception as e:
182
183
  raise e
183
184
 
184
- async def get_permissions_by_condition(self, condition: dict, use_filter_by: bool = True):
185
+ def build_filter(self,cond: dict):
186
+ if "and" in cond:
187
+ return and_(*[self.build_filter(c) for c in cond["and"]])
188
+ elif "or" in cond:
189
+ return or_(*[self.build_filter(c) for c in cond["or"]])
190
+ else:
191
+ # Multiple simple field=value pairs in the same dict
192
+ return and_(*[
193
+ getattr(Permission, field) == value
194
+ for field, value in cond.items()
195
+ ])
196
+
197
+
198
+ async def get_permissions_by_condition(self, condition: dict):
185
199
  """
186
- Get permission(s) based on a condition dict.
187
- If use_filter_by is True, assumes all conditions are `==`.
200
+ Get permission(s) based on nested logical conditions.
201
+
202
+ Example:
203
+ {
204
+ "and": [
205
+ {"entity_id": "123"},
206
+ {"or": [
207
+ {"user_id": "456"},
208
+ {"group_id": "789"}
209
+ ]}
210
+ ]
211
+ }
188
212
  """
189
213
  with self.db() as session:
190
214
  try:
191
- query = session.query(Permission)
192
- if use_filter_by:
193
- query = query.filter_by(**condition)
194
- else:
195
- filters = [getattr(Permission, k) == v for k, v in condition.items()]
196
- query = query.filter(*filters)
215
+ query = session.query(Permission).filter(self.build_filter(condition))
197
216
  return query.all()
198
217
  except Exception as e:
199
218
  raise e
200
-
219
+
201
220
  async def delete_permission_by_uuids(self,permission_uuids:List[str]):
202
221
  """
203
222
  Delete a permission by uuids
@@ -278,7 +297,85 @@ class RBACService:
278
297
  return self.get_user_only_permissions(user_uuid)
279
298
  except Exception as e:
280
299
  raise e
281
-
300
+
301
+ def revoke_all_user_access(self, user_uuid: str) -> dict:
302
+ """
303
+ Revoke all roles and permissions from a user in a single operation.
304
+ This is typically used when soft-deleting a user.
305
+
306
+ Args:
307
+ user_uuid: The UUID of the user to revoke all access from
308
+
309
+ Returns:
310
+ dict: Summary of revoked roles and permissions
311
+ """
312
+ with self.db() as session:
313
+ try:
314
+ if not session.is_active:
315
+ session.begin()
316
+
317
+ # Get all user roles
318
+ user_roles = (
319
+ session.query(UserRole)
320
+ .options(joinedload(UserRole.role))
321
+ .filter(UserRole.user_uuid == user_uuid)
322
+ .all()
323
+ )
324
+
325
+ role_uuids = [ur.role.uuid for ur in user_roles] if user_roles else []
326
+
327
+ # Get all direct user permissions
328
+ user_permissions = (
329
+ session.query(UserPermission)
330
+ .options(joinedload(UserPermission.permission))
331
+ .filter(UserPermission.user_uuid == user_uuid)
332
+ .all()
333
+ )
334
+
335
+ permission_uuids = [up.permission.uuid for up in user_permissions] if user_permissions else []
336
+
337
+ # Revoke all roles
338
+ if role_uuids:
339
+ session.query(UserRole).filter(
340
+ UserRole.user_uuid == user_uuid,
341
+ UserRole.role_uuid.in_(role_uuids)
342
+ ).delete(synchronize_session=False)
343
+
344
+ # Revoke all direct permissions and remove from Casbin
345
+ if permission_uuids:
346
+ permissions = session.query(Permission).filter(
347
+ Permission.uuid.in_(permission_uuids)
348
+ ).all()
349
+
350
+ # Remove Casbin policies
351
+ policies = [
352
+ [f"user:{user_uuid}", permission.resource, permission.action, permission.module]
353
+ for permission in permissions
354
+ ]
355
+ removed_policies = self.enforcer.remove_policies(policies)
356
+ if removed_policies:
357
+ self.enforcer.save_policy()
358
+ self.enforcer.load_policy()
359
+
360
+ # Delete from database
361
+ session.query(UserPermission).filter(
362
+ UserPermission.user_uuid == user_uuid,
363
+ UserPermission.permission_uuid.in_(permission_uuids)
364
+ ).delete(synchronize_session=False)
365
+
366
+ session.commit()
367
+
368
+ return {
369
+ "user_uuid": user_uuid,
370
+ "roles_revoked": len(role_uuids),
371
+ "permissions_revoked": len(permission_uuids),
372
+ "role_uuids": role_uuids,
373
+ "permission_uuids": permission_uuids
374
+ }
375
+
376
+ except Exception as e:
377
+ raise e
378
+
282
379
  def list_roles(self) -> Any:
283
380
  """
284
381
  Get the list of all roles
@@ -392,7 +489,7 @@ class RBACService:
392
489
  if not role:
393
490
  raise NotFoundError(detail="Requested role does not exist")
394
491
 
395
- return role
492
+ return role
396
493
 
397
494
  def update_role_permissions(
398
495
  self,
@@ -402,6 +499,7 @@ class RBACService:
402
499
  description: Optional[str] = None,
403
500
  ) -> Any:
404
501
  """Update role permissions by replacing all existing permissions with new ones"""
502
+
405
503
  with self.db() as session:
406
504
  try:
407
505
  if not session.is_active:
@@ -438,50 +536,46 @@ class RBACService:
438
536
  role.description = description
439
537
 
440
538
  if permissions is not None:
441
- existing_permissions = role.permissions
442
-
443
- # Remove Casbin policies for existing permissions
444
- remove_policies = [
445
- [role_uuid, existing_permission.resource, existing_permission.action, existing_permission.module]
446
- for existing_permission in existing_permissions
447
- ]
448
- self.enforcer.remove_policies(remove_policies)
449
- self.enforcer.save_policy()
450
-
451
- # Delete existing role permissions
539
+ # Remove ALL existing policies for this role from Casbin
540
+ self.enforcer.remove_filtered_policy(0, str(role_uuid))
541
+
542
+ # Delete existing role permissions from database
452
543
  session.query(RolePermission).filter(
453
544
  RolePermission.role_uuid == role_uuid
454
545
  ).delete(synchronize_session=False)
455
546
 
456
- if permissions:
457
- # Fetch all permissions in a single query
458
- permissions_objs = (
459
- session.query(Permission)
460
- .filter(Permission.uuid.in_(permissions))
461
- .all()
462
- )
463
-
464
- found_permission_ids = {p.uuid for p in permissions_objs}
465
- missing_permission_ids = set(permissions) - found_permission_ids
466
- if missing_permission_ids:
467
- raise NotFoundError(
468
- detail=f"Permissions with UUIDs '{', '.join(missing_permission_ids)}' not found"
547
+ # Add new permissions if provided
548
+ if permissions:
549
+ # Fetch all permissions in a single query
550
+ permissions_objs = (
551
+ session.query(Permission)
552
+ .filter(Permission.uuid.in_(permissions))
553
+ .all()
469
554
  )
470
555
 
471
- # Bulk insert role permissions
472
- role_permissions = [
473
- {"role_uuid": role_uuid, "permission_uuid": permission.uuid}
474
- for permission in permissions_objs
475
- ]
476
- session.bulk_insert_mappings(RolePermission, role_permissions)
477
-
478
- # Add Casbin policies
479
- policies = [
480
- [role_uuid, permission.resource, permission.action, permission.module]
481
- for permission in permissions_objs
482
- ]
483
- self.enforcer.add_policies(policies)
484
- self.enforcer.save_policy()
556
+ found_permission_ids = {p.uuid for p in permissions_objs}
557
+ missing_permission_ids = set(permissions) - found_permission_ids
558
+ if missing_permission_ids:
559
+ raise NotFoundError(
560
+ detail=f"Permissions with UUIDs '{', '.join(missing_permission_ids)}' not found"
561
+ )
562
+
563
+ # Bulk insert role permissions
564
+ role_permissions = [
565
+ {"role_uuid": role_uuid, "permission_uuid": permission.uuid}
566
+ for permission in permissions_objs
567
+ ]
568
+ session.bulk_insert_mappings(RolePermission, role_permissions)
569
+
570
+ # Add new Casbin policies
571
+ policies = [
572
+ [role_uuid, permission.resource, permission.action, permission.module]
573
+ for permission in permissions_objs
574
+ ]
575
+ self.enforcer.add_policies(policies)
576
+
577
+ # Save all Casbin changes
578
+ # self.enforcer.save_policy()
485
579
 
486
580
  session.commit()
487
581
 
@@ -0,0 +1,4 @@
1
+ from .role_repository import RoleRepository
2
+ from .permission_repository import PermissionRepository
3
+
4
+ __all__ = ["RoleRepository", "PermissionRepository"]
@@ -0,0 +1,12 @@
1
+ from typing import Any, List, Optional, Callable
2
+ from sqlalchemy.orm import joinedload
3
+ from contextlib import AbstractContextManager
4
+ from sqlalchemy.orm import Session
5
+ from abs_repository_core.repository.base_repository import BaseRepository
6
+ from abs_repository_core.schemas.base_schema import FindBase, FindUniqueValues
7
+ from abs_auth_rbac_core.models.permissions import Permission
8
+
9
+ class PermissionRepository(BaseRepository):
10
+ def __init__(self, db: Callable[..., Session]):
11
+ self.db = db
12
+ super().__init__(db, Permission)
@@ -0,0 +1,18 @@
1
+ from typing import Any, List, Optional, Callable
2
+ from sqlalchemy.orm import joinedload
3
+ from contextlib import AbstractContextManager
4
+ from sqlalchemy.orm import Session
5
+ from abs_repository_core.repository.base_repository import BaseRepository
6
+ from abs_auth_rbac_core.models.roles import Role
7
+ from abs_exception_core.exceptions import NotFoundError
8
+ from abs_repository_core.schemas import FilterSchema, FindBase
9
+
10
+
11
+ class RoleRepository(BaseRepository):
12
+ def __init__(self, db: Callable[..., Session]):
13
+ self.db = db
14
+ super().__init__(db, Role)
15
+
16
+
17
+
18
+
@@ -0,0 +1,4 @@
1
+ from .role_service import RoleService
2
+ from .permission_service import PermissionService
3
+
4
+ __all__ = ["RoleService", "PermissionService"]
@@ -0,0 +1,15 @@
1
+ from typing import Any, List, Optional
2
+ from abs_repository_core.services.base_service import BaseService
3
+ from abs_repository_core.schemas.base_schema import FindBase, FindUniqueValues
4
+ from abs_auth_rbac_core.models.roles import Role
5
+ from abs_auth_rbac_core.repository.permission_repository import PermissionRepository
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class PermissionService(BaseService):
10
+ def __init__(self, repository:PermissionRepository):
11
+ super().__init__(repository)
12
+ self.repository = repository
13
+
14
+ def list_permissions(self, schema: FindBase, eager: bool = True):
15
+ return self.repository.read_by_options(schema, eager)
@@ -0,0 +1,18 @@
1
+ from typing import Any, List, Optional
2
+ from abs_repository_core.services.base_service import BaseService
3
+ from abs_repository_core.schemas.base_schema import FindBase, FindUniqueValues
4
+ from abs_auth_rbac_core.models.roles import Role
5
+ from abs_auth_rbac_core.repository.role_repository import RoleRepository
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class RoleService(BaseService):
10
+ def __init__(self, repository:RoleRepository):
11
+ self.repository = repository
12
+ super().__init__(repository)
13
+
14
+
15
+ def list_roles(self, schema: FindBase, eager: bool = True):
16
+ return self.repository.read_by_options(schema, eager)
17
+
18
+
@@ -153,6 +153,10 @@ class PermissionAction(str, Enum):
153
153
  VIEW_IMPORT_DATA = "VIEW_IMPORT_DATA"
154
154
  VIEW_WORKFLOW = "VIEW_WORKFLOW"
155
155
  VIEW_ERROR_DATA = "VIEW_ERROR_DATA"
156
+ VIEW_ENTITY_RECORD = "VIEW_ENTITY_RECORD"
157
+ EDIT_ENTITY_RECORD = "EDIT_ENTITY_RECORD"
158
+ DELETE_ENTITY_RECORD = "DELETE_ENTITY_RECORD"
159
+ CREATE_ENTITY_RECORD = "CREATE_ENTITY_RECORD"
156
160
 
157
161
 
158
162
 
@@ -172,12 +176,17 @@ class PermissionModule(str, Enum):
172
176
  ASL = "ASL"
173
177
  EDS = "EDS"
174
178
  WORKFORCE_AGENT = "WORKFORCE_AGENT"
175
-
179
+ ENTITY = "ENTITY"
180
+ ENTITY_VIEW = "ENTITY_VIEW"
181
+ ENTITY_RECORD = "ENTITY_RECORD"
182
+ CHATBOT = "CHATBOT"
176
183
 
177
184
 
178
185
  class PermissionResource(str, Enum):
186
+ ENTITIES = "ENTITIES"
179
187
  DASHBOARD = "DASHBOARDS"
180
188
  WORKFORCE_AGENT = "WORKFORCE_AGENT"
189
+ CHATBOT = "CHATBOT"
181
190
  EPAR = "EPAR"
182
191
  EPARS = "EPARS"
183
192
  OCFO = "OCFO"
@@ -262,6 +271,7 @@ class PermissionResource(str, Enum):
262
271
  AUTOMATION_ACTION = "AUTOMATION_ACTION"
263
272
  AUTOMATION_INPUT = "AUTOMATION_INPUT"
264
273
  AUTOMATION_HISTORY = "AUTOMATION_HISTORY"
274
+ USER_PROFILE = "USER_PROFILE"
265
275
 
266
276
 
267
277
  class PermissionData(NamedTuple):
@@ -273,6 +283,22 @@ class PermissionData(NamedTuple):
273
283
 
274
284
 
275
285
  class PermissionConstants:
286
+ # User Profile Permissions
287
+ USER_PROFILE_VIEW = PermissionData(
288
+ name="View User Profile",
289
+ description="Permission to view user profile",
290
+ module=PermissionModule.USER_MANAGEMENT,
291
+ resource=PermissionResource.USER_PROFILE,
292
+ action=PermissionAction.VIEW,
293
+ )
294
+ USER_PROFILE_EDIT = PermissionData(
295
+ name="Edit User Profile",
296
+ description="Permission to edit user profile",
297
+ module=PermissionModule.USER_MANAGEMENT,
298
+ resource=PermissionResource.USER_PROFILE,
299
+ action=PermissionAction.EDIT,
300
+ )
301
+
276
302
  # Automation Builder Permissions
277
303
  AUTOMATION_HISTORY_VIEW = PermissionData(
278
304
  name="View Automation History",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "abs-auth-rbac-core"
3
- version = "0.3.1"
3
+ version = "0.3.13"
4
4
  description = "RBAC and Auth core utilities including JWT token management."
5
5
  authors = [
6
6
  {name = "AutoBridgeSystems", email = "info@autobridgesystems.com"}
@@ -18,7 +18,9 @@ dependencies = [
18
18
  "casbin-sqlalchemy-adapter (>=1.4.0,<2.0.0)",
19
19
  "psycopg2-binary (>=2.9.10,<3.0.0)",
20
20
  "casbin-redis-watcher (>=1.3.0,<2.0.0)",
21
- "abs-utils (>=0.4.0,<0.5.0)"
21
+ "abs-utils (>=0.4.1,<0.5.0)",
22
+ "abs-repository-core (>=0.3.0,<0.4.0)",
23
+ "abs-nosql-repository-core (>=0.11.8,<0.12.0)"
22
24
  ]
23
25
 
24
26
  [build-system]
@@ -1,3 +0,0 @@
1
- from .jwt_functions import JWTFunctions
2
-
3
- __all__ = ["JWTFunctions"]
@@ -1,51 +0,0 @@
1
- from fastapi import Depends, Request
2
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
- import logging
4
- from typing import Callable, Any
5
-
6
- from .jwt_functions import JWTFunctions
7
- from .auth_functions import get_user_by_attribute
8
- from abs_exception_core.exceptions import UnauthorizedError
9
-
10
- security = HTTPBearer()
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- # Dependency acting like per-route middleware
15
- def auth_middleware(
16
- db_session: Callable[...,Any],
17
- jwt_secret_key:str,
18
- jwt_algorithm:str
19
- ):
20
- """
21
- This middleware is used for authentication of the user.
22
- Args:
23
- db_session: Callable[...,Any]: Session of the SQLAlchemy database engine
24
- jwt_secret_key: Secret key of the JWT for jwt functions
25
- jwt_algorithm: Algorithm used for JWT
26
-
27
- Returns:
28
- """
29
- async def get_auth(request: Request, token: HTTPAuthorizationCredentials = Depends(security)):
30
- jwt_functions = JWTFunctions(secret_key=jwt_secret_key,algorithm=jwt_algorithm)
31
- try:
32
- if not token or not token.credentials:
33
- raise UnauthorizedError(detail="Invalid authentication credentials")
34
-
35
- payload = jwt_functions.get_data(token=token.credentials)
36
- uuid = payload.get("uuid")
37
-
38
- user = get_user_by_attribute(db_session=db_session,attribute="uuid", value=uuid)
39
-
40
- if not user:
41
- logger.error(f"Authentication failed: User with id {uuid} not found")
42
- raise UnauthorizedError(detail="Authentication failed")
43
-
44
- # Attach user to request state
45
- request.state.user = user
46
- return user
47
-
48
- except Exception as e:
49
- logger.error(f"Authentication error: {str(e)}", exc_info=True)
50
- raise UnauthorizedError(detail="Authentication failed")
51
- return get_auth