pyauthz 0.9.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.
- pyauthz-0.9.0/.gitignore +50 -0
- pyauthz-0.9.0/ARCHITECTURE.md +124 -0
- pyauthz-0.9.0/CHANGELOG.md +140 -0
- pyauthz-0.9.0/CONTRIBUTING.md +123 -0
- pyauthz-0.9.0/LICENSE +190 -0
- pyauthz-0.9.0/PKG-INFO +206 -0
- pyauthz-0.9.0/README.md +148 -0
- pyauthz-0.9.0/SECURITY.md +42 -0
- pyauthz-0.9.0/docs/FEDRAMP_GUIDE.md +243 -0
- pyauthz-0.9.0/llms.txt +21 -0
- pyauthz-0.9.0/pyauthz/__init__.py +108 -0
- pyauthz-0.9.0/pyauthz/audit/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/audit/compliance.py +102 -0
- pyauthz-0.9.0/pyauthz/audit/integrity.py +92 -0
- pyauthz-0.9.0/pyauthz/audit/logger.py +92 -0
- pyauthz-0.9.0/pyauthz/audit/models.py +74 -0
- pyauthz-0.9.0/pyauthz/cli/__init__.py +1 -0
- pyauthz-0.9.0/pyauthz/cli/main.py +332 -0
- pyauthz-0.9.0/pyauthz/contrib/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/contrib/x509.py +183 -0
- pyauthz-0.9.0/pyauthz/core/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/core/breakglass.py +183 -0
- pyauthz-0.9.0/pyauthz/core/cache.py +111 -0
- pyauthz-0.9.0/pyauthz/core/conditions.py +151 -0
- pyauthz-0.9.0/pyauthz/core/decision.py +44 -0
- pyauthz-0.9.0/pyauthz/core/delegation.py +155 -0
- pyauthz-0.9.0/pyauthz/core/diff.py +143 -0
- pyauthz-0.9.0/pyauthz/core/engine.py +669 -0
- pyauthz-0.9.0/pyauthz/core/errors.py +83 -0
- pyauthz-0.9.0/pyauthz/core/explain.py +121 -0
- pyauthz-0.9.0/pyauthz/core/lint.py +244 -0
- pyauthz-0.9.0/pyauthz/core/simulation.py +150 -0
- pyauthz-0.9.0/pyauthz/core/temporal.py +160 -0
- pyauthz-0.9.0/pyauthz/core/tenancy.py +74 -0
- pyauthz-0.9.0/pyauthz/filtering/__init__.py +42 -0
- pyauthz-0.9.0/pyauthz/filtering/compiler.py +292 -0
- pyauthz-0.9.0/pyauthz/filtering/django.py +110 -0
- pyauthz-0.9.0/pyauthz/filtering/expressions.py +78 -0
- pyauthz-0.9.0/pyauthz/filtering/raw_sql.py +148 -0
- pyauthz-0.9.0/pyauthz/filtering/sql.py +134 -0
- pyauthz-0.9.0/pyauthz/middleware/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/models/__init__.py +19 -0
- pyauthz-0.9.0/pyauthz/models/action.py +18 -0
- pyauthz-0.9.0/pyauthz/models/condition.py +38 -0
- pyauthz-0.9.0/pyauthz/models/context.py +29 -0
- pyauthz-0.9.0/pyauthz/models/permission.py +79 -0
- pyauthz-0.9.0/pyauthz/models/policy.py +46 -0
- pyauthz-0.9.0/pyauthz/models/resource.py +27 -0
- pyauthz-0.9.0/pyauthz/models/role.py +22 -0
- pyauthz-0.9.0/pyauthz/models/subject.py +24 -0
- pyauthz-0.9.0/pyauthz/policies/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/policies/combiners.py +140 -0
- pyauthz-0.9.0/pyauthz/py.typed +0 -0
- pyauthz-0.9.0/pyauthz/storage/__init__.py +0 -0
- pyauthz-0.9.0/pyauthz/storage/base.py +49 -0
- pyauthz-0.9.0/pyauthz/storage/memory.py +82 -0
- pyauthz-0.9.0/pyauthz/storage/watcher.py +133 -0
- pyauthz-0.9.0/pyauthz/storage/yaml_file.py +167 -0
- pyauthz-0.9.0/pyauthz/testing/__init__.py +22 -0
- pyauthz-0.9.0/pyauthz/testing/assertions.py +33 -0
- pyauthz-0.9.0/pyauthz/testing/scenario.py +199 -0
- pyauthz-0.9.0/pyauthz_django/__init__.py +25 -0
- pyauthz-0.9.0/pyauthz_django/decorator.py +66 -0
- pyauthz-0.9.0/pyauthz_django/middleware.py +85 -0
- pyauthz-0.9.0/pyauthz_fastapi/__init__.py +35 -0
- pyauthz-0.9.0/pyauthz_fastapi/decorator.py +159 -0
- pyauthz-0.9.0/pyauthz_fastapi/dependencies.py +52 -0
- pyauthz-0.9.0/pyauthz_fastapi/middleware.py +105 -0
- pyauthz-0.9.0/pyauthz_flask/__init__.py +27 -0
- pyauthz-0.9.0/pyauthz_flask/decorator.py +77 -0
- pyauthz-0.9.0/pyauthz_flask/extension.py +110 -0
- pyauthz-0.9.0/pyauthz_strawberry/__init__.py +31 -0
- pyauthz-0.9.0/pyauthz_strawberry/decorator.py +103 -0
- pyauthz-0.9.0/pyauthz_strawberry/extension.py +78 -0
- pyauthz-0.9.0/pyproject.toml +87 -0
- pyauthz-0.9.0/tests/__init__.py +0 -0
- pyauthz-0.9.0/tests/test_abac_engine.py +271 -0
- pyauthz-0.9.0/tests/test_audit.py +77 -0
- pyauthz-0.9.0/tests/test_breakglass.py +93 -0
- pyauthz-0.9.0/tests/test_cache.py +170 -0
- pyauthz-0.9.0/tests/test_cli.py +196 -0
- pyauthz-0.9.0/tests/test_combiners.py +150 -0
- pyauthz-0.9.0/tests/test_compliance.py +589 -0
- pyauthz-0.9.0/tests/test_conditions.py +285 -0
- pyauthz-0.9.0/tests/test_delegation.py +138 -0
- pyauthz-0.9.0/tests/test_diff.py +110 -0
- pyauthz-0.9.0/tests/test_engine.py +401 -0
- pyauthz-0.9.0/tests/test_explain.py +93 -0
- pyauthz-0.9.0/tests/test_filtering.py +649 -0
- pyauthz-0.9.0/tests/test_hierarchy.py +152 -0
- pyauthz-0.9.0/tests/test_lint.py +133 -0
- pyauthz-0.9.0/tests/test_models.py +205 -0
- pyauthz-0.9.0/tests/test_properties.py +130 -0
- pyauthz-0.9.0/tests/test_simulation.py +190 -0
- pyauthz-0.9.0/tests/test_storage.py +87 -0
- pyauthz-0.9.0/tests/test_temporal.py +110 -0
- pyauthz-0.9.0/tests/test_tenancy.py +81 -0
- pyauthz-0.9.0/tests/test_testing.py +123 -0
- pyauthz-0.9.0/tests/test_watcher.py +90 -0
- pyauthz-0.9.0/tests/test_yaml.py +195 -0
- pyauthz-0.9.0/tests_pyauthz_django/__init__.py +0 -0
- pyauthz-0.9.0/tests_pyauthz_django/test_django.py +272 -0
- pyauthz-0.9.0/tests_pyauthz_fastapi/__init__.py +0 -0
- pyauthz-0.9.0/tests_pyauthz_fastapi/test_fastapi.py +227 -0
- pyauthz-0.9.0/tests_pyauthz_flask/__init__.py +0 -0
- pyauthz-0.9.0/tests_pyauthz_flask/test_flask.py +175 -0
- pyauthz-0.9.0/tests_pyauthz_strawberry/__init__.py +0 -0
- pyauthz-0.9.0/tests_pyauthz_strawberry/test_strawberry.py +140 -0
pyauthz-0.9.0/.gitignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Python bytecode
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
|
|
16
|
+
# Testing / coverage
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.hypothesis/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Environment / secrets
|
|
23
|
+
.env
|
|
24
|
+
.env.*
|
|
25
|
+
|
|
26
|
+
# Audit logs (generated at runtime)
|
|
27
|
+
logs/
|
|
28
|
+
*.jsonl
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.vscode/
|
|
32
|
+
.idea/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
|
|
36
|
+
# OS junk
|
|
37
|
+
Thumbs.db
|
|
38
|
+
Desktop.ini
|
|
39
|
+
.DS_Store
|
|
40
|
+
|
|
41
|
+
# Internal planning docs (local only)
|
|
42
|
+
AGENTS.md
|
|
43
|
+
CONTEXT.md
|
|
44
|
+
Development.md
|
|
45
|
+
Implementation.md
|
|
46
|
+
Progress.md
|
|
47
|
+
GOV_PITCH.md
|
|
48
|
+
rampart-project.md
|
|
49
|
+
bastion-project.md
|
|
50
|
+
pyauthz-project.md
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# PyAuthz Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the internal architecture of PyAuthz for contributors and advanced users.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
PyAuthz evaluates authorization as: **Can this Subject perform this Action on this Resource?**
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Subject ──┐
|
|
11
|
+
Action ──┤──> Engine ──> Decision (allow/deny + reasoning)
|
|
12
|
+
Resource ──┤
|
|
13
|
+
Env ──┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Evaluation Pipeline
|
|
17
|
+
|
|
18
|
+
When `rp.authorize(subject, action, resource)` is called:
|
|
19
|
+
|
|
20
|
+
1. **Cache check** — If a `DecisionCache` is attached, check for a cached decision.
|
|
21
|
+
2. **Action resolution** — If the action has no colon, prepend the resource type: `"write"` → `"documents:write"`.
|
|
22
|
+
3. **Environment merge** — Merge default environment with per-request env kwargs.
|
|
23
|
+
4. **Role resolution** — Walk the role hierarchy (parents) to compute the subject's effective roles.
|
|
24
|
+
5. **Policy matching** — Find all applicable policies:
|
|
25
|
+
- Policy is enabled
|
|
26
|
+
- Subject has at least one of the policy's required roles (or policy has no role restriction)
|
|
27
|
+
- Action matches at least one of the policy's permission patterns
|
|
28
|
+
- All conditions evaluate to true (ABAC)
|
|
29
|
+
6. **Combination** — Run the combiner algorithm on matched policies:
|
|
30
|
+
- `deny-override`: any DENY wins over all ALLOWs
|
|
31
|
+
- `allow-override`: any ALLOW wins over all DENYs
|
|
32
|
+
- `priority-first`: highest priority policy wins
|
|
33
|
+
- `first-match`: first matched policy wins
|
|
34
|
+
7. **Decision** — Build and return a `Decision` object with the result, reasoning, matched policies, and timing.
|
|
35
|
+
8. **Cache store** — If caching is enabled, store the decision for future lookups.
|
|
36
|
+
|
|
37
|
+
## Module Map
|
|
38
|
+
|
|
39
|
+
### `pyauthz.core.engine` — The Heart
|
|
40
|
+
|
|
41
|
+
The `PyAuthz` class is the central coordinator. It owns storage, cache, audit, and the evaluation pipeline. All authorization flows through `_evaluate()`.
|
|
42
|
+
|
|
43
|
+
### `pyauthz.models.*` — Data Models
|
|
44
|
+
|
|
45
|
+
Pydantic models for all domain objects. These are the public API surface:
|
|
46
|
+
|
|
47
|
+
- `Subject` — who is requesting access (id, roles, attributes)
|
|
48
|
+
- `Resource` — what is being accessed (id, type, owner_id, attributes)
|
|
49
|
+
- `Role` — named permission set with optional parent hierarchy
|
|
50
|
+
- `Policy` — explicit allow/deny rule with conditions
|
|
51
|
+
- `Permission` — parsed `resource_type:action` with wildcard support
|
|
52
|
+
- `Condition` — ABAC condition (field, operator, value)
|
|
53
|
+
- `Decision` — evaluation result with full reasoning chain
|
|
54
|
+
|
|
55
|
+
### `pyauthz.core.conditions` — ABAC Engine
|
|
56
|
+
|
|
57
|
+
Evaluates conditions against an `AccessRequest` context. Handles:
|
|
58
|
+
- Dot-notation field resolution (`subject.attributes.department`)
|
|
59
|
+
- Cross-references (`resource.owner_id == subject.id`)
|
|
60
|
+
- 10 operators: `==`, `!=`, `>`, `<`, `>=`, `<=`, `in`, `not_in`, `contains`, `matches`
|
|
61
|
+
- Fail-safe: missing attributes produce DENY, never crash
|
|
62
|
+
|
|
63
|
+
### `pyauthz.policies.combiners` — Decision Algorithms
|
|
64
|
+
|
|
65
|
+
Pure functions that take a list of matched policies and return (effect, matched_names, reasoning). Each combiner implements a different conflict resolution strategy.
|
|
66
|
+
|
|
67
|
+
### `pyauthz.storage.*` — Persistence
|
|
68
|
+
|
|
69
|
+
- `StorageBackend` — protocol defining the storage interface
|
|
70
|
+
- `MemoryStorage` — thread-safe in-memory implementation (default)
|
|
71
|
+
- `yaml_file` — YAML loader with schema validation
|
|
72
|
+
- `watcher` — PolicyWatcher for hot-reload on file changes
|
|
73
|
+
|
|
74
|
+
### `pyauthz.filtering.*` — Query Filters
|
|
75
|
+
|
|
76
|
+
Converts policies into database-compatible filter expressions:
|
|
77
|
+
- `expressions.py` — FilterExpression AST (frozen dataclasses)
|
|
78
|
+
- `compiler.py` — policy-to-AST compilation
|
|
79
|
+
- `sql.py` — SQLAlchemy WHERE clause generation
|
|
80
|
+
- `django.py` — Django Q object generation
|
|
81
|
+
- `raw_sql.py` — Parameterized raw SQL generation
|
|
82
|
+
|
|
83
|
+
### `pyauthz.core.cache` — Decision Caching
|
|
84
|
+
|
|
85
|
+
TTL-based LRU cache with automatic invalidation when policies or roles change.
|
|
86
|
+
|
|
87
|
+
### `pyauthz.core.tenancy` — Multi-Tenancy
|
|
88
|
+
|
|
89
|
+
Per-tenant isolated storage instances via `TenantManager`.
|
|
90
|
+
|
|
91
|
+
### `pyauthz.core.temporal` — Time-Bounded Roles
|
|
92
|
+
|
|
93
|
+
Role assignments with `effective_from` and `expires_at` windows.
|
|
94
|
+
|
|
95
|
+
### `pyauthz.core.delegation` — Permission Delegation
|
|
96
|
+
|
|
97
|
+
User A grants User B temporary permissions with expiration and audit trail.
|
|
98
|
+
|
|
99
|
+
### `pyauthz.core.simulation` — Policy Simulation
|
|
100
|
+
|
|
101
|
+
"What would happen if..." analysis by cloning the engine with modified policies/roles.
|
|
102
|
+
|
|
103
|
+
## Thread Safety
|
|
104
|
+
|
|
105
|
+
The engine uses `threading.RLock` in `MemoryStorage` for all reads and writes. The `DecisionCache` has its own lock. The engine is safe for concurrent use in WSGI workers.
|
|
106
|
+
|
|
107
|
+
## Async Support
|
|
108
|
+
|
|
109
|
+
Async methods (`aauthorize`, `ais_allowed`, `aauthorize_bulk`) use `asyncio.to_thread()` to run the synchronous engine in a thread pool. The engine itself is not async-native — this keeps the code simple while providing a real async interface.
|
|
110
|
+
|
|
111
|
+
## Extension Points
|
|
112
|
+
|
|
113
|
+
- **Custom storage backends**: Implement `StorageBackend` for SQL, Redis, etc.
|
|
114
|
+
- **Custom combiners**: Add functions to `COMBINERS` dict
|
|
115
|
+
- **Framework integrations**: Follow the `pyauthz_fastapi`/`pyauthz_flask` pattern
|
|
116
|
+
- **Audit exporters**: Implement custom exporters for the audit logger
|
|
117
|
+
|
|
118
|
+
## Design Principles
|
|
119
|
+
|
|
120
|
+
1. **Deny by default** — If nothing explicitly allows access, deny.
|
|
121
|
+
2. **Fail-safe** — Missing attributes, bad conditions, or errors produce DENY, never ALLOW.
|
|
122
|
+
3. **Separation of concerns** — Models don't know about the engine. The engine doesn't know about frameworks.
|
|
123
|
+
4. **Structured decisions** — Every authorization produces a `Decision` with reasoning, not just a boolean.
|
|
124
|
+
5. **Zero external dependencies for core** — Only Pydantic and PyYAML for the core engine.
|
|
@@ -0,0 +1,140 @@
|
|
|
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/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.9.0] - 2026-03-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Federal Compliance & Hardened Audit (Phase 9)**
|
|
13
|
+
- `AuditEvent` expanded with optional `source_ip`, `request_id`, `service_id` fields (NIST 800-53 AU-3)
|
|
14
|
+
- `MutationEvent` and `MutationAction` models for tracking configuration changes
|
|
15
|
+
- Engine mutation audit hooks — `add_role()`, `remove_role()`, `replace_roles()`, `add_policy()`, `remove_policy()`, `replace_policies()` all emit `MutationEvent` records (AC-2, CM-3)
|
|
16
|
+
- `TemporalRoleStore` and `DelegationStore` accept optional `audit_logger` for temporal/delegation audit events (AC-2)
|
|
17
|
+
- `ComplianceExporter` — enriches events with NIST 800-53 control mappings and event categories
|
|
18
|
+
- `HmacExporter` — HMAC-SHA256 signed log entries with `verify()` for tamper detection (AU-9)
|
|
19
|
+
- `pyauthz.contrib.x509` — X.509 Distinguished Name parsing: `parse_dn()`, `extract_dod_id()`, `subject_from_dn()`
|
|
20
|
+
- `Denied` exception now carries `.suggestions` populated automatically by `explain_denial()`
|
|
21
|
+
- Django integration tests (9 tests)
|
|
22
|
+
- Strawberry GraphQL integration tests (7 tests)
|
|
23
|
+
- 42 new compliance tests (404 total across all suites)
|
|
24
|
+
- `docs/FEDRAMP_GUIDE.md` — authorization patterns for FedRAMP compliance
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `Denied.__init__` accepts optional `suggestions` parameter
|
|
29
|
+
- Strawberry `PyAuthzExtension` now inherits from `SchemaExtension` (compatible with Strawberry v0.300+)
|
|
30
|
+
- `AuditLogger` gains `log_mutation()` method for configuration change events
|
|
31
|
+
|
|
32
|
+
## [0.8.0] - 2026-03-06
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `diff_policies()` — compare two policy sets and report authorization outcome changes (allow→deny, deny→allow)
|
|
37
|
+
- `lint_policies()` — detect common policy mistakes: undefined roles (L001), overly broad (L002), contradictory (L003), disabled (L004), orphan roles (L005), undefined parents (L006)
|
|
38
|
+
- `pyauthz lint` CLI command for linting policy files
|
|
39
|
+
- `explain_denial()` — generate actionable suggestions for denied access (which roles would help, which conditions failed)
|
|
40
|
+
- `BreakGlassManager` — emergency access bypass with required justification, audit logging, and authorized subject list (maps to NIST 800-53 AC-2(2))
|
|
41
|
+
- 29 new tests (346 total) covering diff, lint, explain, and break-glass
|
|
42
|
+
|
|
43
|
+
## [0.7.0] - 2026-03-06
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `pyauthz.testing` module with `AuthScenario`, `must_allow`, `must_deny` for pytest-friendly authorization testing
|
|
48
|
+
- `pyauthz` CLI tool with `check`, `simulate`, `roles`, and `policies` commands
|
|
49
|
+
- `CONTRIBUTING.md` contributor guide
|
|
50
|
+
- `ARCHITECTURE.md` internal architecture documentation
|
|
51
|
+
- 23 new tests (317 total) for testing utilities and CLI
|
|
52
|
+
|
|
53
|
+
## [0.6.0] - 2026-03-06
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- `DecisionCache` — TTL-based LRU decision cache with automatic invalidation on policy/role changes
|
|
58
|
+
- `PyAuthz.set_cache()` — attach a cache to the engine for automatic caching of authorization decisions
|
|
59
|
+
- `TenantManager` — multi-tenancy support with per-tenant isolated roles and policies
|
|
60
|
+
- `TemporalRole` + `TemporalRoleStore` — time-bounded role assignments with `effective_from` and `expires_at`
|
|
61
|
+
- `Delegation` + `DelegationStore` — controlled permission delegation between users with expiration
|
|
62
|
+
- `simulate()` + `SimulationResult` — policy simulation ("what would happen if...") without modifying the live engine
|
|
63
|
+
- All new types exported from `pyauthz.__init__`
|
|
64
|
+
- 50 new tests (294 total) covering cache, multi-tenancy, temporal permissions, delegation, and simulation
|
|
65
|
+
|
|
66
|
+
## [0.5.0] - 2026-03-06
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- `pyauthz_flask` package — Flask extension with before_request subject resolution, `@requires()` decorator, `get_subject()` helper, and Denied -> 403 JSON handler
|
|
71
|
+
- `pyauthz_django` package — Django middleware with subject resolution, `@requires()` view decorator, and Denied -> 403 JSON response
|
|
72
|
+
- `pyauthz_strawberry` package — Strawberry GraphQL extension with context-based subject resolution and `@requires()` resolver decorator (sync + async)
|
|
73
|
+
- 7 Flask integration tests (244 total)
|
|
74
|
+
|
|
75
|
+
## [0.4.0] - 2026-03-06
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- `authorized_filter()` method on `PyAuthz` engine — produces `FilterResult` with AST
|
|
80
|
+
- `FilterExpression` AST: `AllowAll`, `DenyAll`, `Compare`, `And`, `Or`, `Not` nodes
|
|
81
|
+
- `to_sqlalchemy_filter()` — compile FilterExpression to SQLAlchemy WHERE clauses
|
|
82
|
+
- `to_django_q()` — compile FilterExpression to Django ORM Q objects
|
|
83
|
+
- `to_raw_sql()` — compile FilterExpression to parameterized raw SQL
|
|
84
|
+
- Environment conditions flagged as unpushable in `FilterResult.unpushable_conditions`
|
|
85
|
+
- Deny-override combiner produces `AND NOT` clauses for DENY policies
|
|
86
|
+
- Cross-reference conditions resolved to literal values at compile time
|
|
87
|
+
- Performance benchmarks: simple RBAC <500μs, ABAC <2000μs
|
|
88
|
+
- 44 new tests (237 total) covering filtering AST, compilers, and benchmarks
|
|
89
|
+
|
|
90
|
+
## [0.3.0] - 2026-03-06
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- `pyauthz_fastapi` package — first-class FastAPI authorization integration
|
|
95
|
+
- `PyAuthzMiddleware` — ASGI middleware for request-level subject resolution
|
|
96
|
+
- `@requires()` decorator for route-level authorization checks
|
|
97
|
+
- `setup_pyauthz()` convenience function (adds middleware + Denied exception handler)
|
|
98
|
+
- `get_subject()`, `get_decision()`, `get_pyauthz()` FastAPI dependency injection helpers
|
|
99
|
+
- Subject resolver protocol: user-provided async callable `(Request) -> Subject`
|
|
100
|
+
- Resource resolver protocol: user-provided callable for custom resource resolution
|
|
101
|
+
- Denied exception automatically mapped to 403 JSON response with structured error body
|
|
102
|
+
- 9 FastAPI integration tests (193 total)
|
|
103
|
+
|
|
104
|
+
## [0.2.0] - 2026-03-06
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
|
|
108
|
+
- Role hierarchy with parent inheritance — roles can list `parents` to inherit permissions
|
|
109
|
+
- Cycle detection at `add_role()` time — circular role hierarchies are caught immediately, never at evaluation time
|
|
110
|
+
- Full ABAC condition evaluation with 10 operators: ==, !=, >, <, >=, <=, in, not_in, contains, matches (regex)
|
|
111
|
+
- Dot-notation field resolution for deep attribute access (`subject.attributes.department`, `resource.attributes.classification`)
|
|
112
|
+
- Cross-reference conditions: `Condition(field="resource.owner_id", op="==", value="subject.id")`
|
|
113
|
+
- Fail-safe condition evaluation: missing attributes produce DENY with descriptive reasoning, never crash
|
|
114
|
+
- `allow-override` combiner: any ALLOW wins over all DENYs
|
|
115
|
+
- `priority-first` combiner: highest-priority applicable policy wins
|
|
116
|
+
- `PolicyWatcher` for YAML hot-reload — polls for file changes and auto-reloads policies
|
|
117
|
+
- 75 new tests (184 total) covering ABAC conditions, role hierarchy, new combiners, and the file watcher
|
|
118
|
+
|
|
119
|
+
## [0.1.0] - 2026-03-06
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
|
|
123
|
+
- Core authorization engine (`PyAuthz`) with sync and async APIs
|
|
124
|
+
- Pydantic data models: `Subject`, `Resource`, `Role`, `Permission`, `Policy`, `Condition`, `Decision`, `AccessRequest`
|
|
125
|
+
- Permission string parsing with wildcard support (`*:*`, `documents:*`, `*:read`)
|
|
126
|
+
- Action shorthand: `"write"` auto-expands to `"documents:write"` via `resource.type`
|
|
127
|
+
- Flat RBAC: roles with permissions, subjects with roles
|
|
128
|
+
- Auto-default policy: allow if role has matching permission (no explicit policies needed)
|
|
129
|
+
- `authorize()` / `aauthorize()` — returns `Decision` on allow, raises `Denied` on deny
|
|
130
|
+
- `is_allowed()` / `ais_allowed()` — returns `bool`, never raises
|
|
131
|
+
- `authorize_bulk()` / `aauthorize_bulk()` — batch authorization checks
|
|
132
|
+
- Policy combination algorithms: deny-override (default), first-match
|
|
133
|
+
- In-memory storage backend with thread-safe locking
|
|
134
|
+
- YAML policy loading with `version: 1` schema validation
|
|
135
|
+
- Structured audit logging with `JsonExporter`
|
|
136
|
+
- Error hierarchy: `Denied`, `SchemaError`, `ConfigError`, `PolicyVersionError`
|
|
137
|
+
- Role and policy management: `add_role()`, `remove_role()`, `replace_roles()`, `add_policy()`, `remove_policy()`, `replace_policies()`
|
|
138
|
+
- Environment context: per-request kwargs and `set_environment()` defaults
|
|
139
|
+
- `py.typed` marker for PEP 561 compliance
|
|
140
|
+
- 109 tests with >95% coverage including Hypothesis property-based tests
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Contributing to PyAuthz
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to PyAuthz! This guide covers everything you need to get started.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone the repo
|
|
9
|
+
git clone https://github.com/marci/pyauthz.git
|
|
10
|
+
cd pyauthz
|
|
11
|
+
|
|
12
|
+
# Create a virtual environment
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
.venv/Scripts/activate # Windows
|
|
15
|
+
# source .venv/bin/activate # macOS/Linux
|
|
16
|
+
|
|
17
|
+
# Install in editable mode with dev dependencies
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
|
|
20
|
+
# Install framework extras if working on integrations
|
|
21
|
+
pip install fastapi httpx flask strawberry-graphql
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running Tests
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Run all tests
|
|
28
|
+
pytest
|
|
29
|
+
|
|
30
|
+
# Run specific test file
|
|
31
|
+
pytest tests/test_engine.py
|
|
32
|
+
|
|
33
|
+
# Run with coverage
|
|
34
|
+
pytest --cov=pyauthz --cov-report=term-missing
|
|
35
|
+
|
|
36
|
+
# Run only core tests (faster)
|
|
37
|
+
pytest tests/
|
|
38
|
+
|
|
39
|
+
# Run framework integration tests
|
|
40
|
+
pytest tests_fastapi/ tests_flask/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Code Quality
|
|
44
|
+
|
|
45
|
+
We use `ruff` for linting and formatting:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Lint
|
|
49
|
+
ruff check .
|
|
50
|
+
|
|
51
|
+
# Auto-fix lint issues
|
|
52
|
+
ruff check --fix .
|
|
53
|
+
|
|
54
|
+
# Format
|
|
55
|
+
ruff format .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
All code must pass `ruff check` and `ruff format --check` before merge.
|
|
59
|
+
|
|
60
|
+
## Project Structure
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
pyauthz/
|
|
64
|
+
├── core/ # Engine, decision, errors, conditions, cache, etc.
|
|
65
|
+
├── models/ # Pydantic data models (Subject, Resource, Role, etc.)
|
|
66
|
+
├── policies/ # Combiner algorithms
|
|
67
|
+
├── storage/ # Storage backends (memory, YAML loader, watcher)
|
|
68
|
+
├── filtering/ # Query filter compilation (AST, SQL, Django)
|
|
69
|
+
├── audit/ # Audit logging
|
|
70
|
+
├── testing/ # Testing utilities (AuthScenario, assertions)
|
|
71
|
+
├── cli/ # CLI tool (pyauthz check, simulate)
|
|
72
|
+
├── middleware/ # Shared middleware types
|
|
73
|
+
pyauthz_fastapi/ # FastAPI integration package
|
|
74
|
+
pyauthz_flask/ # Flask integration package
|
|
75
|
+
pyauthz_django/ # Django integration package
|
|
76
|
+
pyauthz_strawberry/ # Strawberry GraphQL integration package
|
|
77
|
+
tests/ # Core test suite
|
|
78
|
+
tests_fastapi/ # FastAPI integration tests
|
|
79
|
+
tests_flask/ # Flask integration tests
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for a detailed overview of how the engine works, the evaluation pipeline, and design decisions.
|
|
85
|
+
|
|
86
|
+
## Making Changes
|
|
87
|
+
|
|
88
|
+
1. **Read the code first.** Understand the existing patterns before modifying.
|
|
89
|
+
2. **Write tests.** Every new feature or bug fix should include tests. Tests go in `tests/test_<module>.py`.
|
|
90
|
+
3. **Keep it simple.** PyAuthz values simplicity over cleverness. If there's a simple way to do it, prefer that.
|
|
91
|
+
4. **Don't break the public API.** The `pyauthz.__init__` exports define the public API. Changes to these are breaking changes.
|
|
92
|
+
|
|
93
|
+
## Commit Messages
|
|
94
|
+
|
|
95
|
+
- Explain *why*, not just *what*
|
|
96
|
+
- One logical change per commit
|
|
97
|
+
- Use imperative mood: "Add cache invalidation" not "Added cache invalidation"
|
|
98
|
+
|
|
99
|
+
## Adding a New Combiner
|
|
100
|
+
|
|
101
|
+
1. Create the combiner function in `pyauthz/policies/combiners.py`
|
|
102
|
+
2. Register it in the `COMBINERS` dict
|
|
103
|
+
3. Add tests in `tests/test_combiners.py`
|
|
104
|
+
4. Update the engine docstring and README
|
|
105
|
+
|
|
106
|
+
## Adding a New Framework Integration
|
|
107
|
+
|
|
108
|
+
1. Create a new namespace package at the project root: `pyauthz_<framework>/`
|
|
109
|
+
2. Follow the patterns from `pyauthz_fastapi/` or `pyauthz_flask/`
|
|
110
|
+
3. Add tests in `tests_<framework>/`
|
|
111
|
+
4. Add the framework to dev dependencies in `pyproject.toml`
|
|
112
|
+
|
|
113
|
+
## Adding a New Storage Backend
|
|
114
|
+
|
|
115
|
+
1. Implement the `StorageBackend` protocol from `pyauthz/storage/base.py`
|
|
116
|
+
2. Add tests that verify thread safety and correctness
|
|
117
|
+
3. Add the dependency to optional extras in `pyproject.toml`
|
|
118
|
+
|
|
119
|
+
## Reporting Issues
|
|
120
|
+
|
|
121
|
+
- Use GitHub Issues for bugs and feature requests
|
|
122
|
+
- Include a minimal reproduction case for bugs
|
|
123
|
+
- Include Python version, OS, and PyAuthz version
|
pyauthz-0.9.0/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding any notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
Copyright 2026 Marci
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|