abs-auth-rbac-core 0.1.18__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.
Potentially problematic release.
This version of abs-auth-rbac-core might be problematic. Click here for more details.
- abs_auth_rbac_core-0.2.0/PKG-INFO +761 -0
- abs_auth_rbac_core-0.2.0/README.md +738 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/gov_casbin_rule.py +9 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/pyproject.toml +1 -1
- abs_auth_rbac_core-0.1.18/PKG-INFO +0 -234
- abs_auth_rbac_core-0.1.18/README.md +0 -211
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/auth/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/auth/auth_functions.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/auth/jwt_functions.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/auth/middleware.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/base_model.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/permissions.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/rbac_model.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/role_permission.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/roles.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/seeder/permission_seeder.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/user.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/user_permission.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/models/user_role.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/rbac/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/rbac/decorator.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/rbac/policy.conf +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/rbac/service.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/schema/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/schema/permission.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/util/__init__.py +0 -0
- {abs_auth_rbac_core-0.1.18 → abs_auth_rbac_core-0.2.0}/abs_auth_rbac_core/util/permission_constants.py +0 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: abs-auth-rbac-core
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: RBAC and Auth core utilities including JWT token management.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: AutoBridgeSystems
|
|
7
|
+
Author-email: info@autobridgesystems.com
|
|
8
|
+
Requires-Python: >=3.13,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: abs-exception-core (>=0.1.0,<0.2.0)
|
|
13
|
+
Requires-Dist: casbin (>=1.41.0,<2.0.0)
|
|
14
|
+
Requires-Dist: casbin-redis-watcher (>=1.3.0,<2.0.0)
|
|
15
|
+
Requires-Dist: casbin-sqlalchemy-adapter (>=1.4.0,<2.0.0)
|
|
16
|
+
Requires-Dist: fastapi[standard] (>=0.115.12,<0.116.0)
|
|
17
|
+
Requires-Dist: passlib (>=1.7.4,<2.0.0)
|
|
18
|
+
Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0)
|
|
19
|
+
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
|
20
|
+
Requires-Dist: sqlalchemy (>=2.0.40,<3.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# ABS Auth RBAC Core
|
|
24
|
+
|
|
25
|
+
A comprehensive authentication and Role-Based Access Control (RBAC) package for FastAPI applications. This package provides robust JWT-based authentication and flexible role-based permission management using Casbin with Redis support for real-time policy updates.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **JWT-based Authentication**: Secure token-based authentication with customizable expiration
|
|
30
|
+
- **Password Hashing**: Secure password storage using bcrypt
|
|
31
|
+
- **Role-Based Access Control (RBAC)**: Flexible permission management using Casbin
|
|
32
|
+
- **Real-time Policy Updates**: Redis integration for live policy synchronization
|
|
33
|
+
- **User-Role Management**: Dynamic role assignment and revocation
|
|
34
|
+
- **Permission Enforcement**: Decorator-based permission checking
|
|
35
|
+
- **Middleware Integration**: Seamless FastAPI middleware integration
|
|
36
|
+
- **Comprehensive Error Handling**: Built-in exception handling for security scenarios
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install abs-auth-rbac-core
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
### 1. Basic Setup
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from abs_auth_rbac_core.auth.jwt_functions import JWTFunctions
|
|
50
|
+
from abs_auth_rbac_core.rbac import RBACService
|
|
51
|
+
import os
|
|
52
|
+
|
|
53
|
+
# Initialize JWT functions
|
|
54
|
+
jwt_functions = JWTFunctions(
|
|
55
|
+
secret_key=os.getenv("JWT_SECRET_KEY"),
|
|
56
|
+
algorithm=os.getenv("JWT_ALGORITHM", "HS256"),
|
|
57
|
+
expire_minutes=int(os.getenv("JWT_EXPIRE_MINUTES", "60"))
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Initialize RBAC service with database session
|
|
61
|
+
rbac_service = RBACService(
|
|
62
|
+
session=your_db_session,
|
|
63
|
+
redis_config=RedisWatcherSchema(
|
|
64
|
+
host=os.getenv("REDIS_HOST"),
|
|
65
|
+
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
66
|
+
channel=os.getenv("REDIS_CHANNEL", "casbin_policy_updates"),
|
|
67
|
+
password=os.getenv("REDIS_PASSWORD"),
|
|
68
|
+
ssl=os.getenv("REDIS_SSL", "false").lower() == "true"
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Authentication Setup
|
|
74
|
+
|
|
75
|
+
The auth middleware can be implemented in two ways:
|
|
76
|
+
|
|
77
|
+
#### Option 1: Using the Package Middleware (Recommended)
|
|
78
|
+
|
|
79
|
+
The package middleware automatically handles JWT token validation and sets the user in the request state:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from abs_auth_rbac_core.auth.middleware import auth_middleware
|
|
83
|
+
|
|
84
|
+
# Create authentication middleware
|
|
85
|
+
auth_middleware = auth_middleware(
|
|
86
|
+
db_session=your_db_session,
|
|
87
|
+
jwt_secret_key=os.getenv("JWT_SECRET_KEY"),
|
|
88
|
+
jwt_algorithm=os.getenv("JWT_ALGORITHM", "HS256")
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Apply to specific routers (recommended approach)
|
|
92
|
+
app.include_router(
|
|
93
|
+
protected_router,
|
|
94
|
+
dependencies=[Depends(auth_middleware)]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Public routes (no middleware)
|
|
98
|
+
app.include_router(public_router)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**How it works:**
|
|
102
|
+
1. The middleware validates the JWT token from the Authorization header
|
|
103
|
+
2. Extracts the user UUID from the token payload
|
|
104
|
+
3. Fetches the user from the database using the UUID
|
|
105
|
+
4. **Sets the user object in `request.state.user`**
|
|
106
|
+
5. Returns the user object for use in route handlers
|
|
107
|
+
|
|
108
|
+
**Accessing the user in routes:**
|
|
109
|
+
```python
|
|
110
|
+
@router.get("/profile")
|
|
111
|
+
async def get_profile(request: Request):
|
|
112
|
+
# User is automatically available in request.state.user
|
|
113
|
+
user = request.state.user
|
|
114
|
+
return {"user_id": user.uuid, "email": user.email}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Option 2: Custom Authentication Function
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from abs_auth_rbac_core.auth import JWTFunctions
|
|
121
|
+
from fastapi import Security, HTTPAuthorizationCredentials
|
|
122
|
+
from fastapi.security import HTTPBearer
|
|
123
|
+
from abs_exception_core.exceptions import UnauthorizedError
|
|
124
|
+
|
|
125
|
+
# Create security scheme
|
|
126
|
+
security = HTTPBearer(auto_error=False)
|
|
127
|
+
jwt_functions = JWTFunctions(
|
|
128
|
+
secret_key=os.getenv("JWT_SECRET_KEY"),
|
|
129
|
+
algorithm=os.getenv("JWT_ALGORITHM", "HS256"),
|
|
130
|
+
expire_minutes=int(os.getenv("JWT_EXPIRE_MINUTES", "60"))
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
async def get_current_user(
|
|
134
|
+
credentials: HTTPAuthorizationCredentials = Security(security),
|
|
135
|
+
) -> Dict:
|
|
136
|
+
try:
|
|
137
|
+
if not credentials:
|
|
138
|
+
raise UnauthorizedError(detail="No authorization token provided")
|
|
139
|
+
|
|
140
|
+
token = credentials.credentials
|
|
141
|
+
# Remove 'Bearer ' prefix if present
|
|
142
|
+
if token.lower().startswith("bearer "):
|
|
143
|
+
token = token[7:]
|
|
144
|
+
|
|
145
|
+
decoded_token = jwt_functions.decode_jwt(token)
|
|
146
|
+
if not decoded_token:
|
|
147
|
+
raise UnauthorizedError(detail="Invalid or expired token")
|
|
148
|
+
|
|
149
|
+
return decoded_token
|
|
150
|
+
except Exception as e:
|
|
151
|
+
raise UnauthorizedError(detail=str(e))
|
|
152
|
+
|
|
153
|
+
# Use in individual routes
|
|
154
|
+
@app.get("/protected")
|
|
155
|
+
async def protected_route(current_user: dict = Depends(get_current_user)):
|
|
156
|
+
return {"message": f"Hello {current_user.get('name')}"}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. RBAC Operations
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Create a role with permissions
|
|
163
|
+
role = rbac_service.create_role(
|
|
164
|
+
name="admin",
|
|
165
|
+
description="Administrator role with full access",
|
|
166
|
+
permission_ids=["permission_uuid1", "permission_uuid2"]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Assign roles to user
|
|
170
|
+
rbac_service.bulk_assign_roles_to_user(
|
|
171
|
+
user_uuid="user_uuid",
|
|
172
|
+
role_uuids=["role_uuid1", "role_uuid2"]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Check user permissions
|
|
176
|
+
has_permission = rbac_service.check_permission(
|
|
177
|
+
user_uuid="user_uuid",
|
|
178
|
+
resource="USER_MANAGEMENT",
|
|
179
|
+
action="VIEW",
|
|
180
|
+
module="USER_MANAGEMENT"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Get user permissions
|
|
184
|
+
user_permissions = rbac_service.get_user_permissions(user_uuid="user_uuid")
|
|
185
|
+
user_roles = rbac_service.get_user_roles(user_uuid="user_uuid")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Core Components
|
|
189
|
+
|
|
190
|
+
### Authentication (`auth/`)
|
|
191
|
+
- `jwt_functions.py`: JWT token management and password hashing
|
|
192
|
+
- `middleware.py`: Authentication middleware for FastAPI
|
|
193
|
+
- `auth_functions.py`: Core authentication functions
|
|
194
|
+
|
|
195
|
+
### RBAC (`rbac/`)
|
|
196
|
+
- `service.py`: Main RBAC service with role and permission management
|
|
197
|
+
- `decorator.py`: Decorators for permission checking
|
|
198
|
+
|
|
199
|
+
### Models (`models/`)
|
|
200
|
+
- `user.py`: User model
|
|
201
|
+
- `roles.py`: Role model
|
|
202
|
+
- `permissions.py`: Permission model
|
|
203
|
+
- `user_role.py`: User-Role association model
|
|
204
|
+
- `role_permission.py`: Role-Permission association model
|
|
205
|
+
- `rbac_model.py`: Base RBAC model
|
|
206
|
+
- `base_model.py`: Base model with common fields
|
|
207
|
+
|
|
208
|
+
### Utilities (`util/`)
|
|
209
|
+
- `permission_constants.py`: Predefined permission constants and enums
|
|
210
|
+
|
|
211
|
+
## Complete Implementation Example
|
|
212
|
+
|
|
213
|
+
### 1. Dependency Injection Setup
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from dependency_injector import containers, providers
|
|
217
|
+
from abs_auth_rbac_core.auth.middleware import auth_middleware
|
|
218
|
+
from abs_auth_rbac_core.rbac import RBACService
|
|
219
|
+
from abs_auth_rbac_core.util.permission_constants import (
|
|
220
|
+
PermissionAction,
|
|
221
|
+
PermissionModule,
|
|
222
|
+
PermissionResource
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
class Container(containers.DeclarativeContainer):
|
|
226
|
+
# Configure wiring for dependency injection
|
|
227
|
+
wiring_config = containers.WiringConfiguration(
|
|
228
|
+
modules=[
|
|
229
|
+
"src.api.auth_route",
|
|
230
|
+
"src.api.endpoints.rbac.permission_route",
|
|
231
|
+
"src.api.endpoints.rbac.role_route",
|
|
232
|
+
"src.api.endpoints.rbac.users_route",
|
|
233
|
+
# Add other modules that need dependency injection
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Database session provider
|
|
238
|
+
db_session = providers.Factory(your_db_session_factory)
|
|
239
|
+
|
|
240
|
+
# RBAC service provider
|
|
241
|
+
rbac_service = providers.Singleton(
|
|
242
|
+
RBACService,
|
|
243
|
+
session=db_session,
|
|
244
|
+
redis_config=RedisWatcherSchema(
|
|
245
|
+
host=os.getenv("REDIS_HOST"),
|
|
246
|
+
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
247
|
+
channel=os.getenv("REDIS_CHANNEL", "casbin_policy_updates"),
|
|
248
|
+
password=os.getenv("REDIS_PASSWORD"),
|
|
249
|
+
ssl=os.getenv("REDIS_SSL", "false").lower() == "true"
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Auth middleware provider
|
|
254
|
+
get_auth_middleware = providers.Factory(
|
|
255
|
+
auth_middleware,
|
|
256
|
+
db_session=db_session,
|
|
257
|
+
jwt_secret_key=os.getenv("JWT_SECRET_KEY"),
|
|
258
|
+
jwt_algorithm=os.getenv("JWT_ALGORITHM", "HS256")
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Initialize container
|
|
262
|
+
container = Container()
|
|
263
|
+
app.container = container
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 2. Complete Application Setup
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from fastapi import FastAPI, Depends
|
|
270
|
+
from dependency_injector.wiring import Provide, inject
|
|
271
|
+
from src.core.container import Container
|
|
272
|
+
|
|
273
|
+
@singleton
|
|
274
|
+
class CreateApp:
|
|
275
|
+
def __init__(self):
|
|
276
|
+
self.container = Container()
|
|
277
|
+
self.db = self.container.db()
|
|
278
|
+
# Get the auth middleware factory
|
|
279
|
+
self.auth_middleware = self.container.get_auth_middleware()
|
|
280
|
+
|
|
281
|
+
self.app = FastAPI(
|
|
282
|
+
title="Your Service",
|
|
283
|
+
description="Service Description",
|
|
284
|
+
version="0.0.1"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Apply CORS middleware
|
|
288
|
+
self.app.add_middleware(
|
|
289
|
+
CORSMiddleware,
|
|
290
|
+
allow_origins=[str(origin) for origin in configs.BACKEND_CORS_ORIGINS],
|
|
291
|
+
allow_credentials=True,
|
|
292
|
+
allow_methods=["*"],
|
|
293
|
+
allow_headers=["*"],
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Public routes (no authentication required)
|
|
297
|
+
self.app.include_router(auth_router, tags=["Auth"])
|
|
298
|
+
self.app.include_router(public_router_v1)
|
|
299
|
+
|
|
300
|
+
# Protected routes (authentication required)
|
|
301
|
+
self.app.include_router(
|
|
302
|
+
router_v1,
|
|
303
|
+
dependencies=[Depends(self.auth_middleware)]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Register exception handlers
|
|
307
|
+
register_exception_handlers(self.app)
|
|
308
|
+
|
|
309
|
+
# Initialize application
|
|
310
|
+
application = CreateApp()
|
|
311
|
+
app = application.app
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 3. Route Implementation with Permissions
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from fastapi import APIRouter, Depends, Request
|
|
318
|
+
from dependency_injector.wiring import Provide, inject
|
|
319
|
+
from abs_auth_rbac_core.rbac import rbac_require_permission
|
|
320
|
+
from abs_auth_rbac_core.util.permission_constants import (
|
|
321
|
+
PermissionAction,
|
|
322
|
+
PermissionModule,
|
|
323
|
+
PermissionResource
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Protected router (requires authentication)
|
|
327
|
+
router = APIRouter(prefix="/users")
|
|
328
|
+
|
|
329
|
+
# Public router (no authentication required)
|
|
330
|
+
public_router = APIRouter(prefix="/users")
|
|
331
|
+
|
|
332
|
+
# Public route example (no authentication or permissions required)
|
|
333
|
+
@public_router.post("/all", response_model=FindUserResult)
|
|
334
|
+
@inject
|
|
335
|
+
async def get_user_list(
|
|
336
|
+
request: Request,
|
|
337
|
+
find_query: FindUser = Body(...),
|
|
338
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
339
|
+
service: UserService = Depends(Provide[Container.user_service]),
|
|
340
|
+
):
|
|
341
|
+
"""Get the list of users with filtering, sorting and pagination"""
|
|
342
|
+
find_query.searchable_fields = find_query.searchable_fields or ["name"]
|
|
343
|
+
users = service.get_list(schema=find_query)
|
|
344
|
+
return users
|
|
345
|
+
|
|
346
|
+
# Protected route with permission check
|
|
347
|
+
@router.get("/{user_id}", response_model=UserProfile)
|
|
348
|
+
@inject
|
|
349
|
+
@rbac_require_permission(
|
|
350
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
351
|
+
)
|
|
352
|
+
async def get_user(
|
|
353
|
+
user_id: int,
|
|
354
|
+
request: Request,
|
|
355
|
+
service: UserService = Depends(Provide[Container.user_service]),
|
|
356
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
357
|
+
):
|
|
358
|
+
"""Get user profile with permissions and roles"""
|
|
359
|
+
return service.get_user_profile("id", user_id, rbac_service)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**How the RBAC decorator works:**
|
|
363
|
+
1. The `@rbac_require_permission` decorator automatically extracts the user from `request.state.user`
|
|
364
|
+
2. Gets the user's UUID: `current_user_uuid = request.state.user.uuid`
|
|
365
|
+
3. Checks if the user has the required permissions using the RBAC service
|
|
366
|
+
4. If permission is denied, raises `PermissionDeniedError`
|
|
367
|
+
5. If permission is granted, the route handler executes normally
|
|
368
|
+
|
|
369
|
+
**Important:** The `request: Request` parameter is required in routes that use the `@rbac_require_permission` decorator because it needs access to `request.state.user`.
|
|
370
|
+
|
|
371
|
+
### Authentication Flow Overview
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
1. Client sends request with Authorization header
|
|
375
|
+
Authorization: Bearer <jwt_token>
|
|
376
|
+
|
|
377
|
+
2. Auth middleware intercepts the request
|
|
378
|
+
├── Validates JWT token
|
|
379
|
+
├── Extracts user UUID from token
|
|
380
|
+
├── Fetches user from database
|
|
381
|
+
└── Sets user in request.state.user
|
|
382
|
+
|
|
383
|
+
3. RBAC decorator checks permissions
|
|
384
|
+
├── Gets user UUID from request.state.user
|
|
385
|
+
├── Checks permissions against Casbin policies
|
|
386
|
+
└── Allows/denies access based on permissions
|
|
387
|
+
|
|
388
|
+
4. Route handler executes (if permissions granted)
|
|
389
|
+
└── Can access user via request.state.user
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Accessing Current User
|
|
393
|
+
|
|
394
|
+
#### In Routes with RBAC Decorator
|
|
395
|
+
```python
|
|
396
|
+
@router.get("/my-profile")
|
|
397
|
+
@inject
|
|
398
|
+
@rbac_require_permission(
|
|
399
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
400
|
+
)
|
|
401
|
+
async def get_my_profile(
|
|
402
|
+
request: Request,
|
|
403
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
404
|
+
):
|
|
405
|
+
# Access current user from request state
|
|
406
|
+
current_user = request.state.user
|
|
407
|
+
return service.get_user_profile("uuid", current_user.uuid, rbac_service)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### In Routes with Custom Auth Function
|
|
411
|
+
```python
|
|
412
|
+
@router.get("/profile")
|
|
413
|
+
async def get_profile(current_user: dict = Depends(get_current_user)):
|
|
414
|
+
# current_user is the decoded JWT payload
|
|
415
|
+
return {"user_id": current_user["uuid"], "email": current_user["email"]}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
#### In Service Methods
|
|
419
|
+
```python
|
|
420
|
+
def some_service_method(self, user_uuid: str, rbac_service: RBACService):
|
|
421
|
+
# Get user permissions
|
|
422
|
+
permissions = rbac_service.get_user_permissions(user_uuid=user_uuid)
|
|
423
|
+
# Get user roles
|
|
424
|
+
roles = rbac_service.get_user_roles(user_uuid=user_uuid)
|
|
425
|
+
return {"permissions": permissions, "roles": roles}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
@router.post("/create")
|
|
429
|
+
@inject
|
|
430
|
+
@rbac_require_permission(
|
|
431
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.CREATE.value}"
|
|
432
|
+
)
|
|
433
|
+
async def create_user(
|
|
434
|
+
user: CreateUserWithRoles,
|
|
435
|
+
request: Request,
|
|
436
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
437
|
+
):
|
|
438
|
+
"""Create a new user with roles"""
|
|
439
|
+
new_user = service.add_user(user)
|
|
440
|
+
|
|
441
|
+
# Assign roles if provided
|
|
442
|
+
if user.role_uuids and len(user.role_uuids) > 0 and new_user.uuid:
|
|
443
|
+
rbac_service.bulk_assign_roles_to_user(
|
|
444
|
+
user_uuid=new_user.uuid,
|
|
445
|
+
role_uuids=user.role_uuids,
|
|
446
|
+
)
|
|
447
|
+
return new_user
|
|
448
|
+
|
|
449
|
+
@router.patch("/{user_id}")
|
|
450
|
+
@inject
|
|
451
|
+
@rbac_require_permission(
|
|
452
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.EDIT.value}"
|
|
453
|
+
)
|
|
454
|
+
async def update_user(
|
|
455
|
+
user_id: int,
|
|
456
|
+
user: UpdateUserWithRoles,
|
|
457
|
+
request: Request,
|
|
458
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
459
|
+
):
|
|
460
|
+
"""Update user with new attributes and roles"""
|
|
461
|
+
return service.patch_user(user_id, user, rbac_service)
|
|
462
|
+
|
|
463
|
+
@router.delete("/{user_id}")
|
|
464
|
+
@inject
|
|
465
|
+
@rbac_require_permission(
|
|
466
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.DELETE.value}"
|
|
467
|
+
)
|
|
468
|
+
async def delete_user(
|
|
469
|
+
user_id: int,
|
|
470
|
+
request: Request,
|
|
471
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
472
|
+
):
|
|
473
|
+
"""Delete user"""
|
|
474
|
+
return service.remove_user(user_id, rbac_service)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 3. Role and Permission Management
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
@router.get("/roles")
|
|
481
|
+
@inject
|
|
482
|
+
@rbac_require_permission(
|
|
483
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.ROLE_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
484
|
+
)
|
|
485
|
+
async def get_roles(
|
|
486
|
+
request: Request,
|
|
487
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
488
|
+
):
|
|
489
|
+
"""Get all roles"""
|
|
490
|
+
return rbac_service.list_roles()
|
|
491
|
+
|
|
492
|
+
@router.post("/roles")
|
|
493
|
+
@inject
|
|
494
|
+
@rbac_require_permission(
|
|
495
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.ROLE_MANAGEMENT.value}:{PermissionAction.CREATE.value}"
|
|
496
|
+
)
|
|
497
|
+
async def create_role(
|
|
498
|
+
role: CreateRoleSchema,
|
|
499
|
+
request: Request,
|
|
500
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
501
|
+
):
|
|
502
|
+
"""Create a new role with permissions"""
|
|
503
|
+
return rbac_service.create_role(
|
|
504
|
+
name=role.name,
|
|
505
|
+
description=role.description,
|
|
506
|
+
permission_ids=role.permission_ids
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
@router.get("/user-permissions/{user_uuid}")
|
|
510
|
+
@inject
|
|
511
|
+
@rbac_require_permission([
|
|
512
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.VIEW.value}",
|
|
513
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.ROLE_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
514
|
+
])
|
|
515
|
+
async def get_user_permissions(
|
|
516
|
+
user_uuid: str,
|
|
517
|
+
request: Request,
|
|
518
|
+
rbac_service: RBACService = Depends(Provide[Container.rbac_service]),
|
|
519
|
+
):
|
|
520
|
+
"""Get all permissions for a user"""
|
|
521
|
+
return rbac_service.get_user_permissions(user_uuid)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### 4. User Profile with Permissions
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
def get_user_profile(self, attr: str, value: any, rbac_service: RBACService) -> UserProfile:
|
|
528
|
+
"""Get user profile with permissions and roles"""
|
|
529
|
+
user = self.user_repository.read_by_attr(attr, value, eager=True)
|
|
530
|
+
|
|
531
|
+
# Get user permissions and roles
|
|
532
|
+
permissions = rbac_service.get_user_permissions(user_uuid=user.uuid)
|
|
533
|
+
user_permissions = rbac_service.get_user_only_permissions(user_uuid=user.uuid)
|
|
534
|
+
roles = rbac_service.get_user_roles(user_uuid=user.uuid)
|
|
535
|
+
|
|
536
|
+
# Convert roles to response models
|
|
537
|
+
role_models = [UserRoleResponse.model_validate(role) for role in roles]
|
|
538
|
+
|
|
539
|
+
return UserProfile(
|
|
540
|
+
id=user.id,
|
|
541
|
+
uuid=user.uuid,
|
|
542
|
+
email=user.email,
|
|
543
|
+
name=user.name,
|
|
544
|
+
is_active=user.is_active,
|
|
545
|
+
last_login_at=user.last_login_at,
|
|
546
|
+
permissions=permissions,
|
|
547
|
+
user_permissions=user_permissions,
|
|
548
|
+
roles=role_models,
|
|
549
|
+
)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Permission System
|
|
553
|
+
|
|
554
|
+
### Permission Format
|
|
555
|
+
Permissions follow the format: `module:resource:action`
|
|
556
|
+
|
|
557
|
+
- **Module**: The system module (e.g., `USER_MANAGEMENT`, `EMAIL_PROCESS`)
|
|
558
|
+
- **Resource**: The specific resource within the module (e.g., `USER_MANAGEMENT`, `ROLE_MANAGEMENT`)
|
|
559
|
+
- **Action**: The action being performed (e.g., `VIEW`, `CREATE`, `EDIT`, `DELETE`)
|
|
560
|
+
|
|
561
|
+
### Using Permission Constants
|
|
562
|
+
|
|
563
|
+
```python
|
|
564
|
+
from abs_auth_rbac_core.util.permission_constants import (
|
|
565
|
+
PermissionAction,
|
|
566
|
+
PermissionModule,
|
|
567
|
+
PermissionResource,
|
|
568
|
+
PermissionConstants
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Using enums
|
|
572
|
+
permission_string = f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
573
|
+
|
|
574
|
+
# Using predefined constants
|
|
575
|
+
user_view_permission = PermissionConstants.RBAC_USER_MANAGEMENT_VIEW
|
|
576
|
+
permission_string = f"{user_view_permission.module}:{user_view_permission.resource}:{user_view_permission.action}"
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Multiple Permissions
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
@rbac_require_permission([
|
|
583
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.USER_MANAGEMENT.value}:{PermissionAction.VIEW.value}",
|
|
584
|
+
f"{PermissionModule.USER_MANAGEMENT.value}:{PermissionResource.ROLE_MANAGEMENT.value}:{PermissionAction.VIEW.value}"
|
|
585
|
+
])
|
|
586
|
+
async def get_user_with_roles():
|
|
587
|
+
# User needs both permissions to access this endpoint
|
|
588
|
+
pass
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Configuration
|
|
592
|
+
|
|
593
|
+
### Environment Variables
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# JWT Configuration
|
|
597
|
+
JWT_SECRET_KEY=your-secret-key
|
|
598
|
+
JWT_ALGORITHM=HS256
|
|
599
|
+
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
|
600
|
+
JWT_REFRESH_TOKEN_EXPIRE_MINUTES=1440
|
|
601
|
+
|
|
602
|
+
# Redis Configuration (for real-time policy updates)
|
|
603
|
+
REDIS_HOST=localhost
|
|
604
|
+
REDIS_PORT=6379
|
|
605
|
+
REDIS_PASSWORD=your-redis-password
|
|
606
|
+
REDIS_CHANNEL=casbin_policy_updates
|
|
607
|
+
REDIS_SSL=false
|
|
608
|
+
|
|
609
|
+
# Database Configuration
|
|
610
|
+
DATABASE_URI=postgresql://user:password@localhost/dbname
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Casbin Policy Configuration
|
|
614
|
+
|
|
615
|
+
The package uses a default policy configuration that supports:
|
|
616
|
+
- Role-based access control
|
|
617
|
+
- Resource-based permissions
|
|
618
|
+
- Module-based organization
|
|
619
|
+
- Super admin bypass
|
|
620
|
+
|
|
621
|
+
Policy format: `[role] [resource] [action] [module]`
|
|
622
|
+
|
|
623
|
+
## Error Handling
|
|
624
|
+
|
|
625
|
+
The package includes comprehensive error handling:
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
from abs_exception_core.exceptions import (
|
|
629
|
+
UnauthorizedError,
|
|
630
|
+
PermissionDeniedError,
|
|
631
|
+
ValidationError,
|
|
632
|
+
DuplicatedError,
|
|
633
|
+
NotFoundError
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
# Handle authentication errors
|
|
637
|
+
try:
|
|
638
|
+
user = await auth_middleware(request)
|
|
639
|
+
except UnauthorizedError as e:
|
|
640
|
+
return {"error": "Authentication failed", "detail": str(e)}
|
|
641
|
+
|
|
642
|
+
# Handle permission errors
|
|
643
|
+
try:
|
|
644
|
+
# Protected operation
|
|
645
|
+
pass
|
|
646
|
+
except PermissionDeniedError as e:
|
|
647
|
+
return {"error": "Permission denied", "detail": str(e)}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
## Best Practices
|
|
651
|
+
|
|
652
|
+
### 1. Security
|
|
653
|
+
- Always use environment variables for sensitive data
|
|
654
|
+
- Implement proper password policies
|
|
655
|
+
- Regularly rotate JWT secret keys
|
|
656
|
+
- Use HTTPS in production
|
|
657
|
+
- Implement rate limiting for authentication endpoints
|
|
658
|
+
|
|
659
|
+
### 2. Permission Design
|
|
660
|
+
- Use descriptive permission names
|
|
661
|
+
- Group related permissions by module
|
|
662
|
+
- Implement least privilege principle
|
|
663
|
+
- Document permission requirements
|
|
664
|
+
|
|
665
|
+
### 3. Performance
|
|
666
|
+
- Use Redis for real-time policy updates
|
|
667
|
+
- Implement caching for frequently accessed permissions
|
|
668
|
+
- Optimize database queries with eager loading
|
|
669
|
+
- Monitor policy enforcement performance
|
|
670
|
+
|
|
671
|
+
### 4. Maintenance
|
|
672
|
+
- Regularly audit user permissions
|
|
673
|
+
- Implement permission cleanup for inactive users
|
|
674
|
+
- Monitor and log security events
|
|
675
|
+
- Keep dependencies updated
|
|
676
|
+
|
|
677
|
+
### 5. Testing
|
|
678
|
+
- Test all permission combinations
|
|
679
|
+
- Mock external dependencies
|
|
680
|
+
- Test error scenarios
|
|
681
|
+
- Implement integration tests
|
|
682
|
+
|
|
683
|
+
## Monitoring and Logging
|
|
684
|
+
|
|
685
|
+
```python
|
|
686
|
+
import logging
|
|
687
|
+
from abs_utils.logger import setup_logger
|
|
688
|
+
|
|
689
|
+
logger = setup_logger(__name__)
|
|
690
|
+
|
|
691
|
+
# Log authentication events
|
|
692
|
+
logger.info(f"User {user_uuid} authenticated successfully")
|
|
693
|
+
|
|
694
|
+
# Log permission checks
|
|
695
|
+
logger.info(f"Permission check: {user_uuid} -> {resource}:{action}:{module}")
|
|
696
|
+
|
|
697
|
+
# Log role assignments
|
|
698
|
+
logger.info(f"Roles assigned to user {user_uuid}: {role_uuids}")
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## Migration and Deployment
|
|
702
|
+
|
|
703
|
+
### Database Migrations
|
|
704
|
+
Ensure your database has the required tables:
|
|
705
|
+
- `users`
|
|
706
|
+
- `roles`
|
|
707
|
+
- `permissions`
|
|
708
|
+
- `user_roles`
|
|
709
|
+
- `role_permissions`
|
|
710
|
+
- `gov_casbin_rules`
|
|
711
|
+
|
|
712
|
+
### Redis Setup
|
|
713
|
+
For real-time policy updates, configure Redis:
|
|
714
|
+
```bash
|
|
715
|
+
# Install Redis
|
|
716
|
+
sudo apt-get install redis-server
|
|
717
|
+
|
|
718
|
+
# Configure Redis
|
|
719
|
+
redis-cli config set requirepass your-password
|
|
720
|
+
redis-cli config set notify-keyspace-events KEA
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Health Checks
|
|
724
|
+
```python
|
|
725
|
+
@app.get("/health")
|
|
726
|
+
async def health_check():
|
|
727
|
+
return {
|
|
728
|
+
"status": "healthy",
|
|
729
|
+
"rbac_watcher": rbac_service.is_watcher_active(),
|
|
730
|
+
"policy_count": rbac_service.get_policy_count()
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
## Troubleshooting
|
|
735
|
+
|
|
736
|
+
### Common Issues
|
|
737
|
+
|
|
738
|
+
1. **Authentication Fails**
|
|
739
|
+
- Check JWT secret key configuration
|
|
740
|
+
- Verify token expiration settings
|
|
741
|
+
- Ensure user exists in database
|
|
742
|
+
|
|
743
|
+
2. **Permission Denied**
|
|
744
|
+
- Verify user has required roles
|
|
745
|
+
- Check role-permission assignments
|
|
746
|
+
- Validate permission format
|
|
747
|
+
|
|
748
|
+
3. **Redis Connection Issues**
|
|
749
|
+
- Check Redis server status
|
|
750
|
+
- Verify connection parameters
|
|
751
|
+
- Ensure Redis supports pub/sub
|
|
752
|
+
|
|
753
|
+
4. **Policy Not Updating**
|
|
754
|
+
- Check Redis watcher configuration
|
|
755
|
+
- Verify policy format
|
|
756
|
+
- Monitor Redis logs
|
|
757
|
+
|
|
758
|
+
## License
|
|
759
|
+
|
|
760
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
761
|
+
|