fastapi-rbac-authz 0.3.0__py3-none-any.whl → 0.5.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.
- fastapi_rbac/__init__.py +2 -10
- fastapi_rbac/context.py +17 -22
- fastapi_rbac/core.py +34 -60
- fastapi_rbac/dependencies.py +25 -151
- fastapi_rbac/{exceptions.py → errors.py} +2 -3
- fastapi_rbac/permissions.py +3 -1
- fastapi_rbac/router.py +24 -70
- fastapi_rbac/ui/routes.py +2 -3
- fastapi_rbac/ui/schema.py +3 -3
- {fastapi_rbac_authz-0.3.0.dist-info → fastapi_rbac_authz-0.5.0.dist-info}/METADATA +42 -30
- fastapi_rbac_authz-0.5.0.dist-info/RECORD +15 -0
- fastapi_rbac_authz-0.3.0.dist-info/RECORD +0 -15
- {fastapi_rbac_authz-0.3.0.dist-info → fastapi_rbac_authz-0.5.0.dist-info}/WHEEL +0 -0
fastapi_rbac/__init__.py
CHANGED
|
@@ -4,13 +4,8 @@ __version__ = "0.1.0"
|
|
|
4
4
|
|
|
5
5
|
from fastapi_rbac.context import ContextualAuthz
|
|
6
6
|
from fastapi_rbac.core import RBACAuthz
|
|
7
|
-
from fastapi_rbac.dependencies import
|
|
8
|
-
|
|
9
|
-
create_auth_dependency,
|
|
10
|
-
create_authz_dependency,
|
|
11
|
-
evaluate_permissions,
|
|
12
|
-
)
|
|
13
|
-
from fastapi_rbac.exceptions import Forbidden
|
|
7
|
+
from fastapi_rbac.dependencies import create_authz_dependency
|
|
8
|
+
from fastapi_rbac.errors import Forbidden
|
|
14
9
|
from fastapi_rbac.permissions import (
|
|
15
10
|
Contextual,
|
|
16
11
|
Global,
|
|
@@ -22,14 +17,11 @@ from fastapi_rbac.router import RBACRouter
|
|
|
22
17
|
__all__ = [
|
|
23
18
|
"RBACAuthz",
|
|
24
19
|
"RBACRouter",
|
|
25
|
-
"RBACUser",
|
|
26
20
|
"ContextualAuthz",
|
|
27
21
|
"Global",
|
|
28
22
|
"Contextual",
|
|
29
23
|
"PermissionGrant",
|
|
30
24
|
"PermissionScope",
|
|
31
25
|
"Forbidden",
|
|
32
|
-
"create_auth_dependency",
|
|
33
26
|
"create_authz_dependency",
|
|
34
|
-
"evaluate_permissions",
|
|
35
27
|
]
|
fastapi_rbac/context.py
CHANGED
|
@@ -1,34 +1,29 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Generic, TypeVar
|
|
3
2
|
|
|
4
|
-
UserT = TypeVar("UserT")
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
class ContextualAuthz(ABC, Generic[UserT]):
|
|
4
|
+
class ContextualAuthz(ABC):
|
|
8
5
|
"""Base class for contextual authorization checks.
|
|
9
6
|
|
|
10
|
-
Subclasses
|
|
11
|
-
additional dependencies like database sessions.
|
|
7
|
+
Subclasses can use FastAPI dependencies in the __init__ method.
|
|
12
8
|
|
|
13
9
|
Example:
|
|
14
|
-
class MyContext(ContextualAuthz[User]):
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
user: User,
|
|
18
|
-
request: Request,
|
|
19
|
-
db: AsyncSession = Depends(get_db),
|
|
20
|
-
):
|
|
21
|
-
self.user = user
|
|
22
|
-
self.request = request
|
|
23
|
-
self.db = db
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
>>> class MyContext(ContextualAuthz):
|
|
12
|
+
>>> def __init__(
|
|
13
|
+
>>> self,
|
|
14
|
+
>>> user: Annotated[User, Depends(get_current_user)], # your auth dep
|
|
15
|
+
>>> request: Request, # fastapi dep
|
|
16
|
+
>>> db: AsyncSession = Depends(get_db), # your database dep
|
|
17
|
+
>>> ):
|
|
18
|
+
>>> self.user = user
|
|
19
|
+
>>> self.request = request
|
|
20
|
+
>>> self.db = db
|
|
21
|
+
>>>
|
|
22
|
+
>>> async def has_permissions(self) -> bool:
|
|
23
|
+
>>> # Check access using self.user, self.request, self.db
|
|
24
|
+
>>> return True
|
|
28
25
|
"""
|
|
29
26
|
|
|
30
|
-
user: UserT
|
|
31
|
-
|
|
32
27
|
@abstractmethod
|
|
33
28
|
async def has_permissions(self) -> bool:
|
|
34
29
|
"""Check if the user has permission in this context.
|
|
@@ -36,4 +31,4 @@ class ContextualAuthz(ABC, Generic[UserT]):
|
|
|
36
31
|
Returns:
|
|
37
32
|
True if access should be granted, False otherwise.
|
|
38
33
|
"""
|
|
39
|
-
|
|
34
|
+
raise NotImplementedError()
|
fastapi_rbac/core.py
CHANGED
|
@@ -1,42 +1,15 @@
|
|
|
1
1
|
from collections.abc import Awaitable, Callable
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
from fastapi import APIRouter, FastAPI
|
|
5
5
|
|
|
6
|
-
from fastapi_rbac.dependencies import
|
|
6
|
+
from fastapi_rbac.dependencies import _rbac_roles_dependency_placeholder
|
|
7
7
|
from fastapi_rbac.permissions import PermissionGrant
|
|
8
|
+
from fastapi_rbac.router import RBACRouter
|
|
9
|
+
from fastapi_rbac.ui.routes import create_ui_router
|
|
8
10
|
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
pass
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _wrap_include_router(app: FastAPI, original_include_router: Callable[..., None]) -> Callable[..., None]:
|
|
16
|
-
"""Wrap FastAPI's include_router to track RBACRouters."""
|
|
17
|
-
|
|
18
|
-
def wrapped_include_router(
|
|
19
|
-
router: APIRouter,
|
|
20
|
-
*,
|
|
21
|
-
prefix: str = "",
|
|
22
|
-
**kwargs: Any,
|
|
23
|
-
) -> None:
|
|
24
|
-
# Import here to avoid circular import
|
|
25
|
-
from fastapi_rbac.router import RBACRouter
|
|
26
|
-
|
|
27
|
-
# Track RBAC routers in app state
|
|
28
|
-
if isinstance(router, RBACRouter):
|
|
29
|
-
if not hasattr(app.state, "_rbac_routers_"):
|
|
30
|
-
app.state._rbac_routers_ = []
|
|
31
|
-
app.state._rbac_routers_.append((prefix, router))
|
|
32
|
-
|
|
33
|
-
# Call original method
|
|
34
|
-
return original_include_router(router, prefix=prefix, **kwargs)
|
|
35
|
-
|
|
36
|
-
return wrapped_include_router
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class RBACAuthz(Generic[UserT]):
|
|
12
|
+
class RBACAuthz:
|
|
40
13
|
"""Main RBAC authorization configuration.
|
|
41
14
|
|
|
42
15
|
Attaches to a FastAPI application and provides authorization
|
|
@@ -44,68 +17,69 @@ class RBACAuthz(Generic[UserT]):
|
|
|
44
17
|
|
|
45
18
|
Args:
|
|
46
19
|
app: The FastAPI application instance.
|
|
47
|
-
get_roles: Callable that extracts role strings from a user object.
|
|
48
20
|
permissions: Mapping of role names to sets of permission grants.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
21
|
+
roles_dependency: FastAPI dependency that returns the user's roles as set[str].
|
|
22
|
+
This dependency is injected into all RBAC-protected endpoints via
|
|
23
|
+
FastAPI's dependency_overrides mechanism, and will be evaluated before RBAC checks
|
|
52
24
|
ui_path: Optional path to mount the authorization UI (e.g., "/_rbac").
|
|
53
|
-
ui_permissions: Optional set of permissions required to access the UI.
|
|
54
25
|
"""
|
|
55
26
|
|
|
56
27
|
def __init__(
|
|
57
28
|
self,
|
|
58
29
|
app: FastAPI,
|
|
59
|
-
get_roles: Callable[[UserT], set[str]],
|
|
60
30
|
permissions: dict[str, set[PermissionGrant]],
|
|
61
|
-
|
|
31
|
+
roles_dependency: Callable[..., set[str]] | Callable[..., Awaitable[set[str]]],
|
|
62
32
|
ui_path: str | None = None,
|
|
63
|
-
ui_permissions: set[str] | None = None,
|
|
64
33
|
) -> None:
|
|
65
34
|
self.app = app
|
|
66
|
-
self.get_roles = get_roles
|
|
67
35
|
self.permissions = permissions
|
|
68
|
-
self.
|
|
36
|
+
self.roles_dependency = roles_dependency
|
|
69
37
|
self.ui_path = ui_path
|
|
70
|
-
self.ui_permissions = ui_permissions
|
|
71
38
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
39
|
+
# Instance attributes for state management
|
|
40
|
+
self.routers: list[tuple[str, RBACRouter]] = []
|
|
41
|
+
self._include_router_wrapped: bool = False
|
|
75
42
|
|
|
76
43
|
# Attach to app state for access from routers
|
|
77
44
|
app.state.rbac = self
|
|
78
45
|
|
|
79
|
-
#
|
|
80
|
-
# This allows the
|
|
46
|
+
# Override the placeholder dependency with user's roles dependency
|
|
47
|
+
# This allows the roles dependency to be injected into all
|
|
81
48
|
# RBAC-protected endpoints with proper FastAPI dependency resolution
|
|
82
|
-
|
|
83
|
-
app.dependency_overrides[_rbac_user_dependency_placeholder] = user_dependency
|
|
84
|
-
|
|
85
|
-
# Wrap include_router to track RBAC routers
|
|
49
|
+
app.dependency_overrides[_rbac_roles_dependency_placeholder] = roles_dependency
|
|
86
50
|
self._wrap_app_include_router()
|
|
87
51
|
|
|
88
|
-
# Mount UI if path specified
|
|
89
52
|
if ui_path:
|
|
90
53
|
self._mount_ui()
|
|
91
54
|
|
|
92
55
|
def _wrap_app_include_router(self) -> None:
|
|
93
56
|
"""Wrap the app's include_router method to track RBACRouters."""
|
|
94
|
-
|
|
95
|
-
if hasattr(self.app, "_rbac_include_router_wrapped_"):
|
|
57
|
+
if self._include_router_wrapped:
|
|
96
58
|
return
|
|
97
59
|
|
|
98
60
|
original_include_router = self.app.include_router
|
|
99
|
-
self.app.include_router =
|
|
100
|
-
self.
|
|
61
|
+
self.app.include_router = self._create_wrapped_include_router(original_include_router) # type: ignore[method-assign]
|
|
62
|
+
self._include_router_wrapped = True
|
|
63
|
+
|
|
64
|
+
def _create_wrapped_include_router(self, original_include_router: Callable[..., None]) -> Callable[..., None]:
|
|
65
|
+
"""Create a wrapped include_router that tracks RBACRouters."""
|
|
66
|
+
|
|
67
|
+
def wrapped_include_router(
|
|
68
|
+
router: APIRouter,
|
|
69
|
+
*,
|
|
70
|
+
prefix: str = "",
|
|
71
|
+
**kwargs: Any,
|
|
72
|
+
) -> None:
|
|
73
|
+
if isinstance(router, RBACRouter):
|
|
74
|
+
self.routers.append((prefix, router))
|
|
75
|
+
return original_include_router(router, prefix=prefix, **kwargs)
|
|
76
|
+
|
|
77
|
+
return wrapped_include_router
|
|
101
78
|
|
|
102
79
|
def _mount_ui(self) -> None:
|
|
103
80
|
"""Mount the authorization visualization UI."""
|
|
104
81
|
if not self.ui_path:
|
|
105
82
|
return
|
|
106
83
|
|
|
107
|
-
# Import here to avoid circular import
|
|
108
|
-
from fastapi_rbac.ui.routes import create_ui_router
|
|
109
|
-
|
|
110
84
|
ui_router = create_ui_router(self.ui_path)
|
|
111
85
|
self.app.include_router(ui_router, prefix=self.ui_path)
|
fastapi_rbac/dependencies.py
CHANGED
|
@@ -1,96 +1,29 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
from collections.abc import
|
|
3
|
-
from typing import
|
|
2
|
+
from collections.abc import Callable, Coroutine
|
|
3
|
+
from typing import Annotated, Any
|
|
4
4
|
|
|
5
5
|
from fastapi import Depends, Request
|
|
6
6
|
|
|
7
7
|
from fastapi_rbac.context import ContextualAuthz
|
|
8
|
-
from fastapi_rbac.
|
|
8
|
+
from fastapi_rbac.errors import Forbidden
|
|
9
9
|
from fastapi_rbac.permissions import (
|
|
10
10
|
has_global_permission,
|
|
11
11
|
has_permission,
|
|
12
12
|
resolve_grants,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from fastapi_rbac.core import RBACAuthz
|
|
17
|
-
|
|
18
|
-
UserT = TypeVar("UserT")
|
|
19
|
-
|
|
20
15
|
# Type alias for context classes
|
|
21
|
-
ContextClass = type[ContextualAuthz
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def RBACUser(request: Request) -> Any:
|
|
25
|
-
"""Get the current authenticated user for use in context classes.
|
|
26
|
-
|
|
27
|
-
This dependency reads the user from request.state.user, which is set
|
|
28
|
-
by the auth dependency created via create_auth_dependency().
|
|
29
|
-
|
|
30
|
-
Usage in context classes:
|
|
31
|
-
class MyContext(ContextualAuthz[User]):
|
|
32
|
-
def __init__(
|
|
33
|
-
self,
|
|
34
|
-
user: Annotated[User, Depends(RBACUser)],
|
|
35
|
-
request: Request,
|
|
36
|
-
):
|
|
37
|
-
self.user = user
|
|
38
|
-
self.request = request
|
|
16
|
+
ContextClass = type[ContextualAuthz]
|
|
39
17
|
|
|
40
|
-
async def has_permissions(self) -> bool:
|
|
41
|
-
# Check permissions based on user and request context
|
|
42
|
-
return self.user.id in allowed_users
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
async def _rbac_roles_dependency_placeholder(request: Request) -> set[str]: # noqa: ARG001
|
|
20
|
+
"""Placeholder dependency for user roles.
|
|
46
21
|
|
|
47
|
-
|
|
48
|
-
|
|
22
|
+
This placeholder is replaced at runtime via FastAPI's dependency_overrides
|
|
23
|
+
mechanism when RBACAuthz is initialized. The roles_dependency provided to
|
|
24
|
+
RBACAuthz will be used instead of this placeholder.
|
|
49
25
|
"""
|
|
50
|
-
|
|
51
|
-
if user is None:
|
|
52
|
-
raise Forbidden("User not authenticated")
|
|
53
|
-
return user
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def create_auth_dependency(
|
|
57
|
-
rbac: "RBACAuthz[UserT]", # noqa: ARG001 - kept for API consistency with RBACRouter
|
|
58
|
-
user_dependency: Callable[..., UserT] | Callable[..., Awaitable[UserT]],
|
|
59
|
-
) -> Callable[..., Coroutine[Any, Any, UserT]]:
|
|
60
|
-
"""Create a typed auth dependency for use in endpoints.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
rbac: The RBACAuthz configuration instance. Currently unused but kept for
|
|
64
|
-
API consistency - RBACRouter stores the rbac reference for permission
|
|
65
|
-
evaluation.
|
|
66
|
-
user_dependency: A FastAPI dependency that returns the authenticated user.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
A dependency that can be used with Depends() in endpoint signatures.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
async def auth_dependency(
|
|
73
|
-
request: Request,
|
|
74
|
-
user: Annotated[UserT, Depends(user_dependency)],
|
|
75
|
-
) -> UserT:
|
|
76
|
-
# Store user in request state for authz dependency
|
|
77
|
-
request.state.user = user
|
|
78
|
-
return user
|
|
79
|
-
|
|
80
|
-
return auth_dependency
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
async def _rbac_user_dependency_placeholder(request: Request) -> Any:
|
|
84
|
-
"""Placeholder dependency for user authentication.
|
|
85
|
-
|
|
86
|
-
This placeholder is used in the authz_dependency signature and gets replaced
|
|
87
|
-
at runtime via FastAPI's dependency_overrides mechanism when RBACAuthz is
|
|
88
|
-
initialized with a user_dependency.
|
|
89
|
-
|
|
90
|
-
If no user_dependency is configured, this falls back to reading from
|
|
91
|
-
request.state.user (which must be set by the user's own auth mechanism).
|
|
92
|
-
"""
|
|
93
|
-
return getattr(request.state, "user", None)
|
|
26
|
+
raise RuntimeError("RBACAuthz not configured with roles_dependency")
|
|
94
27
|
|
|
95
28
|
|
|
96
29
|
def create_authz_dependency(
|
|
@@ -100,7 +33,7 @@ def create_authz_dependency(
|
|
|
100
33
|
"""Create an authorization dependency that uses FastAPI's full DI for contexts.
|
|
101
34
|
|
|
102
35
|
This function creates a FastAPI dependency that:
|
|
103
|
-
1. Resolves user via the injected
|
|
36
|
+
1. Resolves a list of user roles via the injected roles_dependency
|
|
104
37
|
2. Gets RBAC config from app.state.rbac
|
|
105
38
|
3. Uses FastAPI's Depends(context_class) to instantiate each context
|
|
106
39
|
- Context classes can use ANY FastAPI dependency patterns in __init__:
|
|
@@ -111,9 +44,9 @@ def create_authz_dependency(
|
|
|
111
44
|
- db: Db (via Depends)
|
|
112
45
|
4. Calls has_permissions() on each resolved context
|
|
113
46
|
|
|
114
|
-
The
|
|
115
|
-
When RBACAuthz is initialized with a
|
|
116
|
-
|
|
47
|
+
The roles_dependency is injected via FastAPI's dependency_overrides mechanism.
|
|
48
|
+
When RBACAuthz is initialized with a roles_dependency, it overrides the
|
|
49
|
+
_rbac_roles_dependency_placeholder with the provided dependency.
|
|
117
50
|
|
|
118
51
|
Args:
|
|
119
52
|
required_permissions: Set of permission strings required for access.
|
|
@@ -124,10 +57,13 @@ def create_authz_dependency(
|
|
|
124
57
|
"""
|
|
125
58
|
# Build context parameters - each context class becomes a Depends(context_class)
|
|
126
59
|
# FastAPI will resolve all __init__ parameters automatically
|
|
60
|
+
if not required_permissions and not context_classes:
|
|
61
|
+
raise RuntimeError("Endpoint must be protected with either permissions or contexts")
|
|
62
|
+
|
|
127
63
|
context_params: list[inspect.Parameter] = []
|
|
128
64
|
for i, ctx_class in enumerate(context_classes):
|
|
129
65
|
param = inspect.Parameter(
|
|
130
|
-
f"
|
|
66
|
+
f"_fastapi_rbac_authz_ctx_{i}_",
|
|
131
67
|
inspect.Parameter.KEYWORD_ONLY,
|
|
132
68
|
default=None,
|
|
133
69
|
annotation=Annotated[ctx_class, Depends(ctx_class)],
|
|
@@ -136,25 +72,18 @@ def create_authz_dependency(
|
|
|
136
72
|
|
|
137
73
|
async def authz_dependency(
|
|
138
74
|
request: Request,
|
|
139
|
-
|
|
75
|
+
_rbac_roles_: Annotated[set[str], Depends(_rbac_roles_dependency_placeholder)],
|
|
140
76
|
**kwargs: Any,
|
|
141
77
|
) -> None:
|
|
142
78
|
"""Authorization dependency that checks permissions and contexts."""
|
|
143
|
-
# Get RBAC config from app state
|
|
144
79
|
rbac = getattr(request.app.state, "rbac", None)
|
|
145
80
|
if rbac is None:
|
|
146
81
|
raise RuntimeError("RBACAuthz not configured. Make sure to create an RBACAuthz instance with your app.")
|
|
147
82
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
raise Forbidden("User not authenticated")
|
|
152
|
-
|
|
153
|
-
# Check permissions first
|
|
154
|
-
if not required_permissions and not context_classes:
|
|
155
|
-
raise RuntimeError("Endpoint must be protected with permissions or contexts")
|
|
83
|
+
roles = _rbac_roles_
|
|
84
|
+
if not roles:
|
|
85
|
+
raise Forbidden("User has no roles")
|
|
156
86
|
|
|
157
|
-
roles = rbac.get_roles(user)
|
|
158
87
|
grants = resolve_grants(roles, rbac.permissions)
|
|
159
88
|
|
|
160
89
|
if not grants:
|
|
@@ -175,7 +104,7 @@ def create_authz_dependency(
|
|
|
175
104
|
# Context instances are already resolved by FastAPI via Depends(context_class)
|
|
176
105
|
if need_contextual_check:
|
|
177
106
|
for i in range(len(context_classes)):
|
|
178
|
-
context = kwargs.get(f"
|
|
107
|
+
context = kwargs.get(f"_fastapi_rbac_authz_ctx_{i}_")
|
|
179
108
|
if context is not None and not await context.has_permissions():
|
|
180
109
|
raise Forbidden()
|
|
181
110
|
|
|
@@ -187,68 +116,13 @@ def create_authz_dependency(
|
|
|
187
116
|
annotation=Request,
|
|
188
117
|
),
|
|
189
118
|
inspect.Parameter(
|
|
190
|
-
"
|
|
119
|
+
"_rbac_roles_",
|
|
191
120
|
inspect.Parameter.KEYWORD_ONLY,
|
|
192
121
|
default=None,
|
|
193
|
-
annotation=Annotated[
|
|
122
|
+
annotation=Annotated[set[str], Depends(_rbac_roles_dependency_placeholder)],
|
|
194
123
|
),
|
|
195
124
|
]
|
|
196
125
|
|
|
197
126
|
new_sig = inspect.Signature(parameters=base_params + context_params)
|
|
198
127
|
authz_dependency.__signature__ = new_sig # type: ignore[attr-defined]
|
|
199
|
-
|
|
200
128
|
return authz_dependency
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
async def evaluate_permissions(
|
|
204
|
-
user: Any,
|
|
205
|
-
request: Request,
|
|
206
|
-
rbac: "RBACAuthz[Any]",
|
|
207
|
-
required_permissions: set[str],
|
|
208
|
-
context_classes: list[ContextClass],
|
|
209
|
-
) -> None:
|
|
210
|
-
"""Directly evaluate permissions without FastAPI dependency injection.
|
|
211
|
-
|
|
212
|
-
This function is useful for testing or when you need to check permissions
|
|
213
|
-
outside of a FastAPI endpoint context. Unlike create_authz_dependency(),
|
|
214
|
-
this function instantiates context classes directly with user and request.
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
user: The authenticated user object.
|
|
218
|
-
request: The current HTTP request.
|
|
219
|
-
rbac: The RBACAuthz configuration instance.
|
|
220
|
-
required_permissions: Set of permission strings required for access.
|
|
221
|
-
context_classes: List of ContextualAuthz subclasses to check.
|
|
222
|
-
|
|
223
|
-
Raises:
|
|
224
|
-
Forbidden: If the user does not have the required permissions.
|
|
225
|
-
RuntimeError: If no permissions or contexts are specified.
|
|
226
|
-
"""
|
|
227
|
-
if not required_permissions and not context_classes:
|
|
228
|
-
raise RuntimeError("Endpoint must be protected with permissions or contexts")
|
|
229
|
-
|
|
230
|
-
roles = rbac.get_roles(user)
|
|
231
|
-
grants = resolve_grants(roles, rbac.permissions)
|
|
232
|
-
|
|
233
|
-
if not grants:
|
|
234
|
-
raise Forbidden()
|
|
235
|
-
|
|
236
|
-
need_contextual_check = not required_permissions
|
|
237
|
-
|
|
238
|
-
for required in required_permissions:
|
|
239
|
-
if has_global_permission(grants, required):
|
|
240
|
-
# Global permission - no need for contextual check for this permission
|
|
241
|
-
continue
|
|
242
|
-
|
|
243
|
-
need_contextual_check = True
|
|
244
|
-
if not has_permission(grants, required):
|
|
245
|
-
raise Forbidden()
|
|
246
|
-
|
|
247
|
-
# Run contextual checks if needed
|
|
248
|
-
# Instantiate context classes directly with user and request
|
|
249
|
-
if need_contextual_check:
|
|
250
|
-
for ctx_class in context_classes:
|
|
251
|
-
# Context classes are expected to accept user and request in their __init__
|
|
252
|
-
context = ctx_class(user=user, request=request) # type: ignore[call-arg]
|
|
253
|
-
if not await context.has_permissions():
|
|
254
|
-
raise Forbidden()
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from fastapi import HTTPException
|
|
2
|
+
from starlette.status import HTTP_403_FORBIDDEN
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class Forbidden(HTTPException):
|
|
5
|
-
"""403 Forbidden - user lacks required permissions."""
|
|
6
|
-
|
|
7
6
|
def __init__(self, detail: str = "Forbidden") -> None:
|
|
8
|
-
super().__init__(status_code=
|
|
7
|
+
super().__init__(status_code=HTTP_403_FORBIDDEN, detail=detail)
|
fastapi_rbac/permissions.py
CHANGED
|
@@ -23,9 +23,11 @@ class PermissionGrant:
|
|
|
23
23
|
def __hash__(self) -> int:
|
|
24
24
|
return hash((self.permission, self.scope))
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def __str__(self) -> str:
|
|
27
27
|
return f"{self.__class__.__name__}({self.permission!r})"
|
|
28
28
|
|
|
29
|
+
__repr__ = __str__
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
class Global(PermissionGrant):
|
|
31
33
|
"""A global permission grant - bypasses contextual checks."""
|
fastapi_rbac/router.py
CHANGED
|
@@ -9,35 +9,30 @@ from fastapi import APIRouter, Depends
|
|
|
9
9
|
|
|
10
10
|
from fastapi_rbac.context import ContextualAuthz
|
|
11
11
|
from fastapi_rbac.dependencies import create_authz_dependency
|
|
12
|
-
from fastapi_rbac.permissions import WILDCARD
|
|
12
|
+
from fastapi_rbac.permissions import SEPARATOR, WILDCARD
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
ContextClass = type[ContextualAuthz[Any]]
|
|
14
|
+
ContextClass = type[ContextualAuthz] # alias
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _validate_permissions(permissions: set[str] | None, location: str) -> None:
|
|
24
|
-
"""Validate that permissions don't contain wildcards.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
permissions: Set of permission strings to validate.
|
|
28
|
-
location: Description of where the permissions are defined (for error message).
|
|
29
|
-
|
|
30
|
-
Raises:
|
|
31
|
-
RuntimeError: If any permission contains a wildcard.
|
|
32
|
-
"""
|
|
17
|
+
def _validate_permissions(
|
|
18
|
+
permissions: set[str] | None,
|
|
19
|
+
location: str,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Various startup permissions validations"""
|
|
33
22
|
if permissions is None:
|
|
34
23
|
return
|
|
35
24
|
for perm in permissions:
|
|
36
|
-
if
|
|
25
|
+
if WILDCARD in perm:
|
|
37
26
|
raise RuntimeError(
|
|
38
27
|
f"Wildcard permissions are not allowed in {location}. "
|
|
39
28
|
f"Found '{perm}'. Wildcards should only be used in role grants."
|
|
40
29
|
)
|
|
30
|
+
if SEPARATOR not in perm:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
f"Each permission must decline at least one resource and one action. Found '{perm}' at {location}"
|
|
33
|
+
)
|
|
34
|
+
if perm.startswith(SEPARATOR) or perm.startswith(WILDCARD):
|
|
35
|
+
raise RuntimeError(f"Permission must explicitly define resource. Found '{perm}' at {location}")
|
|
41
36
|
|
|
42
37
|
|
|
43
38
|
class RBACRouter(APIRouter):
|
|
@@ -73,7 +68,6 @@ class RBACRouter(APIRouter):
|
|
|
73
68
|
contexts: list[ContextClass] | None = None,
|
|
74
69
|
**kwargs: Any,
|
|
75
70
|
) -> None:
|
|
76
|
-
# Validate no wildcards in router-level permissions
|
|
77
71
|
_validate_permissions(permissions, "router permissions")
|
|
78
72
|
|
|
79
73
|
super().__init__(**kwargs)
|
|
@@ -81,24 +75,6 @@ class RBACRouter(APIRouter):
|
|
|
81
75
|
self.default_contexts: list[ContextClass] = contexts or []
|
|
82
76
|
self.endpoint_metadata: dict[tuple[str, str], dict[str, Any]] = {}
|
|
83
77
|
|
|
84
|
-
def _create_authz_dependency(
|
|
85
|
-
self,
|
|
86
|
-
permissions: set[str],
|
|
87
|
-
contexts: list[ContextClass],
|
|
88
|
-
) -> Callable[..., Any]:
|
|
89
|
-
"""Create an authorization dependency for an endpoint.
|
|
90
|
-
|
|
91
|
-
This dependency will be injected into the endpoint and will check
|
|
92
|
-
permissions before the endpoint handler is called.
|
|
93
|
-
|
|
94
|
-
Uses create_authz_dependency from dependencies.py which supports
|
|
95
|
-
resolving Depends() parameters in context classes.
|
|
96
|
-
"""
|
|
97
|
-
return create_authz_dependency(
|
|
98
|
-
required_permissions=permissions,
|
|
99
|
-
context_classes=contexts,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
78
|
def _resolve_permissions_and_contexts(
|
|
103
79
|
self,
|
|
104
80
|
path: str,
|
|
@@ -117,13 +93,8 @@ class RBACRouter(APIRouter):
|
|
|
117
93
|
Returns:
|
|
118
94
|
Tuple of (final_permissions, final_contexts).
|
|
119
95
|
"""
|
|
120
|
-
# Validate no wildcards in endpoint-level permissions
|
|
121
96
|
_validate_permissions(permissions, "endpoint permissions")
|
|
122
|
-
|
|
123
|
-
# Resolve final permissions: endpoint overrides router
|
|
124
97
|
final_permissions = permissions if permissions is not None else self.default_permissions
|
|
125
|
-
|
|
126
|
-
# Resolve final contexts: endpoint merges with router
|
|
127
98
|
final_contexts = list(self.default_contexts)
|
|
128
99
|
if contexts:
|
|
129
100
|
final_contexts.extend(contexts)
|
|
@@ -162,33 +133,16 @@ class RBACRouter(APIRouter):
|
|
|
162
133
|
new_params = params + [authz_param]
|
|
163
134
|
new_sig = sig.replace(parameters=new_params)
|
|
164
135
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*args: Any,
|
|
173
|
-
_rbac_authz_check_: Annotated[None, Depends(authz_dep)] = None,
|
|
174
|
-
**kwargs: Any,
|
|
175
|
-
) -> Any:
|
|
176
|
-
return await endpoint(*args, **kwargs)
|
|
177
|
-
|
|
178
|
-
wrapped_async.__signature__ = new_sig # type: ignore[attr-defined]
|
|
179
|
-
return wrapped_async
|
|
180
|
-
else:
|
|
181
|
-
|
|
182
|
-
@wraps(endpoint)
|
|
183
|
-
def wrapped_sync(
|
|
184
|
-
*args: Any,
|
|
185
|
-
_rbac_authz_check_: Annotated[None, Depends(authz_dep)] = None,
|
|
186
|
-
**kwargs: Any,
|
|
187
|
-
) -> Any:
|
|
188
|
-
return endpoint(*args, **kwargs)
|
|
136
|
+
@wraps(endpoint)
|
|
137
|
+
async def wrapped_async(
|
|
138
|
+
*args: Any,
|
|
139
|
+
_rbac_authz_check_: Annotated[None, Depends(authz_dep)] = None,
|
|
140
|
+
**kwargs: Any,
|
|
141
|
+
) -> Any:
|
|
142
|
+
return await endpoint(*args, **kwargs)
|
|
189
143
|
|
|
190
|
-
|
|
191
|
-
|
|
144
|
+
wrapped_async.__signature__ = new_sig # type: ignore[attr-defined]
|
|
145
|
+
return wrapped_async
|
|
192
146
|
|
|
193
147
|
def _add_route_with_authz(
|
|
194
148
|
self,
|
|
@@ -218,7 +172,7 @@ class RBACRouter(APIRouter):
|
|
|
218
172
|
|
|
219
173
|
# If there are permissions or contexts, wrap the endpoint
|
|
220
174
|
if final_permissions or final_contexts:
|
|
221
|
-
authz_dep =
|
|
175
|
+
authz_dep = create_authz_dependency(final_permissions, final_contexts)
|
|
222
176
|
endpoint = self._wrap_endpoint_with_authz(endpoint, authz_dep)
|
|
223
177
|
|
|
224
178
|
# Attach RBAC metadata to endpoint for later introspection
|
fastapi_rbac/ui/routes.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from fastapi import APIRouter, Request
|
|
9
9
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
@@ -59,8 +59,7 @@ def create_ui_router(ui_path: str) -> APIRouter:
|
|
|
59
59
|
include_in_schema=False,
|
|
60
60
|
)
|
|
61
61
|
async def get_schema(request: Request) -> JSONResponse:
|
|
62
|
-
|
|
63
|
-
rbac: RBACAuthz[Any] = request.app.state.rbac
|
|
62
|
+
rbac: RBACAuthz = request.app.state.rbac
|
|
64
63
|
schema = build_ui_schema(request.app, rbac)
|
|
65
64
|
return JSONResponse(content=schema.model_dump())
|
|
66
65
|
|
fastapi_rbac/ui/schema.py
CHANGED
|
@@ -147,8 +147,8 @@ def _build_endpoints_schema(app: FastAPI) -> tuple[list[EndpointSchema], dict[st
|
|
|
147
147
|
context_classes: dict[str, type] = {}
|
|
148
148
|
seen_endpoints: set[tuple[str, str]] = set()
|
|
149
149
|
|
|
150
|
-
# Get all registered RBAC routers from
|
|
151
|
-
rbac_routers: list[tuple[str, Any]] = getattr(app.state, "
|
|
150
|
+
# Get all registered RBAC routers from RBACAuthz instance
|
|
151
|
+
rbac_routers: list[tuple[str, Any]] = getattr(app.state.rbac, "routers", [])
|
|
152
152
|
|
|
153
153
|
# Build metadata map from all registered routers
|
|
154
154
|
metadata_map: dict[tuple[str, str], dict[str, Any]] = {}
|
|
@@ -232,7 +232,7 @@ def _build_contexts_schema(
|
|
|
232
232
|
]
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
def build_ui_schema(app: FastAPI, rbac: RBACAuthz
|
|
235
|
+
def build_ui_schema(app: FastAPI, rbac: RBACAuthz) -> UISchema:
|
|
236
236
|
"""Build the complete UI schema for RBAC visualization.
|
|
237
237
|
|
|
238
238
|
Args:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-rbac-authz
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Role-based access control with contextual authorization for FastAPI
|
|
5
5
|
Project-URL: Homepage, https://github.com/parikls/fastapi-rbac
|
|
6
6
|
Author-email: Dmytro Smyk <porovozls@gmail.com>
|
|
@@ -24,6 +24,14 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
|
|
25
25
|
Role-based access control with contextual authorization for FastAPI.
|
|
26
26
|
|
|
27
|
+
Library IS NOT responsible for authentication. You can use any authentication mechanism you want.
|
|
28
|
+
|
|
29
|
+
What you need to provide:
|
|
30
|
+
- A dependency that returns the authenticated user's roles as `set[str]`
|
|
31
|
+
- Permission definitions per role
|
|
32
|
+
- Use `RBACRouter` instead of `APIRouter` for protected routes
|
|
33
|
+
- Define permissions and context checks per endpoint
|
|
34
|
+
|
|
27
35
|
## Installation
|
|
28
36
|
|
|
29
37
|
```bash
|
|
@@ -36,16 +44,26 @@ pip install fastapi-rbac-authz
|
|
|
36
44
|
from typing import Annotated
|
|
37
45
|
from fastapi import Depends, FastAPI
|
|
38
46
|
from fastapi_rbac import (
|
|
39
|
-
RBACAuthz, RBACRouter, Global, Contextual, ContextualAuthz
|
|
47
|
+
RBACAuthz, RBACRouter, Global, Contextual, ContextualAuthz
|
|
40
48
|
)
|
|
41
49
|
|
|
42
|
-
# 1.
|
|
50
|
+
# 1. You might have your user model (or you may not, we don't care)
|
|
43
51
|
class User:
|
|
44
52
|
def __init__(self, user_id: str, roles: set[str]):
|
|
45
53
|
self.user_id = user_id
|
|
46
54
|
self.roles = roles
|
|
47
55
|
|
|
48
|
-
# 2.
|
|
56
|
+
# 2. Assuming you have your own dependency that returns a user instance
|
|
57
|
+
async def get_current_user() -> User:
|
|
58
|
+
# Your authentication logic here
|
|
59
|
+
return User(user_id="user-1", roles={"viewer"})
|
|
60
|
+
|
|
61
|
+
# 3. Dependency that library **REQUIRES**. **MUST** return a set of user roles
|
|
62
|
+
async def get_current_user_roles() -> set[str]:
|
|
63
|
+
user = await get_current_user()
|
|
64
|
+
return user.roles
|
|
65
|
+
|
|
66
|
+
# 4. Define your roles permissions
|
|
49
67
|
PERMISSIONS = {
|
|
50
68
|
"admin": {
|
|
51
69
|
Global("report:*"), # Admin can do anything with reports
|
|
@@ -55,13 +73,12 @@ PERMISSIONS = {
|
|
|
55
73
|
},
|
|
56
74
|
}
|
|
57
75
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
class ReportAccessContext(ContextualAuthz[User]):
|
|
76
|
+
# 5. Create context authorization checks
|
|
77
|
+
class ReportAccessContext(ContextualAuthz):
|
|
61
78
|
def __init__(
|
|
62
79
|
self,
|
|
63
80
|
report_id: int, # <-- Injected from path parameter
|
|
64
|
-
user: Annotated[User, Depends(
|
|
81
|
+
user: Annotated[User, Depends(get_current_user)], # Your own way how to get the user if you need it
|
|
65
82
|
):
|
|
66
83
|
self.user = user
|
|
67
84
|
self.report_id = report_id
|
|
@@ -71,22 +88,17 @@ class ReportAccessContext(ContextualAuthz[User]):
|
|
|
71
88
|
allowed_reports = {1, 2, 3} # e.g., query from database
|
|
72
89
|
return self.report_id in allowed_reports
|
|
73
90
|
|
|
74
|
-
#
|
|
91
|
+
# 6. Configure RBAC
|
|
75
92
|
app = FastAPI()
|
|
76
93
|
|
|
77
|
-
async def get_current_user() -> User:
|
|
78
|
-
# Your authentication logic here
|
|
79
|
-
return User(user_id="user-1", roles={"viewer"})
|
|
80
|
-
|
|
81
94
|
RBACAuthz(
|
|
82
95
|
app,
|
|
83
|
-
get_roles=lambda u: u.roles,
|
|
84
96
|
permissions=PERMISSIONS,
|
|
85
|
-
|
|
97
|
+
roles_dependency=get_current_user_roles, # Returns set[str]
|
|
86
98
|
ui_path="/_rbac", # Optional: mount visualization UI
|
|
87
99
|
)
|
|
88
100
|
|
|
89
|
-
#
|
|
101
|
+
# 7. Create protected routes
|
|
90
102
|
router = RBACRouter(permissions={"report:read"}, contexts=[ReportAccessContext])
|
|
91
103
|
|
|
92
104
|
@router.get("/reports/{report_id}")
|
|
@@ -136,14 +148,14 @@ PERMISSIONS = {
|
|
|
136
148
|
|
|
137
149
|
## Context Checks
|
|
138
150
|
|
|
139
|
-
Context checks are classes that implement fine-grained authorization logic. They're regular FastAPI dependencies, so you can inject any parameters (path params, query params, request body, database sessions, etc.).
|
|
151
|
+
Context checks are classes that implement fine-grained authorization logic. They're regular FastAPI dependencies, so you can inject any parameters (path params, query params, request body, database sessions, etc.). Each context class is responsible for its own authentication via FastAPI's dependency injection.
|
|
140
152
|
|
|
141
153
|
```python
|
|
142
|
-
class ReportAccessContext(ContextualAuthz
|
|
154
|
+
class ReportAccessContext(ContextualAuthz):
|
|
143
155
|
def __init__(
|
|
144
156
|
self,
|
|
145
157
|
report_id: int, # Injected from path parameter
|
|
146
|
-
user: Annotated[User, Depends(
|
|
158
|
+
user: Annotated[User, Depends(get_current_user)], # Your auth dependency
|
|
147
159
|
db: Annotated[AsyncSession, Depends(get_db)], # Database session
|
|
148
160
|
):
|
|
149
161
|
self.user = user
|
|
@@ -161,11 +173,11 @@ class ReportAccessContext(ContextualAuthz[User]):
|
|
|
161
173
|
When a request hits an RBAC-protected endpoint:
|
|
162
174
|
|
|
163
175
|
```
|
|
164
|
-
1.
|
|
165
|
-
└──
|
|
176
|
+
1. Role Resolution
|
|
177
|
+
└── roles_dependency runs → User's roles (set[str]) available
|
|
166
178
|
|
|
167
179
|
2. Permission Check
|
|
168
|
-
└── Does user have ANY grant (
|
|
180
|
+
└── Does user have ANY grant (scoped or wildcard) for required permission?
|
|
169
181
|
├── No → 403 Forbidden
|
|
170
182
|
└── Yes → Continue
|
|
171
183
|
|
|
@@ -175,7 +187,7 @@ When a request hits an RBAC-protected endpoint:
|
|
|
175
187
|
└── No → Continue to context checks
|
|
176
188
|
|
|
177
189
|
4. Context Checks (only for Contextual grants)
|
|
178
|
-
└── Run all context classes via FastAPI DI
|
|
190
|
+
└── Run all context classes via FastAPI DI (each gets its own user via Depends)
|
|
179
191
|
└── Do ALL contexts return True?
|
|
180
192
|
├── No → 403 Forbidden
|
|
181
193
|
└── Yes → Access granted
|
|
@@ -214,8 +226,8 @@ uvicorn examples.basic_app:app --reload
|
|
|
214
226
|
|
|
215
227
|
Then open your browser:
|
|
216
228
|
|
|
217
|
-
- **http://localhost:
|
|
218
|
-
- **http://localhost:
|
|
229
|
+
- **http://localhost:18000/docs** - OpenAPI docs to test the API
|
|
230
|
+
- **http://localhost:18000/_rbac** - Authorization visualization UI
|
|
219
231
|
|
|
220
232
|
### Test with different users
|
|
221
233
|
|
|
@@ -223,15 +235,15 @@ The example uses `X-Token` header for authentication:
|
|
|
223
235
|
|
|
224
236
|
```bash
|
|
225
237
|
# As admin (has Global("*") - full access)
|
|
226
|
-
curl -H "X-Token: admin-token" http://localhost:
|
|
227
|
-
curl -H "X-Token: admin-token" http://localhost:
|
|
238
|
+
curl -H "X-Token: admin-token" http://localhost:18000/reports
|
|
239
|
+
curl -H "X-Token: admin-token" http://localhost:18000/reports/1
|
|
228
240
|
|
|
229
241
|
# As user (has Contextual permissions - can only access own reports)
|
|
230
|
-
curl -H "X-Token: user-token" http://localhost:
|
|
231
|
-
curl -H "X-Token: user-token" http://localhost:
|
|
242
|
+
curl -H "X-Token: user-token" http://localhost:18000/reports/1 # OK (owns report 1)
|
|
243
|
+
curl -H "X-Token: user-token" http://localhost:18000/reports/3 # 403 (doesn't own report 3)
|
|
232
244
|
|
|
233
245
|
# As viewer (has Contextual read - can only read own reports)
|
|
234
|
-
curl -H "X-Token: viewer-token" http://localhost:
|
|
246
|
+
curl -H "X-Token: viewer-token" http://localhost:18000/reports/1 # 403 (doesn't own any)
|
|
235
247
|
```
|
|
236
248
|
|
|
237
249
|
## Visualization UI
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
fastapi_rbac/__init__.py,sha256=CqpNJ54-bMbUF-LrQUAGX9pfTBYXR6x13kG1EDm5odY,662
|
|
2
|
+
fastapi_rbac/context.py,sha256=7lFQotJU4Wn-_BcHi3A8bAB7ftzVKJRJoXzs3q-I4tY,1039
|
|
3
|
+
fastapi_rbac/core.py,sha256=pY8UjpSxf1xV5ODp2b15_fU16sUfxv32_5vipQIX4XU,3246
|
|
4
|
+
fastapi_rbac/dependencies.py,sha256=zukBpbvB9586Lz_df7t1F-CA-cnhDy6R4aJo1FlSpr4,4916
|
|
5
|
+
fastapi_rbac/errors.py,sha256=Df2zWPe-Kd54bKTB4w40wUTuNnppfCkZ33cJqnylIgo,247
|
|
6
|
+
fastapi_rbac/permissions.py,sha256=lU1K2rHYqkxjYrnkG1Rk2FKEmgw0py62bNkOgSmznA8,2728
|
|
7
|
+
fastapi_rbac/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
|
|
8
|
+
fastapi_rbac/router.py,sha256=4BeKXAEqcdFh4kX9AJaUuCmpVXJXU07Afubhb20Z7B8,9943
|
|
9
|
+
fastapi_rbac/ui/__init__.py,sha256=u2enI_c9k1d0T0z1kBuGI4lpR0wifqFzkfaEUEnuLXg,220
|
|
10
|
+
fastapi_rbac/ui/routes.py,sha256=UbJcMo6mMNNtzlSAdrp1S79wppkO4hFC018POzlsq-k,2025
|
|
11
|
+
fastapi_rbac/ui/schema.py,sha256=Q7diGVuNuGHEGn_uq7OH5Cx2dE9QHfAEdLt70_BzpYE,7919
|
|
12
|
+
fastapi_rbac/ui/static/index.html,sha256=HUxI7eO84duCxxOi9GqP4yybA6QrxQAbNcc6lYkjM30,73455
|
|
13
|
+
fastapi_rbac_authz-0.5.0.dist-info/METADATA,sha256=qavdtMVBkWcB5Zc-2h9WBDSi_rLBrKCmZ8kS-bp-zVw,8770
|
|
14
|
+
fastapi_rbac_authz-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
fastapi_rbac_authz-0.5.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
fastapi_rbac/__init__.py,sha256=pmId7xpntLKSwaQaFmBwJa443-FyfGOFwl_pf8t04sE,817
|
|
2
|
-
fastapi_rbac/context.py,sha256=txZqnw8xGPHsE3DQi75Q7ny_VifT0O2etcz5E8sm2wE,1070
|
|
3
|
-
fastapi_rbac/core.py,sha256=smTSbxCQYyMBYvkzOavPurIMWKXrp1ZHWXOs7WVwVvY,4154
|
|
4
|
-
fastapi_rbac/dependencies.py,sha256=2475jW8QPRUWubA__M48D-EY8GbL2SJH8mLaaEMoLfg,9325
|
|
5
|
-
fastapi_rbac/exceptions.py,sha256=tSFbes6yzAygWXXl9NLdZ5K2_iISiJ6ZGJCD6077i94,244
|
|
6
|
-
fastapi_rbac/permissions.py,sha256=VReUjewdFehkDpA_rowSAV2qFoAfoDhrLvoU2gGSxww,2705
|
|
7
|
-
fastapi_rbac/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
|
|
8
|
-
fastapi_rbac/router.py,sha256=kLUmWmBZKqNoGvoD44g-Un9tCIO_dP3psQYrNBm5QFI,11405
|
|
9
|
-
fastapi_rbac/ui/__init__.py,sha256=u2enI_c9k1d0T0z1kBuGI4lpR0wifqFzkfaEUEnuLXg,220
|
|
10
|
-
fastapi_rbac/ui/routes.py,sha256=d57_bPsrpFePzumqcHIU3Okf8TfXKkqf8KLhkrvCg_I,2081
|
|
11
|
-
fastapi_rbac/ui/schema.py,sha256=p3mWgkpA20jYI_g7KiIdULXCFzSeNXBMIL9c7Pe7Zgg,7917
|
|
12
|
-
fastapi_rbac/ui/static/index.html,sha256=HUxI7eO84duCxxOi9GqP4yybA6QrxQAbNcc6lYkjM30,73455
|
|
13
|
-
fastapi_rbac_authz-0.3.0.dist-info/METADATA,sha256=zi4SKBP5vjZfNamnLuFmxNVXArBJ25Xy763RuRQfeck,7985
|
|
14
|
-
fastapi_rbac_authz-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
-
fastapi_rbac_authz-0.3.0.dist-info/RECORD,,
|
|
File without changes
|