fastapi-rbac-authz 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_rbac_authz-0.2.0/.github/workflows/checks.yaml +30 -0
- fastapi_rbac_authz-0.2.0/.github/workflows/pypi-minor-deployment.yaml +59 -0
- fastapi_rbac_authz-0.2.0/.gitignore +245 -0
- fastapi_rbac_authz-0.2.0/.pre-commit-config.yaml +17 -0
- fastapi_rbac_authz-0.2.0/CLAUDE.md +66 -0
- fastapi_rbac_authz-0.2.0/PKG-INFO +269 -0
- fastapi_rbac_authz-0.2.0/README.md +247 -0
- fastapi_rbac_authz-0.2.0/examples/basic_app.py +186 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/__init__.py +35 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/context.py +39 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/core.py +111 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/dependencies.py +254 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/exceptions.py +8 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/permissions.py +87 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/py.typed +1 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/router.py +319 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/__init__.py +9 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/routes.py +67 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/schema.py +253 -0
- fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/static/index.html +1879 -0
- fastapi_rbac_authz-0.2.0/poetry.lock +913 -0
- fastapi_rbac_authz-0.2.0/pyproject.toml +66 -0
- fastapi_rbac_authz-0.2.0/tests/__init__.py +0 -0
- fastapi_rbac_authz-0.2.0/tests/conftest.py +13 -0
- fastapi_rbac_authz-0.2.0/tests/test_context.py +49 -0
- fastapi_rbac_authz-0.2.0/tests/test_context_di.py +292 -0
- fastapi_rbac_authz-0.2.0/tests/test_core.py +74 -0
- fastapi_rbac_authz-0.2.0/tests/test_dependencies.py +431 -0
- fastapi_rbac_authz-0.2.0/tests/test_exceptions.py +15 -0
- fastapi_rbac_authz-0.2.0/tests/test_integration.py +583 -0
- fastapi_rbac_authz-0.2.0/tests/test_permissions.py +111 -0
- fastapi_rbac_authz-0.2.0/tests/test_router.py +458 -0
- fastapi_rbac_authz-0.2.0/tests/test_ui.py +319 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Checks
|
|
2
|
+
|
|
3
|
+
on: workflow_call
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
checks:
|
|
7
|
+
name: Style & Types
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v3
|
|
11
|
+
|
|
12
|
+
- name: Set up Python
|
|
13
|
+
uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: '3.13'
|
|
16
|
+
|
|
17
|
+
- name: Install poetry
|
|
18
|
+
run: pip install "poetry<3"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: poetry install --with dev
|
|
22
|
+
|
|
23
|
+
- name: Install pre-commit
|
|
24
|
+
run: pip install pre-commit
|
|
25
|
+
|
|
26
|
+
- name: Run pre-commit
|
|
27
|
+
run: pre-commit run --all-files
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: poetry run pytest -v tests/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: PyPi Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
|
|
13
|
+
checks:
|
|
14
|
+
uses: ./.github/workflows/checks.yaml
|
|
15
|
+
|
|
16
|
+
deploy:
|
|
17
|
+
|
|
18
|
+
name: PyPi Deploy
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
needs: ["checks"]
|
|
21
|
+
|
|
22
|
+
env:
|
|
23
|
+
PYPI_USER: ${{ secrets.PYPI_USER }}
|
|
24
|
+
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v3
|
|
28
|
+
|
|
29
|
+
- name: Set up Python 3.13
|
|
30
|
+
uses: actions/setup-python@v4
|
|
31
|
+
with:
|
|
32
|
+
python-version: 3.13
|
|
33
|
+
|
|
34
|
+
- name: Install poetry
|
|
35
|
+
run: pip install "poetry<3"
|
|
36
|
+
|
|
37
|
+
- name: Skip virtualenv creation
|
|
38
|
+
run: poetry config virtualenvs.create false
|
|
39
|
+
|
|
40
|
+
- name: Configure PyPi credentials
|
|
41
|
+
run: poetry config pypi-token.pypi ${{ env.PYPI_PASSWORD }}
|
|
42
|
+
|
|
43
|
+
- name: Bump Version
|
|
44
|
+
run: poetry version minor
|
|
45
|
+
|
|
46
|
+
- name: Git set email
|
|
47
|
+
run: git config --global user.email "github.bot@pypi.org"
|
|
48
|
+
|
|
49
|
+
- name: Git set name
|
|
50
|
+
run: git config --global user.name "GitHub Bot"
|
|
51
|
+
|
|
52
|
+
- name: Commit
|
|
53
|
+
run: git commit -m "[skip ci] auto-bump-version" -a
|
|
54
|
+
|
|
55
|
+
- name: Push
|
|
56
|
+
run: git push
|
|
57
|
+
|
|
58
|
+
- name: Deploy to PyPi
|
|
59
|
+
run: poetry --build publish
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
### JetBrains template
|
|
2
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
3
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
4
|
+
|
|
5
|
+
# User-specific stuff
|
|
6
|
+
.idea/**/workspace.xml
|
|
7
|
+
.idea/**/tasks.xml
|
|
8
|
+
.idea/**/usage.statistics.xml
|
|
9
|
+
.idea/**/dictionaries
|
|
10
|
+
.idea/**/shelf
|
|
11
|
+
|
|
12
|
+
# AWS User-specific
|
|
13
|
+
.idea/**/aws.xml
|
|
14
|
+
|
|
15
|
+
# Generated files
|
|
16
|
+
.idea/**/contentModel.xml
|
|
17
|
+
|
|
18
|
+
# Sensitive or high-churn files
|
|
19
|
+
.idea/**/dataSources/
|
|
20
|
+
.idea/**/dataSources.ids
|
|
21
|
+
.idea/**/dataSources.local.xml
|
|
22
|
+
.idea/**/sqlDataSources.xml
|
|
23
|
+
.idea/**/dynamic.xml
|
|
24
|
+
.idea/**/uiDesigner.xml
|
|
25
|
+
.idea/**/dbnavigator.xml
|
|
26
|
+
|
|
27
|
+
# Gradle
|
|
28
|
+
.idea/**/gradle.xml
|
|
29
|
+
.idea/**/libraries
|
|
30
|
+
|
|
31
|
+
# Gradle and Maven with auto-import
|
|
32
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
33
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
34
|
+
# auto-import.
|
|
35
|
+
# .idea/artifacts
|
|
36
|
+
# .idea/compiler.xml
|
|
37
|
+
# .idea/jarRepositories.xml
|
|
38
|
+
# .idea/modules.xml
|
|
39
|
+
# .idea/*.iml
|
|
40
|
+
# .idea/modules
|
|
41
|
+
# *.iml
|
|
42
|
+
# *.ipr
|
|
43
|
+
|
|
44
|
+
# CMake
|
|
45
|
+
cmake-build-*/
|
|
46
|
+
|
|
47
|
+
# Mongo Explorer plugin
|
|
48
|
+
.idea/**/mongoSettings.xml
|
|
49
|
+
|
|
50
|
+
# File-based project format
|
|
51
|
+
*.iws
|
|
52
|
+
|
|
53
|
+
# IntelliJ
|
|
54
|
+
out/
|
|
55
|
+
|
|
56
|
+
# mpeltonen/sbt-idea plugin
|
|
57
|
+
.idea_modules/
|
|
58
|
+
|
|
59
|
+
# JIRA plugin
|
|
60
|
+
atlassian-ide-plugin.xml
|
|
61
|
+
|
|
62
|
+
# Cursive Clojure plugin
|
|
63
|
+
.idea/replstate.xml
|
|
64
|
+
|
|
65
|
+
# SonarLint plugin
|
|
66
|
+
.idea/sonarlint/
|
|
67
|
+
|
|
68
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
69
|
+
com_crashlytics_export_strings.xml
|
|
70
|
+
crashlytics.properties
|
|
71
|
+
crashlytics-build.properties
|
|
72
|
+
fabric.properties
|
|
73
|
+
|
|
74
|
+
# Editor-based Rest Client
|
|
75
|
+
.idea/httpRequests
|
|
76
|
+
|
|
77
|
+
# Android studio 3.1+ serialized cache file
|
|
78
|
+
.idea/caches/build_file_checksums.ser
|
|
79
|
+
|
|
80
|
+
### Python template
|
|
81
|
+
# Byte-compiled / optimized / DLL files
|
|
82
|
+
__pycache__/
|
|
83
|
+
*.py[cod]
|
|
84
|
+
*$py.class
|
|
85
|
+
|
|
86
|
+
# C extensions
|
|
87
|
+
*.so
|
|
88
|
+
|
|
89
|
+
# Distribution / packaging
|
|
90
|
+
.Python
|
|
91
|
+
build/
|
|
92
|
+
develop-eggs/
|
|
93
|
+
dist/
|
|
94
|
+
downloads/
|
|
95
|
+
eggs/
|
|
96
|
+
.eggs/
|
|
97
|
+
lib/
|
|
98
|
+
lib64/
|
|
99
|
+
parts/
|
|
100
|
+
sdist/
|
|
101
|
+
var/
|
|
102
|
+
wheels/
|
|
103
|
+
share/python-wheels/
|
|
104
|
+
*.egg-info/
|
|
105
|
+
.installed.cfg
|
|
106
|
+
*.egg
|
|
107
|
+
MANIFEST
|
|
108
|
+
|
|
109
|
+
# PyInstaller
|
|
110
|
+
# Usually these files are written by a python script from a template
|
|
111
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
112
|
+
*.manifest
|
|
113
|
+
*.spec
|
|
114
|
+
|
|
115
|
+
# Installer logs
|
|
116
|
+
pip-log.txt
|
|
117
|
+
pip-delete-this-directory.txt
|
|
118
|
+
|
|
119
|
+
# Unit test / coverage reports
|
|
120
|
+
htmlcov/
|
|
121
|
+
.tox/
|
|
122
|
+
.nox/
|
|
123
|
+
.coverage
|
|
124
|
+
.coverage.*
|
|
125
|
+
.cache
|
|
126
|
+
nosetests.xml
|
|
127
|
+
coverage.xml
|
|
128
|
+
*.cover
|
|
129
|
+
*.py,cover
|
|
130
|
+
.hypothesis/
|
|
131
|
+
.pytest_cache/
|
|
132
|
+
cover/
|
|
133
|
+
|
|
134
|
+
# Translations
|
|
135
|
+
*.mo
|
|
136
|
+
*.pot
|
|
137
|
+
|
|
138
|
+
# Django stuff:
|
|
139
|
+
*.log
|
|
140
|
+
local_settings.py
|
|
141
|
+
db.sqlite3
|
|
142
|
+
db.sqlite3-journal
|
|
143
|
+
|
|
144
|
+
# Flask stuff:
|
|
145
|
+
instance/
|
|
146
|
+
.webassets-cache
|
|
147
|
+
|
|
148
|
+
# Scrapy stuff:
|
|
149
|
+
.scrapy
|
|
150
|
+
|
|
151
|
+
# Sphinx documentation
|
|
152
|
+
docs/_build/
|
|
153
|
+
|
|
154
|
+
# PyBuilder
|
|
155
|
+
.pybuilder/
|
|
156
|
+
target/
|
|
157
|
+
|
|
158
|
+
# Jupyter Notebook
|
|
159
|
+
.ipynb_checkpoints
|
|
160
|
+
|
|
161
|
+
# IPython
|
|
162
|
+
profile_default/
|
|
163
|
+
ipython_config.py
|
|
164
|
+
|
|
165
|
+
# pyenv
|
|
166
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
167
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
168
|
+
# .python-version
|
|
169
|
+
|
|
170
|
+
# pipenv
|
|
171
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
172
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
173
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
174
|
+
# install all needed dependencies.
|
|
175
|
+
#Pipfile.lock
|
|
176
|
+
|
|
177
|
+
# poetry
|
|
178
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
179
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
180
|
+
# commonly ignored for libraries.
|
|
181
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
182
|
+
#poetry.lock
|
|
183
|
+
|
|
184
|
+
# pdm
|
|
185
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
186
|
+
#pdm.lock
|
|
187
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
188
|
+
# in version control.
|
|
189
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
190
|
+
.pdm.toml
|
|
191
|
+
.pdm-python
|
|
192
|
+
.pdm-build/
|
|
193
|
+
|
|
194
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
195
|
+
__pypackages__/
|
|
196
|
+
|
|
197
|
+
# Celery stuff
|
|
198
|
+
celerybeat-schedule
|
|
199
|
+
celerybeat.pid
|
|
200
|
+
|
|
201
|
+
# SageMath parsed files
|
|
202
|
+
*.sage.py
|
|
203
|
+
|
|
204
|
+
# Environments
|
|
205
|
+
.env
|
|
206
|
+
.venv
|
|
207
|
+
env/
|
|
208
|
+
venv/
|
|
209
|
+
ENV/
|
|
210
|
+
env.bak/
|
|
211
|
+
venv.bak/
|
|
212
|
+
|
|
213
|
+
# Spyder project settings
|
|
214
|
+
.spyderproject
|
|
215
|
+
.spyproject
|
|
216
|
+
|
|
217
|
+
# Rope project settings
|
|
218
|
+
.ropeproject
|
|
219
|
+
|
|
220
|
+
# mkdocs documentation
|
|
221
|
+
/site
|
|
222
|
+
|
|
223
|
+
# mypy
|
|
224
|
+
.mypy_cache/
|
|
225
|
+
.dmypy.json
|
|
226
|
+
dmypy.json
|
|
227
|
+
|
|
228
|
+
# Pyre type checker
|
|
229
|
+
.pyre/
|
|
230
|
+
|
|
231
|
+
# pytype static type analyzer
|
|
232
|
+
.pytype/
|
|
233
|
+
|
|
234
|
+
# Cython debug symbols
|
|
235
|
+
cython_debug/
|
|
236
|
+
|
|
237
|
+
# PyCharm
|
|
238
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
239
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
240
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
241
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
242
|
+
.idea/
|
|
243
|
+
|
|
244
|
+
docs
|
|
245
|
+
docs/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.14.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [fastapi_rbac, tests, --fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: local
|
|
10
|
+
hooks:
|
|
11
|
+
- id: mypy
|
|
12
|
+
name: mypy
|
|
13
|
+
entry: bash -lc 'poetry run mypy fastapi_rbac tests'
|
|
14
|
+
language: system
|
|
15
|
+
types: [python]
|
|
16
|
+
pass_filenames: false
|
|
17
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
FastAPI RBAC Authorization library providing role-based access control with contextual authorization for FastAPI applications. The package name is `fastapi-rbac` (import as `fastapi_rbac`).
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
poetry install --with dev
|
|
14
|
+
|
|
15
|
+
# Run all tests
|
|
16
|
+
pytest -v tests/
|
|
17
|
+
|
|
18
|
+
# Run specific test file
|
|
19
|
+
pytest -v tests/test_integration.py
|
|
20
|
+
|
|
21
|
+
# Run specific test
|
|
22
|
+
pytest -k "test_name"
|
|
23
|
+
|
|
24
|
+
# Type checking (strict mode)
|
|
25
|
+
mypy fastapi_rbac
|
|
26
|
+
|
|
27
|
+
# Build package
|
|
28
|
+
poetry build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**IMPORTANT:** Always run pre-commit after making any code changes:
|
|
32
|
+
```bash
|
|
33
|
+
pre-commit run --all-files
|
|
34
|
+
```
|
|
35
|
+
This runs ruff (linting + formatting) and mypy (type checking on fastapi_rbac).
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
### Core Components
|
|
40
|
+
|
|
41
|
+
**RBACAuthz[UserT]** (`core.py`) - Main configuration class that attaches to FastAPI app. Configures role-to-permission mappings, user dependency injection, and optional visualization UI.
|
|
42
|
+
|
|
43
|
+
**RBACRouter** (`router.py`) - Extended APIRouter with permission decorators (`@router.get(permissions=..., contexts=...)`). Supports default permissions at router level with per-endpoint overrides. Stores endpoint metadata for UI introspection.
|
|
44
|
+
|
|
45
|
+
**Permission System** (`permissions.py`) - Two scopes: `Global` (bypasses context checks) and `Contextual` (requires context validation). Supports wildcard matching (`resource:*` matches `resource:read`). Wildcards only allowed in role grants, not endpoint requirements.
|
|
46
|
+
|
|
47
|
+
**ContextualAuthz[UserT]** (`context.py`) - Abstract base class for context-specific authorization. Subclasses are FastAPI dependencies that implement `async has_permissions() -> bool`. Supports full FastAPI DI in `__init__`. Context classes should use `Annotated[User, Depends(RBACUser)]` for the user parameter.
|
|
48
|
+
|
|
49
|
+
**Dependencies** (`dependencies.py`) - Creates FastAPI dependencies for auth and authz. Uses `dependency_overrides` pattern for user dependency injection. Manipulates function signatures dynamically via `inspect.Signature`.
|
|
50
|
+
|
|
51
|
+
**UI System** (`ui/`) - Cytoscape.js visualization mounted at configurable path. Introspects RBAC configuration to display role → permission → endpoint ← context relationships.
|
|
52
|
+
|
|
53
|
+
### Key Patterns
|
|
54
|
+
|
|
55
|
+
- **Request State**: User stored in `request.state.user`, RBAC config in `app.state.rbac`, routers in `app.state._rbac_routers_`
|
|
56
|
+
- **Metadata Tracking**: Endpoints store `_rbac_metadata_` attribute; routers track `endpoint_metadata[(path, method)]`
|
|
57
|
+
- **Permission Resolution**: Extract roles → resolve grants → check global permissions first → run contextual checks only if needed
|
|
58
|
+
- **Type Safety**: Strict mypy with full generics (`RBACAuthz[UserT]`, `ContextualAuthz[UserT]`)
|
|
59
|
+
|
|
60
|
+
### Public API (from `__init__.py`)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
RBACAuthz, RBACRouter, RBACUser, ContextualAuthz
|
|
64
|
+
Global, Contextual, PermissionGrant, PermissionScope
|
|
65
|
+
Forbidden, create_auth_dependency, create_authz_dependency, evaluate_permissions
|
|
66
|
+
```
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-rbac-authz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Role-based access control with contextual authorization for FastAPI
|
|
5
|
+
Project-URL: Homepage, https://github.com/parikls/fastapi-rbac
|
|
6
|
+
Author-email: Dmytro Smyk <porovozls@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: FastAPI
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: <4,>=3.12
|
|
19
|
+
Requires-Dist: fastapi>=0.100.0
|
|
20
|
+
Requires-Dist: starlette>=0.27.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# fastapi-rbac-authz
|
|
24
|
+
|
|
25
|
+
Role-based access control with contextual authorization for FastAPI.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install fastapi-rbac-authz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from typing import Annotated
|
|
37
|
+
from fastapi import Depends, FastAPI
|
|
38
|
+
from fastapi_rbac import (
|
|
39
|
+
RBACAuthz, RBACRouter, Global, Contextual, ContextualAuthz, RBACUser
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# 1. Define your user model
|
|
43
|
+
class User:
|
|
44
|
+
def __init__(self, user_id: str, roles: set[str]):
|
|
45
|
+
self.user_id = user_id
|
|
46
|
+
self.roles = roles
|
|
47
|
+
|
|
48
|
+
# 2. Define role permissions
|
|
49
|
+
PERMISSIONS = {
|
|
50
|
+
"admin": {
|
|
51
|
+
Global("report:*"), # Admin can do anything with reports
|
|
52
|
+
},
|
|
53
|
+
"viewer": {
|
|
54
|
+
Contextual("report:read"), # Viewer needs context check
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# 3. Create a context check (for contextual permissions)
|
|
59
|
+
# All __init__ params are injected by FastAPI's DI system
|
|
60
|
+
class ReportAccessContext(ContextualAuthz[User]):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
report_id: int, # <-- Injected from path parameter
|
|
64
|
+
user: Annotated[User, Depends(RBACUser)],
|
|
65
|
+
):
|
|
66
|
+
self.user = user
|
|
67
|
+
self.report_id = report_id
|
|
68
|
+
|
|
69
|
+
async def has_permissions(self) -> bool:
|
|
70
|
+
# Your logic: check if user can access this specific report
|
|
71
|
+
allowed_reports = {1, 2, 3} # e.g., query from database
|
|
72
|
+
return self.report_id in allowed_reports
|
|
73
|
+
|
|
74
|
+
# 4. Create your app and configure RBAC
|
|
75
|
+
app = FastAPI()
|
|
76
|
+
|
|
77
|
+
async def get_current_user() -> User:
|
|
78
|
+
# Your authentication logic here
|
|
79
|
+
return User(user_id="user-1", roles={"viewer"})
|
|
80
|
+
|
|
81
|
+
RBACAuthz(
|
|
82
|
+
app,
|
|
83
|
+
get_roles=lambda u: u.roles,
|
|
84
|
+
permissions=PERMISSIONS,
|
|
85
|
+
user_dependency=get_current_user,
|
|
86
|
+
ui_path="/_rbac", # Optional: mount visualization UI
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 5. Create protected routes
|
|
90
|
+
router = RBACRouter(permissions={"report:read"}, contexts=[ReportAccessContext])
|
|
91
|
+
|
|
92
|
+
@router.get("/reports/{report_id}")
|
|
93
|
+
async def get_report(report_id: int):
|
|
94
|
+
return {"report_id": report_id}
|
|
95
|
+
|
|
96
|
+
@router.post("/reports", permissions={"report:create"}) # Override permissions
|
|
97
|
+
async def create_report():
|
|
98
|
+
return {"id": "new-report"}
|
|
99
|
+
|
|
100
|
+
app.include_router(router, prefix="/api")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Permission Scopes
|
|
104
|
+
|
|
105
|
+
Permissions can be granted with two scopes:
|
|
106
|
+
|
|
107
|
+
### Global Scope
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
Global("report:read")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Global permissions **bypass context checks entirely**. If a user has a global permission, they can access the resource without any additional validation. Use this for admin roles or service accounts that need unrestricted access.
|
|
114
|
+
|
|
115
|
+
### Contextual Scope
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
Contextual("report:read")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Contextual permissions **require context checks to pass**. The user must have the permission AND the context check must return `True`. Use this for regular users who should only access resources they own or are members of.
|
|
122
|
+
|
|
123
|
+
### Example
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
PERMISSIONS = {
|
|
127
|
+
"admin": {
|
|
128
|
+
Global("report:*"), # Can access ALL reports, no questions asked
|
|
129
|
+
},
|
|
130
|
+
"user": {
|
|
131
|
+
Contextual("report:read"), # Can only read reports they have access to
|
|
132
|
+
Contextual("report:create"),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Context Checks
|
|
138
|
+
|
|
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.).
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
class ReportAccessContext(ContextualAuthz[User]):
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
report_id: int, # Injected from path parameter
|
|
146
|
+
user: Annotated[User, Depends(RBACUser)],
|
|
147
|
+
db: Annotated[AsyncSession, Depends(get_db)], # Database session
|
|
148
|
+
):
|
|
149
|
+
self.user = user
|
|
150
|
+
self.report_id = report_id
|
|
151
|
+
self.db = db
|
|
152
|
+
|
|
153
|
+
async def has_permissions(self) -> bool:
|
|
154
|
+
# Query database to check if user can access this report
|
|
155
|
+
report = await self.db.get(Report, self.report_id)
|
|
156
|
+
return report is not None and report.owner_id == self.user.user_id
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Authorization Flow
|
|
160
|
+
|
|
161
|
+
When a request hits an RBAC-protected endpoint:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
1. Authentication
|
|
165
|
+
└── user_dependency runs → User object available
|
|
166
|
+
|
|
167
|
+
2. Permission Check
|
|
168
|
+
└── Does user have ANY grant (global or contextual) for required permission?
|
|
169
|
+
├── No → 403 Forbidden
|
|
170
|
+
└── Yes → Continue
|
|
171
|
+
|
|
172
|
+
3. Scope Evaluation
|
|
173
|
+
└── Is the grant Global?
|
|
174
|
+
├── Yes → Access granted (skip context checks)
|
|
175
|
+
└── No → Continue to context checks
|
|
176
|
+
|
|
177
|
+
4. Context Checks (only for Contextual grants)
|
|
178
|
+
└── Run all context classes via FastAPI DI
|
|
179
|
+
└── Do ALL contexts return True?
|
|
180
|
+
├── No → 403 Forbidden
|
|
181
|
+
└── Yes → Access granted
|
|
182
|
+
|
|
183
|
+
5. Endpoint Handler Executes
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Wildcard Permissions
|
|
187
|
+
|
|
188
|
+
Use wildcards to grant multiple permissions at once:
|
|
189
|
+
|
|
190
|
+
| Grant | Implies |
|
|
191
|
+
|-------|---------|
|
|
192
|
+
| `*` | Everything |
|
|
193
|
+
| `report:*` | `report:read`, `report:create`, `report:delete`, etc. |
|
|
194
|
+
| `report:metrics:*` | `report:metrics:view`, `report:metrics:export`, etc. |
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
PERMISSIONS = {
|
|
198
|
+
"admin": {Global("*")}, # Full access to everything
|
|
199
|
+
"reporter": {Global("report:*")}, # Full access to reports only
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Running the Example
|
|
204
|
+
|
|
205
|
+
A complete runnable example is included in the `examples/` directory:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Install dependencies
|
|
209
|
+
pip install fastapi-rbac-authz uvicorn
|
|
210
|
+
|
|
211
|
+
# Run the example
|
|
212
|
+
uvicorn examples.basic_app:app --reload
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Then open your browser:
|
|
216
|
+
|
|
217
|
+
- **http://localhost:8000/docs** - OpenAPI docs to test the API
|
|
218
|
+
- **http://localhost:8000/_rbac** - Authorization visualization UI
|
|
219
|
+
|
|
220
|
+
### Test with different users
|
|
221
|
+
|
|
222
|
+
The example uses `X-Token` header for authentication:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# As admin (has Global("*") - full access)
|
|
226
|
+
curl -H "X-Token: admin-token" http://localhost:8000/reports
|
|
227
|
+
curl -H "X-Token: admin-token" http://localhost:8000/reports/1
|
|
228
|
+
|
|
229
|
+
# As user (has Contextual permissions - can only access own reports)
|
|
230
|
+
curl -H "X-Token: user-token" http://localhost:8000/reports/1 # OK (owns report 1)
|
|
231
|
+
curl -H "X-Token: user-token" http://localhost:8000/reports/3 # 403 (doesn't own report 3)
|
|
232
|
+
|
|
233
|
+
# As viewer (has Contextual read - can only read own reports)
|
|
234
|
+
curl -H "X-Token: viewer-token" http://localhost:8000/reports/1 # 403 (doesn't own any)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Visualization UI
|
|
238
|
+
|
|
239
|
+
Mount the built-in visualization UI to explore your authorization schema:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
RBACAuthz(
|
|
243
|
+
app,
|
|
244
|
+
# ...
|
|
245
|
+
ui_path="/_rbac",
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Visit `/_rbac` to see an interactive graph showing:
|
|
250
|
+
- **Roles** and their permission grants
|
|
251
|
+
- **Permissions** with scope indicators (global/contextual)
|
|
252
|
+
- **Endpoints** and their required permissions
|
|
253
|
+
- **Context checks** and which endpoints use them
|
|
254
|
+
|
|
255
|
+
Double-click any node to isolate and explore its relationships.
|
|
256
|
+
|
|
257
|
+
### Screenshots
|
|
258
|
+
|
|
259
|
+
**Full authorization graph**
|
|
260
|
+
|
|
261
|
+

|
|
262
|
+
|
|
263
|
+
**Role isolation view** - double-click a role to see what it can access
|
|
264
|
+
|
|
265
|
+

|
|
266
|
+
|
|
267
|
+
**Endpoint isolation view** - double-click an endpoint to see who can access it
|
|
268
|
+
|
|
269
|
+

|