permissions2fast-fastapi 0.1.0__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.
- permissions2fast_fastapi/__init__.py +26 -0
- permissions2fast_fastapi/__version__.py +1 -0
- permissions2fast_fastapi/dependencies.py +123 -0
- permissions2fast_fastapi/models/__init__.py +21 -0
- permissions2fast_fastapi/models/permission_assignment_model.py +13 -0
- permissions2fast_fastapi/models/permission_category_model.py +18 -0
- permissions2fast_fastapi/models/permission_model.py +17 -0
- permissions2fast_fastapi/models/permission_route_model.py +13 -0
- permissions2fast_fastapi/models/role_model.py +21 -0
- permissions2fast_fastapi/models/route_model.py +14 -0
- permissions2fast_fastapi/models/tenant_model.py +16 -0
- permissions2fast_fastapi/models/user_role_model.py +12 -0
- permissions2fast_fastapi/models/user_tenant_role_model.py +15 -0
- permissions2fast_fastapi/routers/__init__.py +5 -0
- permissions2fast_fastapi/routers/permissions_router.py +191 -0
- permissions2fast_fastapi/routers/roles_router.py +177 -0
- permissions2fast_fastapi/routers/routes_router.py +44 -0
- permissions2fast_fastapi/schemas/__init__.py +53 -0
- permissions2fast_fastapi/schemas/permission_category_schema.py +10 -0
- permissions2fast_fastapi/schemas/permission_schema.py +35 -0
- permissions2fast_fastapi/schemas/role_schema.py +40 -0
- permissions2fast_fastapi/schemas/route_schema.py +11 -0
- permissions2fast_fastapi/services/__init__.py +6 -0
- permissions2fast_fastapi/services/access_service.py +110 -0
- permissions2fast_fastapi/services/permission_service.py +197 -0
- permissions2fast_fastapi/services/role_service.py +256 -0
- permissions2fast_fastapi/services/route_service.py +44 -0
- permissions2fast_fastapi/settings.py +64 -0
- permissions2fast_fastapi/utils/__init__.py +27 -0
- permissions2fast_fastapi/utils/permission_cache.py +251 -0
- permissions2fast_fastapi/utils/redis_client.py +82 -0
- permissions2fast_fastapi-0.1.0.dist-info/METADATA +141 -0
- permissions2fast_fastapi-0.1.0.dist-info/RECORD +36 -0
- permissions2fast_fastapi-0.1.0.dist-info/WHEEL +5 -0
- permissions2fast_fastapi-0.1.0.dist-info/licenses/LICENSE +21 -0
- permissions2fast_fastapi-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
permissions2fast-fastapi
|
|
3
|
+
|
|
4
|
+
Complete RBAC (Role-Based Access Control) system for FastAPI applications.
|
|
5
|
+
Provides role management, permission checking, and user-role assignments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .models.role_model import Role
|
|
11
|
+
from .models.permission_model import Permission
|
|
12
|
+
from .models.permission_category_model import PermissionCategory
|
|
13
|
+
from .models.route_model import Route
|
|
14
|
+
from .models.user_role_model import UserRole
|
|
15
|
+
from .models.permission_assignment_model import PermissionAssignment
|
|
16
|
+
from .models.permission_route_model import PermissionRoute
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Role",
|
|
20
|
+
"Permission",
|
|
21
|
+
"PermissionCategory",
|
|
22
|
+
"Route",
|
|
23
|
+
"UserRole",
|
|
24
|
+
"PermissionAssignment",
|
|
25
|
+
"PermissionRoute",
|
|
26
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Dependencies
|
|
3
|
+
|
|
4
|
+
Dependencies for protecting routes based on roles and permissions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
from fastapi import Depends, HTTPException, Request, status
|
|
10
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
11
|
+
|
|
12
|
+
# Import from oauth2fast-fastapi directly
|
|
13
|
+
from oauth2fast_fastapi.dependencies import get_current_verified_user, get_auth_session
|
|
14
|
+
from oauth2fast_fastapi import User
|
|
15
|
+
|
|
16
|
+
from .services import access_service, role_service
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def has_role(
|
|
20
|
+
role_name: str,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Dependency to require a specific role.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
@app.get("/admin")
|
|
27
|
+
def admin_route(user: User = Depends(has_role("admin"))):
|
|
28
|
+
...
|
|
29
|
+
"""
|
|
30
|
+
async def _has_role(
|
|
31
|
+
request: Request,
|
|
32
|
+
user: Annotated[User, Depends(get_current_verified_user)],
|
|
33
|
+
session: Annotated[AsyncSession, Depends(get_auth_session)],
|
|
34
|
+
) -> User:
|
|
35
|
+
tenant_id = get_tenant_id(request)
|
|
36
|
+
user_roles = await role_service.list_user_roles(user.id, session, tenant_id=tenant_id)
|
|
37
|
+
|
|
38
|
+
has_required = any(r.name == role_name for r in user_roles)
|
|
39
|
+
|
|
40
|
+
if not has_required:
|
|
41
|
+
raise HTTPException(
|
|
42
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
43
|
+
detail=f"Missing required role: {role_name}",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return user
|
|
47
|
+
|
|
48
|
+
return _has_role
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_tenant_id(request: Request) -> int | None:
|
|
52
|
+
"""
|
|
53
|
+
Extract tenant_id from request.
|
|
54
|
+
Priority:
|
|
55
|
+
1. request.state.tenant_id (injected by external middleware)
|
|
56
|
+
2. X-Tenant-ID header
|
|
57
|
+
"""
|
|
58
|
+
from .settings import settings
|
|
59
|
+
if not settings.enable_tenancy:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# First check state (injected by middleware)
|
|
63
|
+
if hasattr(request.state, "tenant_id") and request.state.tenant_id:
|
|
64
|
+
try:
|
|
65
|
+
return int(request.state.tenant_id)
|
|
66
|
+
except ValueError:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# Fallback to header
|
|
70
|
+
header_tenant = request.headers.get("X-Tenant-ID")
|
|
71
|
+
if header_tenant:
|
|
72
|
+
try:
|
|
73
|
+
return int(header_tenant)
|
|
74
|
+
except ValueError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def has_permission(
|
|
81
|
+
permission_route: str | None = None,
|
|
82
|
+
method: str | None = None,
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Dependency to require permission for a route.
|
|
86
|
+
|
|
87
|
+
If params are None, verifies access to the *current* request path and method.
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
@app.get("/items")
|
|
91
|
+
def items(user: User = Depends(has_permission())):
|
|
92
|
+
# Checks if user can GET /items
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@app.get("/items")
|
|
96
|
+
def items(user: User = Depends(has_permission(permission_route="/api/items", method="GET"))):
|
|
97
|
+
...
|
|
98
|
+
"""
|
|
99
|
+
async def _has_permission(
|
|
100
|
+
request: Request,
|
|
101
|
+
user: Annotated[User, Depends(get_current_verified_user)],
|
|
102
|
+
session: Annotated[AsyncSession, Depends(get_auth_session)],
|
|
103
|
+
) -> User:
|
|
104
|
+
# Determine what to check
|
|
105
|
+
route_to_check = permission_route or request.url.path
|
|
106
|
+
method_to_check = method or request.method
|
|
107
|
+
|
|
108
|
+
# Extract tenant context
|
|
109
|
+
tenant_id = get_tenant_id(request)
|
|
110
|
+
|
|
111
|
+
is_allowed = await access_service.check_user_access(
|
|
112
|
+
user.id, route_to_check, method_to_check, session, tenant_id=tenant_id
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if not is_allowed:
|
|
116
|
+
raise HTTPException(
|
|
117
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
118
|
+
detail="Not enough permissions to access this resource",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return user
|
|
122
|
+
|
|
123
|
+
return _has_permission
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .role_model import Role
|
|
2
|
+
from .permission_model import Permission
|
|
3
|
+
from .permission_category_model import PermissionCategory
|
|
4
|
+
from .route_model import Route
|
|
5
|
+
from .user_role_model import UserRole
|
|
6
|
+
from .permission_assignment_model import PermissionAssignment
|
|
7
|
+
from .permission_route_model import PermissionRoute
|
|
8
|
+
from .tenant_model import Tenant
|
|
9
|
+
from .user_tenant_role_model import UserTenantRole
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Role",
|
|
13
|
+
"Permission",
|
|
14
|
+
"PermissionCategory",
|
|
15
|
+
"Route",
|
|
16
|
+
"UserRole",
|
|
17
|
+
"PermissionAssignment",
|
|
18
|
+
"PermissionRoute",
|
|
19
|
+
"Tenant",
|
|
20
|
+
"UserTenantRole",
|
|
21
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
3
|
+
|
|
4
|
+
class PermissionAssignment(AuthModel, table=True):
|
|
5
|
+
"""
|
|
6
|
+
Polymorphic Permission Assignment for RBAC.
|
|
7
|
+
Description: Assigns a permission directly to an entity (e.g. User or Role).
|
|
8
|
+
"""
|
|
9
|
+
__tablename__ = "permission_assignments"
|
|
10
|
+
|
|
11
|
+
permission_id: int = Field(primary_key=True, foreign_key="permissions.id")
|
|
12
|
+
entity_type: str = Field(primary_key=True, index=True) # e.g., "User", "Team"
|
|
13
|
+
entity_id: int = Field(primary_key=True, index=True)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel, Relationship
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
# Forward reference for relationship
|
|
5
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
6
|
+
|
|
7
|
+
class PermissionCategory(AuthModel, table=True):
|
|
8
|
+
"""
|
|
9
|
+
Permission Category definition for RBAC.
|
|
10
|
+
Description: Groups related permissions together.
|
|
11
|
+
"""
|
|
12
|
+
__tablename__ = "permission_categories"
|
|
13
|
+
|
|
14
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
15
|
+
name: str = Field(index=True, unique=True)
|
|
16
|
+
|
|
17
|
+
# Relationships
|
|
18
|
+
# permissions: List["Permission"] = Relationship(back_populates="category")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel, Relationship
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
4
|
+
|
|
5
|
+
class Permission(AuthModel, table=True):
|
|
6
|
+
"""
|
|
7
|
+
Permission definition for RBAC.
|
|
8
|
+
Description: Represents a specific action or access right.
|
|
9
|
+
"""
|
|
10
|
+
__tablename__ = "permissions"
|
|
11
|
+
|
|
12
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
13
|
+
name: str = Field(index=True, unique=True)
|
|
14
|
+
permission_category_id: int = Field(foreign_key="permission_categories.id")
|
|
15
|
+
|
|
16
|
+
# Relationships
|
|
17
|
+
# category: "PermissionCategory" = Relationship(back_populates="permissions")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
3
|
+
|
|
4
|
+
class PermissionRoute(AuthModel, table=True):
|
|
5
|
+
"""
|
|
6
|
+
Permission Route mapping for RBAC.
|
|
7
|
+
Description: Maps permissions to specific API routes.
|
|
8
|
+
"""
|
|
9
|
+
__tablename__ = "permission_routes"
|
|
10
|
+
|
|
11
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
12
|
+
permission_id: int = Field(foreign_key="permissions.id")
|
|
13
|
+
route_id: int = Field(foreign_key="routes.id")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from sqlmodel import BigInteger, Column, Field
|
|
2
|
+
from sqlmodel import BigInteger, Column, Field, SQLModel
|
|
3
|
+
|
|
4
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Role(AuthModel, table=True):
|
|
8
|
+
"""
|
|
9
|
+
Role definition for RBAC.
|
|
10
|
+
Description: Role model
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__tablename__ = "roles"
|
|
14
|
+
|
|
15
|
+
id: int = Field(
|
|
16
|
+
default=None, sa_column=Column(BigInteger, index=True, primary_key=True)
|
|
17
|
+
)
|
|
18
|
+
name: str = Field(index=True, unique=True)
|
|
19
|
+
description: str | None = Field(default=None)
|
|
20
|
+
is_active: bool = Field(default=True)
|
|
21
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
4
|
+
|
|
5
|
+
class Route(AuthModel, table=True):
|
|
6
|
+
"""
|
|
7
|
+
Route definition for RBAC.
|
|
8
|
+
Description: Represents an API endpoint or accessible resource path.
|
|
9
|
+
"""
|
|
10
|
+
__tablename__ = "routes"
|
|
11
|
+
|
|
12
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
13
|
+
name: str = Field(index=True, unique=True) # The path
|
|
14
|
+
is_active: bool = Field(default=True)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Tenant(AuthModel, table=True):
|
|
6
|
+
"""
|
|
7
|
+
Tenant definition for Multi-tenant RBAC.
|
|
8
|
+
Description: Represents an isolated organization or tenant.
|
|
9
|
+
"""
|
|
10
|
+
__tablename__ = "tenants"
|
|
11
|
+
|
|
12
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
13
|
+
name: str = Field(index=True)
|
|
14
|
+
schema_name: str | None = Field(default=None, description="DB Schema name if applicable")
|
|
15
|
+
db_url: str | None = Field(default=None, description="External DB URL if applicable")
|
|
16
|
+
is_active: bool = Field(default=True)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
3
|
+
|
|
4
|
+
class UserRole(AuthModel, table=True):
|
|
5
|
+
"""
|
|
6
|
+
User Role mapping for RBAC.
|
|
7
|
+
Description: Maps users to their assigned roles.
|
|
8
|
+
"""
|
|
9
|
+
__tablename__ = "user_role"
|
|
10
|
+
|
|
11
|
+
role_id: int = Field(primary_key=True, foreign_key="roles.id")
|
|
12
|
+
user_id: int = Field(primary_key=True, index=True, foreign_key="users.id")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from sqlmodel import Field, SQLModel
|
|
2
|
+
from oauth2fast_fastapi.models import AuthModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UserTenantRole(AuthModel, table=True):
|
|
6
|
+
"""
|
|
7
|
+
Tenant-specific Role mapping for RBAC.
|
|
8
|
+
Description: Maps users to roles within a specific tenant context.
|
|
9
|
+
"""
|
|
10
|
+
__tablename__ = "user_tenant_roles"
|
|
11
|
+
|
|
12
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
13
|
+
user_id: int = Field(index=True, foreign_key="users.id")
|
|
14
|
+
tenant_id: int = Field(foreign_key="tenants.id", index=True)
|
|
15
|
+
role_id: int = Field(foreign_key="roles.id", index=True)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Permission Management Router
|
|
3
|
+
|
|
4
|
+
Endpoints for managing permissions, categories, and assignments.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
|
|
10
|
+
from ..schemas.permission_schema import (
|
|
11
|
+
PermissionCreate,
|
|
12
|
+
PermissionRead,
|
|
13
|
+
PermissionUpdate,
|
|
14
|
+
UserPermissionCreate,
|
|
15
|
+
UserPermissionRead,
|
|
16
|
+
PermissionRouteCreate
|
|
17
|
+
)
|
|
18
|
+
from ..schemas.permission_category_schema import (
|
|
19
|
+
PermissionCategoryCreate,
|
|
20
|
+
PermissionCategoryRead
|
|
21
|
+
)
|
|
22
|
+
from ..services import permission_service
|
|
23
|
+
from oauth2fast_fastapi.dependencies import get_auth_session
|
|
24
|
+
|
|
25
|
+
router = APIRouter(
|
|
26
|
+
prefix="/permissions",
|
|
27
|
+
tags=["Permissions"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Categories
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.post("/categories", response_model=PermissionCategoryRead)
|
|
35
|
+
async def create_category(
|
|
36
|
+
category_data: PermissionCategoryCreate,
|
|
37
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
38
|
+
):
|
|
39
|
+
"""Create a new permission category."""
|
|
40
|
+
try:
|
|
41
|
+
category = await permission_service.create_category(category_data, session)
|
|
42
|
+
return PermissionCategoryRead.model_validate(category)
|
|
43
|
+
except ValueError as e:
|
|
44
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.get("/categories", response_model=list[PermissionCategoryRead])
|
|
48
|
+
async def list_categories(
|
|
49
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
50
|
+
skip: int = 0,
|
|
51
|
+
limit: int = 100,
|
|
52
|
+
):
|
|
53
|
+
"""List all categories."""
|
|
54
|
+
categories = await permission_service.list_categories(session, skip, limit)
|
|
55
|
+
return [PermissionCategoryRead.model_validate(c) for c in categories]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Permissions CRUD
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.post("/", response_model=PermissionRead)
|
|
62
|
+
async def create_permission(
|
|
63
|
+
permission_data: PermissionCreate,
|
|
64
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
65
|
+
):
|
|
66
|
+
"""Create a new permission."""
|
|
67
|
+
try:
|
|
68
|
+
permission = await permission_service.create_permission(
|
|
69
|
+
permission_data, session
|
|
70
|
+
)
|
|
71
|
+
return PermissionRead.model_validate(permission)
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.get("/", response_model=list[PermissionRead])
|
|
77
|
+
async def list_permissions(
|
|
78
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
79
|
+
skip: int = 0,
|
|
80
|
+
limit: int = 100,
|
|
81
|
+
):
|
|
82
|
+
"""List all permissions."""
|
|
83
|
+
permissions = await permission_service.list_permissions(session, skip, limit)
|
|
84
|
+
return [PermissionRead.model_validate(p) for p in permissions]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get("/{permission_id}", response_model=PermissionRead)
|
|
88
|
+
async def get_permission(
|
|
89
|
+
permission_id: int,
|
|
90
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
91
|
+
):
|
|
92
|
+
"""Get permission by ID."""
|
|
93
|
+
permission = await permission_service.get_permission(permission_id, session)
|
|
94
|
+
if not permission:
|
|
95
|
+
raise HTTPException(status_code=404, detail="Permission not found")
|
|
96
|
+
return PermissionRead.model_validate(permission)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.put("/{permission_id}", response_model=PermissionRead)
|
|
100
|
+
async def update_permission(
|
|
101
|
+
permission_id: int,
|
|
102
|
+
permission_data: PermissionUpdate,
|
|
103
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
104
|
+
):
|
|
105
|
+
"""Update a permission."""
|
|
106
|
+
permission = await permission_service.update_permission(
|
|
107
|
+
permission_id, permission_data, session
|
|
108
|
+
)
|
|
109
|
+
if not permission:
|
|
110
|
+
raise HTTPException(status_code=404, detail="Permission not found")
|
|
111
|
+
return PermissionRead.model_validate(permission)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router.delete("/{permission_id}")
|
|
115
|
+
async def delete_permission(
|
|
116
|
+
permission_id: int,
|
|
117
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
118
|
+
):
|
|
119
|
+
"""Delete a permission."""
|
|
120
|
+
success = await permission_service.delete_permission(permission_id, session)
|
|
121
|
+
if not success:
|
|
122
|
+
raise HTTPException(status_code=404, detail="Permission not found")
|
|
123
|
+
return {"message": "Permission deleted successfully"}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Permission Routes (Link)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@router.post("/{permission_id}/routes")
|
|
130
|
+
async def add_permission_route(
|
|
131
|
+
permission_id: int,
|
|
132
|
+
route_data: PermissionRouteCreate,
|
|
133
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
134
|
+
):
|
|
135
|
+
"""Link a permission to a route."""
|
|
136
|
+
# Ensure permission_id matches URL
|
|
137
|
+
if route_data.permission_id != permission_id:
|
|
138
|
+
route_data.permission_id = permission_id
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
await permission_service.add_permission_route(
|
|
142
|
+
permission_id, route_data.route_id, session
|
|
143
|
+
)
|
|
144
|
+
return {"message": "Route linked to permission successfully"}
|
|
145
|
+
except ValueError as e:
|
|
146
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# User Permissions (Direct Assignment)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@router.post("/assign", response_model=UserPermissionRead)
|
|
153
|
+
async def assign_user_permission(
|
|
154
|
+
assignment_data: UserPermissionCreate,
|
|
155
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
156
|
+
):
|
|
157
|
+
"""Assign a permission directly to a user."""
|
|
158
|
+
try:
|
|
159
|
+
user_perm = await permission_service.assign_user_permission(
|
|
160
|
+
assignment_data.user_id, assignment_data.permission_id, session
|
|
161
|
+
)
|
|
162
|
+
return UserPermissionRead(
|
|
163
|
+
permission_id=user_perm.permission_id,
|
|
164
|
+
entity_type=user_perm.entity_type,
|
|
165
|
+
entity_id=user_perm.entity_id
|
|
166
|
+
)
|
|
167
|
+
except ValueError as e:
|
|
168
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@router.get("/user/{user_id}", response_model=list[PermissionRead])
|
|
172
|
+
async def list_user_permissions(
|
|
173
|
+
user_id: int,
|
|
174
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
175
|
+
):
|
|
176
|
+
"""List all permissions assigned directly to a user."""
|
|
177
|
+
permissions = await permission_service.list_user_permissions(user_id, session)
|
|
178
|
+
return [PermissionRead.model_validate(p) for p in permissions]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.delete("/user/{user_id}/permission/{permission_id}")
|
|
182
|
+
async def remove_user_permission(
|
|
183
|
+
user_id: int,
|
|
184
|
+
permission_id: int,
|
|
185
|
+
session: AsyncSession = Depends(get_auth_session),
|
|
186
|
+
):
|
|
187
|
+
"""Remove a direct permission from a user."""
|
|
188
|
+
success = await permission_service.remove_user_permission(user_id, permission_id, session)
|
|
189
|
+
if not success:
|
|
190
|
+
raise HTTPException(status_code=404, detail="User permission assignment not found")
|
|
191
|
+
return {"message": "Permission removed from user successfully"}
|