fastpermit 0.1.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.
- fastpermit-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +41 -0
- fastpermit-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- fastpermit-0.1.0/.github/workflows/ci.yml +51 -0
- fastpermit-0.1.0/.github/workflows/release.yml +78 -0
- fastpermit-0.1.0/CHANGELOG.md +42 -0
- fastpermit-0.1.0/CONTRIBUTING.md +33 -0
- fastpermit-0.1.0/LICENSE +21 -0
- fastpermit-0.1.0/MANIFEST.in +6 -0
- fastpermit-0.1.0/Makefile +29 -0
- fastpermit-0.1.0/PKG-INFO +383 -0
- fastpermit-0.1.0/README.md +346 -0
- fastpermit-0.1.0/examples/basic.py +100 -0
- fastpermit-0.1.0/pyproject.toml +97 -0
- fastpermit-0.1.0/setup.cfg +4 -0
- fastpermit-0.1.0/src/fastpermit/__init__.py +37 -0
- fastpermit-0.1.0/src/fastpermit/backends/__init__.py +4 -0
- fastpermit-0.1.0/src/fastpermit/backends/base.py +17 -0
- fastpermit-0.1.0/src/fastpermit/backends/memory.py +42 -0
- fastpermit-0.1.0/src/fastpermit/core/__init__.py +12 -0
- fastpermit-0.1.0/src/fastpermit/core/context.py +35 -0
- fastpermit-0.1.0/src/fastpermit/core/evaluator.py +84 -0
- fastpermit-0.1.0/src/fastpermit/core/helpers.py +24 -0
- fastpermit-0.1.0/src/fastpermit/core/permission.py +164 -0
- fastpermit-0.1.0/src/fastpermit/integrations/__init__.py +141 -0
- fastpermit-0.1.0/src/fastpermit/permissions.py +108 -0
- fastpermit-0.1.0/src/fastpermit/principal.py +38 -0
- fastpermit-0.1.0/src/fastpermit/py.typed +0 -0
- fastpermit-0.1.0/src/fastpermit.egg-info/PKG-INFO +383 -0
- fastpermit-0.1.0/src/fastpermit.egg-info/SOURCES.txt +35 -0
- fastpermit-0.1.0/src/fastpermit.egg-info/dependency_links.txt +1 -0
- fastpermit-0.1.0/src/fastpermit.egg-info/requires.txt +11 -0
- fastpermit-0.1.0/src/fastpermit.egg-info/top_level.txt +1 -0
- fastpermit-0.1.0/tests/test_context_and_backend.py +53 -0
- fastpermit-0.1.0/tests/test_evaluator.py +39 -0
- fastpermit-0.1.0/tests/test_integration.py +174 -0
- fastpermit-0.1.0/tests/test_object_permissions.py +107 -0
- fastpermit-0.1.0/tests/test_permissions.py +154 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report incorrect or unexpected FastPermit behavior
|
|
3
|
+
title: "[Bug]: "
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: Thanks for helping improve FastPermit. For security vulnerabilities, follow SECURITY.md instead of filing a public issue.
|
|
9
|
+
- type: input
|
|
10
|
+
id: version
|
|
11
|
+
attributes:
|
|
12
|
+
label: FastPermit version
|
|
13
|
+
placeholder: 0.1.0
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: input
|
|
17
|
+
id: python
|
|
18
|
+
attributes:
|
|
19
|
+
label: Python version
|
|
20
|
+
placeholder: 3.13
|
|
21
|
+
validations:
|
|
22
|
+
required: true
|
|
23
|
+
- type: textarea
|
|
24
|
+
id: reproduction
|
|
25
|
+
attributes:
|
|
26
|
+
label: Minimal reproduction
|
|
27
|
+
description: Include the smallest code sample that reproduces the problem.
|
|
28
|
+
validations:
|
|
29
|
+
required: true
|
|
30
|
+
- type: textarea
|
|
31
|
+
id: expected
|
|
32
|
+
attributes:
|
|
33
|
+
label: Expected behavior
|
|
34
|
+
validations:
|
|
35
|
+
required: true
|
|
36
|
+
- type: textarea
|
|
37
|
+
id: actual
|
|
38
|
+
attributes:
|
|
39
|
+
label: Actual behavior
|
|
40
|
+
validations:
|
|
41
|
+
required: true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a focused improvement to FastPermit
|
|
3
|
+
title: "[Feature]: "
|
|
4
|
+
labels: ["enhancement"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: problem
|
|
8
|
+
attributes:
|
|
9
|
+
label: Problem
|
|
10
|
+
description: What authorization problem would this solve?
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: proposal
|
|
15
|
+
attributes:
|
|
16
|
+
label: Proposed API or behavior
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: alternatives
|
|
21
|
+
attributes:
|
|
22
|
+
label: Alternatives considered
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
cache: pip
|
|
23
|
+
|
|
24
|
+
- name: Install package
|
|
25
|
+
run: python -m pip install -e '.[dev]'
|
|
26
|
+
|
|
27
|
+
- name: Mypy
|
|
28
|
+
run: mypy src/fastpermit
|
|
29
|
+
|
|
30
|
+
- name: Pytest
|
|
31
|
+
run: pytest --cov=fastpermit --cov-report=term-missing
|
|
32
|
+
|
|
33
|
+
package:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.13"
|
|
42
|
+
cache: pip
|
|
43
|
+
|
|
44
|
+
- name: Install build tools
|
|
45
|
+
run: python -m pip install build twine
|
|
46
|
+
|
|
47
|
+
- name: Build distributions
|
|
48
|
+
run: python -m build
|
|
49
|
+
|
|
50
|
+
- name: Validate distributions
|
|
51
|
+
run: python -m twine check dist/*
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build release distributions
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
cache: pip
|
|
23
|
+
|
|
24
|
+
- name: Verify release tag
|
|
25
|
+
shell: bash
|
|
26
|
+
run: |
|
|
27
|
+
python - <<'PY'
|
|
28
|
+
import os
|
|
29
|
+
import tomllib
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
project = tomllib.loads(Path("pyproject.toml").read_text())["project"]
|
|
33
|
+
expected = f"v{project['version']}"
|
|
34
|
+
actual = os.environ["GITHUB_REF_NAME"]
|
|
35
|
+
|
|
36
|
+
if actual != expected:
|
|
37
|
+
raise SystemExit(
|
|
38
|
+
f"Release tag {actual!r} does not match package version {expected!r}."
|
|
39
|
+
)
|
|
40
|
+
PY
|
|
41
|
+
|
|
42
|
+
- name: Install build tools
|
|
43
|
+
run: python -m pip install --upgrade build twine
|
|
44
|
+
|
|
45
|
+
- name: Build distributions
|
|
46
|
+
run: python -m build
|
|
47
|
+
|
|
48
|
+
- name: Validate distributions
|
|
49
|
+
run: python -m twine check dist/*
|
|
50
|
+
|
|
51
|
+
- name: Upload release distributions
|
|
52
|
+
uses: actions/upload-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: python-package-distributions
|
|
55
|
+
path: dist/
|
|
56
|
+
if-no-files-found: error
|
|
57
|
+
|
|
58
|
+
publish:
|
|
59
|
+
name: Publish to PyPI
|
|
60
|
+
needs: build
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
environment:
|
|
63
|
+
name: pypi
|
|
64
|
+
url: https://pypi.org/p/fastpermit
|
|
65
|
+
permissions:
|
|
66
|
+
id-token: write
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- name: Download release distributions
|
|
70
|
+
uses: actions/download-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: python-package-distributions
|
|
73
|
+
path: dist/
|
|
74
|
+
|
|
75
|
+
- name: Publish distributions to PyPI
|
|
76
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
77
|
+
with:
|
|
78
|
+
print-hash: true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
6
|
+
intends to follow Semantic Versioning once the public API stabilizes.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `requirements.txt` for runtime dependencies and `requirements-dev.txt` for development, testing, and packaging dependencies.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Removed Ruff from the mandatory CI and release check gate; linting and formatting remain available as optional local commands.
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2026-08-26
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Flattened the framework integration into `fastpermit.integrations` so framework names no longer appear in internal module paths.
|
|
23
|
+
- Updated package and documentation metadata for the first public release.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- PyPI Trusted Publishing release workflow using GitHub OIDC.
|
|
28
|
+
- Release documentation and tag/version verification.
|
|
29
|
+
- Public repository metadata, maintainer information, CI badges, and security policy.
|
|
30
|
+
- Request-level prechecks for object dependencies so denied requests do not load protected resources.
|
|
31
|
+
- Source and wheel packaging configuration with typed-package marker support.
|
|
32
|
+
- Initial FastPermit package scaffold.
|
|
33
|
+
- Backend-agnostic permission core.
|
|
34
|
+
- Three-state permission evaluation for correct request/object composition.
|
|
35
|
+
- `AND`, `OR`, and `NOT` permission operators.
|
|
36
|
+
- `all_of()` and `any_of()` helpers.
|
|
37
|
+
- `Principal` protocol and `BasicPrincipal` implementation.
|
|
38
|
+
- `PermissionBackend` protocol and `InMemoryBackend`.
|
|
39
|
+
- Built-in `AllowAny`, `DenyAll`, `IsAuthenticated`, `HasRole`, and `HasPermission` rules.
|
|
40
|
+
- Framework `require()` and `require_object()` integrations.
|
|
41
|
+
- Unit and integration test suites.
|
|
42
|
+
- Ruff, mypy, pytest, coverage, build, and GitHub Actions configuration.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m venv .venv
|
|
7
|
+
source .venv/bin/activate
|
|
8
|
+
pip install -e '.[dev]'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run the complete local quality gate:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
make check
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Individual commands:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
make lint
|
|
21
|
+
make format-check
|
|
22
|
+
make typecheck
|
|
23
|
+
make test
|
|
24
|
+
make build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Pull requests
|
|
28
|
+
|
|
29
|
+
- Keep the permission core independent from storage and authentication implementations.
|
|
30
|
+
- Add tests for every behavior change.
|
|
31
|
+
- Add user-facing changes to `CHANGELOG.md`, newest entries first.
|
|
32
|
+
- Keep public APIs fully typed.
|
|
33
|
+
- Use English for code comments and docstrings.
|
fastpermit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitaly Sem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.PHONY: install lint format format-check typecheck test build check clean
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
python -m pip install -e '.[dev]'
|
|
5
|
+
|
|
6
|
+
lint:
|
|
7
|
+
ruff check .
|
|
8
|
+
|
|
9
|
+
format:
|
|
10
|
+
ruff format .
|
|
11
|
+
|
|
12
|
+
format-check:
|
|
13
|
+
ruff format --check .
|
|
14
|
+
|
|
15
|
+
typecheck:
|
|
16
|
+
mypy src/fastpermit
|
|
17
|
+
|
|
18
|
+
test:
|
|
19
|
+
pytest --cov=fastpermit --cov-report=term-missing
|
|
20
|
+
|
|
21
|
+
build:
|
|
22
|
+
python -m build
|
|
23
|
+
python -m twine check dist/*
|
|
24
|
+
|
|
25
|
+
check: typecheck test build
|
|
26
|
+
|
|
27
|
+
clean:
|
|
28
|
+
rm -rf .coverage .mypy_cache .pytest_cache .ruff_cache build dist htmlcov
|
|
29
|
+
find . -type d -name '__pycache__' -prune -exec rm -rf {} +
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastpermit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Composable, backend-agnostic authorization for FastAPI.
|
|
5
|
+
Author: Vitalii Semotiuk
|
|
6
|
+
Maintainer: Vitalii Semotiuk
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Vir2S/fastpermit
|
|
9
|
+
Project-URL: Repository, https://github.com/Vir2S/fastpermit
|
|
10
|
+
Project-URL: Issues, https://github.com/Vir2S/fastpermit/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/Vir2S/fastpermit/blob/master/CHANGELOG.md
|
|
12
|
+
Keywords: fastapi,authorization,permissions,rbac,abac,access-control
|
|
13
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: fastapi>=0.115
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: httpx>=0.28; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.15; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.25; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=6.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.11; extra == "dev"
|
|
35
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# FastPermit
|
|
39
|
+
|
|
40
|
+
[](https://github.com/Vir2S/fastpermit/actions/workflows/ci.yml)
|
|
41
|
+
[](https://www.python.org/)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
|
|
44
|
+
**Composable, backend-agnostic authorization for FastAPI.**
|
|
45
|
+
|
|
46
|
+
FastPermit keeps authentication and authorization separate. Your application authenticates a
|
|
47
|
+
principal with JWT, OAuth2, Auth0, Keycloak, FastAPI Users, or a custom mechanism. FastPermit then
|
|
48
|
+
decides whether that principal may perform an action.
|
|
49
|
+
|
|
50
|
+
The core is deliberately small:
|
|
51
|
+
|
|
52
|
+
- composable permissions with `&`, `|`, and `~`;
|
|
53
|
+
- request-level and object-level authorization;
|
|
54
|
+
- role-based access control (RBAC) primitives;
|
|
55
|
+
- pluggable permission backends;
|
|
56
|
+
- async-first execution;
|
|
57
|
+
- FastAPI dependency integration;
|
|
58
|
+
- no ORM or cache dependency in the core.
|
|
59
|
+
|
|
60
|
+
> Status: `0.1.0` — first public release.
|
|
61
|
+
|
|
62
|
+
## Project status
|
|
63
|
+
|
|
64
|
+
FastPermit is in active early development. The `0.1.x` line focuses on a small, typed,
|
|
65
|
+
backend-agnostic core before adding optional persistence and caching adapters.
|
|
66
|
+
|
|
67
|
+
Planned next steps:
|
|
68
|
+
|
|
69
|
+
- SQLAlchemy 2 / PostgreSQL adapter;
|
|
70
|
+
- Redis permission cache and invalidation hooks;
|
|
71
|
+
- tenant and resource scopes;
|
|
72
|
+
- audit and observability hooks.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Install from PyPI:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install fastpermit
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For development:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/Vir2S/fastpermit.git
|
|
86
|
+
cd fastpermit
|
|
87
|
+
python -m venv .venv
|
|
88
|
+
source .venv/bin/activate
|
|
89
|
+
pip install -e '.[dev]'
|
|
90
|
+
make check
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Quick start
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from typing import Annotated
|
|
97
|
+
|
|
98
|
+
from fastapi import Depends, FastAPI
|
|
99
|
+
|
|
100
|
+
from fastpermit import BasicPrincipal, FastPermit, HasPermission, InMemoryBackend
|
|
101
|
+
|
|
102
|
+
app = FastAPI()
|
|
103
|
+
|
|
104
|
+
backend = InMemoryBackend(
|
|
105
|
+
{
|
|
106
|
+
"user-1": {"project:read", "project:update"},
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def get_current_principal() -> BasicPrincipal:
|
|
112
|
+
# Replace this with JWT, OAuth2, Auth0, Keycloak, or your own authentication.
|
|
113
|
+
return BasicPrincipal(id="user-1", roles=frozenset({"developer"}))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
permit = FastPermit(
|
|
117
|
+
backend=backend,
|
|
118
|
+
principal_loader=get_current_principal,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.get("/projects")
|
|
123
|
+
async def list_projects(
|
|
124
|
+
principal: Annotated[
|
|
125
|
+
BasicPrincipal,
|
|
126
|
+
Depends(permit.require("project:read")),
|
|
127
|
+
],
|
|
128
|
+
) -> dict[str, str]:
|
|
129
|
+
return {"principal_id": str(principal.id)}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A string passed to `require()` is shorthand for `HasPermission(...)`:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
Depends(permit.require("project:read"))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
is equivalent to:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
Depends(permit.require(HasPermission("project:read")))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Permission algebra
|
|
145
|
+
|
|
146
|
+
Permissions can be combined without putting authorization branches in route handlers:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from fastpermit import HasPermission, HasRole, IsAuthenticated
|
|
150
|
+
|
|
151
|
+
permission = (
|
|
152
|
+
IsAuthenticated()
|
|
153
|
+
& HasPermission("project:update")
|
|
154
|
+
& (
|
|
155
|
+
HasRole("admin")
|
|
156
|
+
| HasPermission("project:update:any")
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Supported operators:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
A() & B() # AND
|
|
165
|
+
A() | B() # OR
|
|
166
|
+
~A() # NOT
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The helpers `all_of()` and `any_of()` are available for larger expressions:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from fastpermit import all_of, any_of
|
|
173
|
+
|
|
174
|
+
permission = all_of(
|
|
175
|
+
IsAuthenticated(),
|
|
176
|
+
HasPermission("project:update"),
|
|
177
|
+
any_of(
|
|
178
|
+
HasRole("admin"),
|
|
179
|
+
HasRole("manager"),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Object-level permissions
|
|
185
|
+
|
|
186
|
+
Object rules are intentionally separate from loading the object. A custom rule only needs to
|
|
187
|
+
implement `has_object_permission()`:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from typing import Any
|
|
191
|
+
|
|
192
|
+
from fastpermit import BasePermission, PermissionContext, Principal
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class IsOwner(BasePermission):
|
|
196
|
+
async def has_object_permission(
|
|
197
|
+
self,
|
|
198
|
+
principal: Principal | None,
|
|
199
|
+
obj: Any,
|
|
200
|
+
context: PermissionContext,
|
|
201
|
+
) -> bool:
|
|
202
|
+
return principal is not None and obj.owner_id == principal.id
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Then combine it with ordinary permissions:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
edit_project = (
|
|
209
|
+
HasPermission("project:update:any")
|
|
210
|
+
| (
|
|
211
|
+
HasPermission("project:update")
|
|
212
|
+
& IsOwner()
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Use it with a FastAPI loader dependency:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
@app.patch("/projects/{project_id}")
|
|
221
|
+
async def update_project(
|
|
222
|
+
project=Depends(
|
|
223
|
+
permit.require_object(
|
|
224
|
+
edit_project,
|
|
225
|
+
loader=get_project,
|
|
226
|
+
)
|
|
227
|
+
),
|
|
228
|
+
):
|
|
229
|
+
return project
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
FastPermit evaluates both request-level and object-level branches as a single expression. Rules
|
|
233
|
+
that do not apply during a phase are neutral rather than implicitly allowing or denying it. This
|
|
234
|
+
keeps expressions such as `HasRole("admin") | IsOwner()` and `~IsOwner()` logically correct.
|
|
235
|
+
|
|
236
|
+
## Principals
|
|
237
|
+
|
|
238
|
+
FastPermit uses a small `Principal` protocol rather than a concrete user model. The included
|
|
239
|
+
`BasicPrincipal` is convenient for most applications:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from fastpermit import BasicPrincipal
|
|
243
|
+
|
|
244
|
+
principal = BasicPrincipal(
|
|
245
|
+
id="user-42",
|
|
246
|
+
roles=frozenset({"manager", "reviewer"}),
|
|
247
|
+
attributes={"organization_id": "org-1"},
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
You may return your own object from the authentication dependency as long as it exposes:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
id
|
|
255
|
+
roles
|
|
256
|
+
attributes
|
|
257
|
+
is_authenticated
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Backends
|
|
261
|
+
|
|
262
|
+
A backend answers one question: which permission codes are effective for this principal in this
|
|
263
|
+
scope?
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from collections.abc import Mapping
|
|
267
|
+
from typing import AbstractSet, Any
|
|
268
|
+
|
|
269
|
+
from fastpermit import PermissionBackend, Principal
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class MyBackend(PermissionBackend):
|
|
273
|
+
async def get_permissions(
|
|
274
|
+
self,
|
|
275
|
+
principal: Principal,
|
|
276
|
+
*,
|
|
277
|
+
scope: Mapping[str, Any],
|
|
278
|
+
) -> AbstractSet[str]:
|
|
279
|
+
...
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`InMemoryBackend` is included for tests, prototypes, and examples. SQLAlchemy/PostgreSQL and Redis
|
|
283
|
+
adapters are intentionally planned as optional integrations instead of core requirements.
|
|
284
|
+
|
|
285
|
+
## Request context and scopes
|
|
286
|
+
|
|
287
|
+
A permission receives `PermissionContext`, which contains:
|
|
288
|
+
|
|
289
|
+
- the configured backend;
|
|
290
|
+
- a scope mapping;
|
|
291
|
+
- integration-specific attributes;
|
|
292
|
+
- a per-evaluation permission cache.
|
|
293
|
+
|
|
294
|
+
FastAPI integration exposes the current `Request` as `context.attributes["request"]` without
|
|
295
|
+
making the authorization core depend on FastAPI.
|
|
296
|
+
|
|
297
|
+
Static scope can be attached to a dependency:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
Depends(
|
|
301
|
+
permit.require(
|
|
302
|
+
"billing:read",
|
|
303
|
+
scope={"tenant": "global"},
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Dynamic tenant scopes are planned for the next integration iteration.
|
|
309
|
+
|
|
310
|
+
## HTTP semantics
|
|
311
|
+
|
|
312
|
+
FastPermit does not authenticate requests. Authentication remains the responsibility of your
|
|
313
|
+
principal loader.
|
|
314
|
+
|
|
315
|
+
When a FastPermit rule denies access:
|
|
316
|
+
|
|
317
|
+
- an absent or unauthenticated principal produces `401 Unauthorized`;
|
|
318
|
+
- an authenticated principal without sufficient authorization produces `403 Forbidden`.
|
|
319
|
+
|
|
320
|
+
## Design principles
|
|
321
|
+
|
|
322
|
+
1. Authentication and authorization are separate concerns.
|
|
323
|
+
2. Routes should describe required access, not implement role branches.
|
|
324
|
+
3. Permission codes are stable capabilities such as `project:update`.
|
|
325
|
+
4. Roles aggregate capabilities; application code should not be coupled to role names where a
|
|
326
|
+
capability is the real requirement.
|
|
327
|
+
5. Object-level rules belong in permissions, not route handlers.
|
|
328
|
+
6. Storage and caching are adapters, not core concerns.
|
|
329
|
+
7. Authorization expressions must preserve correct semantics across request and object phases.
|
|
330
|
+
|
|
331
|
+
## Roadmap
|
|
332
|
+
|
|
333
|
+
### 0.1
|
|
334
|
+
|
|
335
|
+
- [x] permission core;
|
|
336
|
+
- [x] `AND`, `OR`, `NOT` composition;
|
|
337
|
+
- [x] `all_of()` / `any_of()`;
|
|
338
|
+
- [x] `IsAuthenticated`;
|
|
339
|
+
- [x] `HasRole`;
|
|
340
|
+
- [x] `HasPermission`;
|
|
341
|
+
- [x] object-level permissions;
|
|
342
|
+
- [x] backend protocol;
|
|
343
|
+
- [x] in-memory backend;
|
|
344
|
+
- [x] FastAPI integration;
|
|
345
|
+
- [x] typed package;
|
|
346
|
+
- [x] tests and CI.
|
|
347
|
+
|
|
348
|
+
### 0.2
|
|
349
|
+
|
|
350
|
+
- [ ] SQLAlchemy 2.x adapter;
|
|
351
|
+
- [ ] PostgreSQL RBAC reference models;
|
|
352
|
+
- [ ] Alembic examples;
|
|
353
|
+
- [ ] user-role and role-permission repositories.
|
|
354
|
+
|
|
355
|
+
### 0.3
|
|
356
|
+
|
|
357
|
+
- [ ] Redis cache adapter;
|
|
358
|
+
- [ ] cache invalidation primitives;
|
|
359
|
+
- [ ] cache versioning;
|
|
360
|
+
- [ ] configurable TTL policies.
|
|
361
|
+
|
|
362
|
+
### 0.4
|
|
363
|
+
|
|
364
|
+
- [ ] dynamic tenant scopes;
|
|
365
|
+
- [ ] attribute-based access control helpers;
|
|
366
|
+
- [ ] resource scopes;
|
|
367
|
+
- [ ] policy metadata.
|
|
368
|
+
|
|
369
|
+
### 0.5
|
|
370
|
+
|
|
371
|
+
- [ ] authorization audit events;
|
|
372
|
+
- [ ] observability hooks;
|
|
373
|
+
- [ ] OpenTelemetry integration.
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT
|
|
378
|
+
|
|
379
|
+
## Maintainer
|
|
380
|
+
|
|
381
|
+
Created and maintained by [Vitaly Sem](https://github.com/Vir2S).
|
|
382
|
+
|
|
383
|
+
FastPermit is an independent open-source project developed with support from Born2CodeLab.
|