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.
Files changed (33) hide show
  1. fastapi_rbac_authz-0.2.0/.github/workflows/checks.yaml +30 -0
  2. fastapi_rbac_authz-0.2.0/.github/workflows/pypi-minor-deployment.yaml +59 -0
  3. fastapi_rbac_authz-0.2.0/.gitignore +245 -0
  4. fastapi_rbac_authz-0.2.0/.pre-commit-config.yaml +17 -0
  5. fastapi_rbac_authz-0.2.0/CLAUDE.md +66 -0
  6. fastapi_rbac_authz-0.2.0/PKG-INFO +269 -0
  7. fastapi_rbac_authz-0.2.0/README.md +247 -0
  8. fastapi_rbac_authz-0.2.0/examples/basic_app.py +186 -0
  9. fastapi_rbac_authz-0.2.0/fastapi_rbac/__init__.py +35 -0
  10. fastapi_rbac_authz-0.2.0/fastapi_rbac/context.py +39 -0
  11. fastapi_rbac_authz-0.2.0/fastapi_rbac/core.py +111 -0
  12. fastapi_rbac_authz-0.2.0/fastapi_rbac/dependencies.py +254 -0
  13. fastapi_rbac_authz-0.2.0/fastapi_rbac/exceptions.py +8 -0
  14. fastapi_rbac_authz-0.2.0/fastapi_rbac/permissions.py +87 -0
  15. fastapi_rbac_authz-0.2.0/fastapi_rbac/py.typed +1 -0
  16. fastapi_rbac_authz-0.2.0/fastapi_rbac/router.py +319 -0
  17. fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/__init__.py +9 -0
  18. fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/routes.py +67 -0
  19. fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/schema.py +253 -0
  20. fastapi_rbac_authz-0.2.0/fastapi_rbac/ui/static/index.html +1879 -0
  21. fastapi_rbac_authz-0.2.0/poetry.lock +913 -0
  22. fastapi_rbac_authz-0.2.0/pyproject.toml +66 -0
  23. fastapi_rbac_authz-0.2.0/tests/__init__.py +0 -0
  24. fastapi_rbac_authz-0.2.0/tests/conftest.py +13 -0
  25. fastapi_rbac_authz-0.2.0/tests/test_context.py +49 -0
  26. fastapi_rbac_authz-0.2.0/tests/test_context_di.py +292 -0
  27. fastapi_rbac_authz-0.2.0/tests/test_core.py +74 -0
  28. fastapi_rbac_authz-0.2.0/tests/test_dependencies.py +431 -0
  29. fastapi_rbac_authz-0.2.0/tests/test_exceptions.py +15 -0
  30. fastapi_rbac_authz-0.2.0/tests/test_integration.py +583 -0
  31. fastapi_rbac_authz-0.2.0/tests/test_permissions.py +111 -0
  32. fastapi_rbac_authz-0.2.0/tests/test_router.py +458 -0
  33. 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
+ ![Full Graph](https://github.com/user-attachments/assets/912826a4-63a6-4865-8ac7-707ce2746033)
262
+
263
+ **Role isolation view** - double-click a role to see what it can access
264
+
265
+ ![Role Isolation](https://github.com/user-attachments/assets/392b8d06-aa10-433a-a7bf-bce1af02d9df)
266
+
267
+ **Endpoint isolation view** - double-click an endpoint to see who can access it
268
+
269
+ ![Endpoint Isolation](https://github.com/user-attachments/assets/b0357fe1-d265-4aed-a4e3-ee439a370c46)