fastapi-rbac-authz 0.3.0__py3-none-any.whl → 0.4.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.4.0.dist-info}/METADATA +28 -21
- fastapi_rbac_authz-0.4.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.4.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.4.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>
|
|
@@ -36,7 +36,7 @@ pip install fastapi-rbac-authz
|
|
|
36
36
|
from typing import Annotated
|
|
37
37
|
from fastapi import Depends, FastAPI
|
|
38
38
|
from fastapi_rbac import (
|
|
39
|
-
RBACAuthz, RBACRouter, Global, Contextual, ContextualAuthz
|
|
39
|
+
RBACAuthz, RBACRouter, Global, Contextual, ContextualAuthz
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
# 1. Define your user model
|
|
@@ -45,7 +45,17 @@ class User:
|
|
|
45
45
|
self.user_id = user_id
|
|
46
46
|
self.roles = roles
|
|
47
47
|
|
|
48
|
-
# 2. Define
|
|
48
|
+
# 2. Define your authentication dependencies
|
|
49
|
+
# The library only needs roles - you can authenticate however you like
|
|
50
|
+
async def get_current_user() -> User:
|
|
51
|
+
# Your authentication logic here
|
|
52
|
+
return User(user_id="user-1", roles={"viewer"})
|
|
53
|
+
|
|
54
|
+
async def get_current_user_roles() -> set[str]:
|
|
55
|
+
user = await get_current_user()
|
|
56
|
+
return user.roles
|
|
57
|
+
|
|
58
|
+
# 3. Define role permissions
|
|
49
59
|
PERMISSIONS = {
|
|
50
60
|
"admin": {
|
|
51
61
|
Global("report:*"), # Admin can do anything with reports
|
|
@@ -55,13 +65,13 @@ PERMISSIONS = {
|
|
|
55
65
|
},
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
class ReportAccessContext(ContextualAuthz
|
|
68
|
+
# 4. Create a context check (for contextual permissions)
|
|
69
|
+
# Context classes are responsible for their own authentication via FastAPI DI
|
|
70
|
+
class ReportAccessContext(ContextualAuthz):
|
|
61
71
|
def __init__(
|
|
62
72
|
self,
|
|
63
73
|
report_id: int, # <-- Injected from path parameter
|
|
64
|
-
user: Annotated[User, Depends(
|
|
74
|
+
user: Annotated[User, Depends(get_current_user)], # Your auth dependency
|
|
65
75
|
):
|
|
66
76
|
self.user = user
|
|
67
77
|
self.report_id = report_id
|
|
@@ -71,22 +81,17 @@ class ReportAccessContext(ContextualAuthz[User]):
|
|
|
71
81
|
allowed_reports = {1, 2, 3} # e.g., query from database
|
|
72
82
|
return self.report_id in allowed_reports
|
|
73
83
|
|
|
74
|
-
#
|
|
84
|
+
# 5. Create your app and configure RBAC
|
|
75
85
|
app = FastAPI()
|
|
76
86
|
|
|
77
|
-
async def get_current_user() -> User:
|
|
78
|
-
# Your authentication logic here
|
|
79
|
-
return User(user_id="user-1", roles={"viewer"})
|
|
80
|
-
|
|
81
87
|
RBACAuthz(
|
|
82
88
|
app,
|
|
83
|
-
get_roles=lambda u: u.roles,
|
|
84
89
|
permissions=PERMISSIONS,
|
|
85
|
-
|
|
90
|
+
roles_dependency=get_current_user_roles, # Returns set[str]
|
|
86
91
|
ui_path="/_rbac", # Optional: mount visualization UI
|
|
87
92
|
)
|
|
88
93
|
|
|
89
|
-
#
|
|
94
|
+
# 6. Create protected routes
|
|
90
95
|
router = RBACRouter(permissions={"report:read"}, contexts=[ReportAccessContext])
|
|
91
96
|
|
|
92
97
|
@router.get("/reports/{report_id}")
|
|
@@ -100,6 +105,8 @@ async def create_report():
|
|
|
100
105
|
app.include_router(router, prefix="/api")
|
|
101
106
|
```
|
|
102
107
|
|
|
108
|
+
> **Note:** The library doesn't care how you authenticate - whether via dependency, middleware, JWT decode, or any other method. You just need to provide a dependency that returns the user's roles as `set[str]`. Context classes are responsible for their own authentication and can use any FastAPI dependency pattern.
|
|
109
|
+
|
|
103
110
|
## Permission Scopes
|
|
104
111
|
|
|
105
112
|
Permissions can be granted with two scopes:
|
|
@@ -136,14 +143,14 @@ PERMISSIONS = {
|
|
|
136
143
|
|
|
137
144
|
## Context Checks
|
|
138
145
|
|
|
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.).
|
|
146
|
+
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
147
|
|
|
141
148
|
```python
|
|
142
|
-
class ReportAccessContext(ContextualAuthz
|
|
149
|
+
class ReportAccessContext(ContextualAuthz):
|
|
143
150
|
def __init__(
|
|
144
151
|
self,
|
|
145
152
|
report_id: int, # Injected from path parameter
|
|
146
|
-
user: Annotated[User, Depends(
|
|
153
|
+
user: Annotated[User, Depends(get_current_user)], # Your auth dependency
|
|
147
154
|
db: Annotated[AsyncSession, Depends(get_db)], # Database session
|
|
148
155
|
):
|
|
149
156
|
self.user = user
|
|
@@ -161,8 +168,8 @@ class ReportAccessContext(ContextualAuthz[User]):
|
|
|
161
168
|
When a request hits an RBAC-protected endpoint:
|
|
162
169
|
|
|
163
170
|
```
|
|
164
|
-
1.
|
|
165
|
-
└──
|
|
171
|
+
1. Role Resolution
|
|
172
|
+
└── roles_dependency runs → User's roles (set[str]) available
|
|
166
173
|
|
|
167
174
|
2. Permission Check
|
|
168
175
|
└── Does user have ANY grant (global or contextual) for required permission?
|
|
@@ -175,7 +182,7 @@ When a request hits an RBAC-protected endpoint:
|
|
|
175
182
|
└── No → Continue to context checks
|
|
176
183
|
|
|
177
184
|
4. Context Checks (only for Contextual grants)
|
|
178
|
-
└── Run all context classes via FastAPI DI
|
|
185
|
+
└── Run all context classes via FastAPI DI (each gets its own user via Depends)
|
|
179
186
|
└── Do ALL contexts return True?
|
|
180
187
|
├── No → 403 Forbidden
|
|
181
188
|
└── Yes → Access granted
|
|
@@ -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.4.0.dist-info/METADATA,sha256=2H4g3gBXal5phFSnAdgpIfhtoyNgeoILke4yAHqMVFs,8730
|
|
14
|
+
fastapi_rbac_authz-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
fastapi_rbac_authz-0.4.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
|