django-scoped-access 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.
Files changed (104) hide show
  1. django_scoped_access-0.1.0/.claude/settings.json +9 -0
  2. django_scoped_access-0.1.0/.claude/settings.local.json +11 -0
  3. django_scoped_access-0.1.0/.github/workflows/ci.yml +62 -0
  4. django_scoped_access-0.1.0/.github/workflows/publish.yml +25 -0
  5. django_scoped_access-0.1.0/.gitignore +12 -0
  6. django_scoped_access-0.1.0/.idea/.gitignore +10 -0
  7. django_scoped_access-0.1.0/.idea/amazonq.xml +11 -0
  8. django_scoped_access-0.1.0/.idea/auth-with-scoped-access.iml +18 -0
  9. django_scoped_access-0.1.0/.idea/inspectionProfiles/Project_Default.xml +13 -0
  10. django_scoped_access-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  11. django_scoped_access-0.1.0/.idea/material_theme_project_new.xml +10 -0
  12. django_scoped_access-0.1.0/.idea/misc.xml +6 -0
  13. django_scoped_access-0.1.0/.idea/modules.xml +8 -0
  14. django_scoped_access-0.1.0/.idea/vcs.xml +6 -0
  15. django_scoped_access-0.1.0/.idea/workspace.xml +71 -0
  16. django_scoped_access-0.1.0/.pre-commit-config.yaml +24 -0
  17. django_scoped_access-0.1.0/.readthedocs.yaml +10 -0
  18. django_scoped_access-0.1.0/AGENTS.md +1 -0
  19. django_scoped_access-0.1.0/CHANGELOG.md +33 -0
  20. django_scoped_access-0.1.0/LICENSE +21 -0
  21. django_scoped_access-0.1.0/PKG-INFO +266 -0
  22. django_scoped_access-0.1.0/README.md +234 -0
  23. django_scoped_access-0.1.0/SPEC.md +317 -0
  24. django_scoped_access-0.1.0/conformance/README.md +33 -0
  25. django_scoped_access-0.1.0/conformance/cases/access-summary.json +45 -0
  26. django_scoped_access-0.1.0/conformance/cases/coverage.json +89 -0
  27. django_scoped_access-0.1.0/conformance/cases/flat-rbac.json +41 -0
  28. django_scoped_access-0.1.0/conformance/cases/lifecycle.json +83 -0
  29. django_scoped_access-0.1.0/conformance/cases/reauth.json +45 -0
  30. django_scoped_access-0.1.0/conformance/cases/tenancy.json +65 -0
  31. django_scoped_access-0.1.0/conformance/cases/write-guard.json +60 -0
  32. django_scoped_access-0.1.0/docs/concepts.md +126 -0
  33. django_scoped_access-0.1.0/docs/configuration.md +134 -0
  34. django_scoped_access-0.1.0/docs/django-and-admin.md +114 -0
  35. django_scoped_access-0.1.0/docs/drf.md +117 -0
  36. django_scoped_access-0.1.0/docs/index.md +125 -0
  37. django_scoped_access-0.1.0/docs/lifecycle.md +162 -0
  38. django_scoped_access-0.1.0/docs/quickstart.md +235 -0
  39. django_scoped_access-0.1.0/docs/reauth.md +117 -0
  40. django_scoped_access-0.1.0/docs/requirements.txt +2 -0
  41. django_scoped_access-0.1.0/docs/security.md +104 -0
  42. django_scoped_access-0.1.0/docs/spec.md +317 -0
  43. django_scoped_access-0.1.0/docs/swappable-models.md +98 -0
  44. django_scoped_access-0.1.0/example/config/__init__.py +0 -0
  45. django_scoped_access-0.1.0/example/config/asgi.py +15 -0
  46. django_scoped_access-0.1.0/example/config/settings.py +145 -0
  47. django_scoped_access-0.1.0/example/config/urls.py +19 -0
  48. django_scoped_access-0.1.0/example/config/wsgi.py +15 -0
  49. django_scoped_access-0.1.0/example/helpdesk/__init__.py +0 -0
  50. django_scoped_access-0.1.0/example/helpdesk/admin.py +1 -0
  51. django_scoped_access-0.1.0/example/helpdesk/apps.py +11 -0
  52. django_scoped_access-0.1.0/example/helpdesk/migrations/0001_initial.py +52 -0
  53. django_scoped_access-0.1.0/example/helpdesk/migrations/__init__.py +0 -0
  54. django_scoped_access-0.1.0/example/helpdesk/models.py +42 -0
  55. django_scoped_access-0.1.0/example/helpdesk/serializers.py +10 -0
  56. django_scoped_access-0.1.0/example/helpdesk/tests.py +1 -0
  57. django_scoped_access-0.1.0/example/helpdesk/views.py +23 -0
  58. django_scoped_access-0.1.0/example/manage.py +23 -0
  59. django_scoped_access-0.1.0/example/seed.py +110 -0
  60. django_scoped_access-0.1.0/mkdocs.yml +76 -0
  61. django_scoped_access-0.1.0/pyproject.toml +120 -0
  62. django_scoped_access-0.1.0/scoped_access/__init__.py +41 -0
  63. django_scoped_access-0.1.0/scoped_access/apps.py +16 -0
  64. django_scoped_access-0.1.0/scoped_access/backends.py +36 -0
  65. django_scoped_access-0.1.0/scoped_access/cache.py +58 -0
  66. django_scoped_access-0.1.0/scoped_access/checks.py +145 -0
  67. django_scoped_access-0.1.0/scoped_access/conf.py +116 -0
  68. django_scoped_access-0.1.0/scoped_access/drf/__init__.py +22 -0
  69. django_scoped_access-0.1.0/scoped_access/drf/mixins.py +80 -0
  70. django_scoped_access-0.1.0/scoped_access/drf/permissions.py +67 -0
  71. django_scoped_access-0.1.0/scoped_access/drf/views.py +68 -0
  72. django_scoped_access-0.1.0/scoped_access/engine.py +496 -0
  73. django_scoped_access-0.1.0/scoped_access/exceptions.py +42 -0
  74. django_scoped_access-0.1.0/scoped_access/migrations/0001_initial.py +87 -0
  75. django_scoped_access-0.1.0/scoped_access/migrations/0002_global_assignment_uniqueness.py +32 -0
  76. django_scoped_access-0.1.0/scoped_access/migrations/0003_protect_assignment_history.py +22 -0
  77. django_scoped_access-0.1.0/scoped_access/migrations/__init__.py +0 -0
  78. django_scoped_access-0.1.0/scoped_access/models.py +562 -0
  79. django_scoped_access-0.1.0/scoped_access/mutations.py +35 -0
  80. django_scoped_access-0.1.0/scoped_access/reauth/__init__.py +10 -0
  81. django_scoped_access-0.1.0/scoped_access/reauth/receivers.py +42 -0
  82. django_scoped_access-0.1.0/scoped_access/reauth/service.py +115 -0
  83. django_scoped_access-0.1.0/scoped_access/reauth/verifiers.py +37 -0
  84. django_scoped_access-0.1.0/scoped_access/registry.py +109 -0
  85. django_scoped_access-0.1.0/scoped_access/role_permissions.py +36 -0
  86. django_scoped_access-0.1.0/scoped_access/services.py +72 -0
  87. django_scoped_access-0.1.0/scoped_access/signals.py +20 -0
  88. django_scoped_access-0.1.0/tests/__init__.py +0 -0
  89. django_scoped_access-0.1.0/tests/settings.py +22 -0
  90. django_scoped_access-0.1.0/tests/test_cache.py +76 -0
  91. django_scoped_access-0.1.0/tests/test_checks.py +140 -0
  92. django_scoped_access-0.1.0/tests/test_conformance.py +232 -0
  93. django_scoped_access-0.1.0/tests/test_drf.py +210 -0
  94. django_scoped_access-0.1.0/tests/test_lifecycle.py +296 -0
  95. django_scoped_access-0.1.0/tests/test_node_filtering.py +86 -0
  96. django_scoped_access-0.1.0/tests/test_orphan_references.py +143 -0
  97. django_scoped_access-0.1.0/tests/test_public_api.py +46 -0
  98. django_scoped_access-0.1.0/tests/test_reauth.py +60 -0
  99. django_scoped_access-0.1.0/tests/test_role_management.py +188 -0
  100. django_scoped_access-0.1.0/tests/testapp/__init__.py +0 -0
  101. django_scoped_access-0.1.0/tests/testapp/apps.py +7 -0
  102. django_scoped_access-0.1.0/tests/testapp/models.py +30 -0
  103. django_scoped_access-0.1.0/tests/urls.py +1 -0
  104. django_scoped_access-0.1.0/uv.lock +899 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run *)",
5
+ "Read(//tmp/**)",
6
+ "Bash(xargs -I{} echo {})"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(DJANGO_SETTINGS_MODULE=config.settings uv run python manage.py makemigrations helpdesk)",
5
+ "Bash(DJANGO_SETTINGS_MODULE=config.settings uv run *)",
6
+ "Bash(curl *)",
7
+ "Bash(python3 -m json.tool)",
8
+ "Bash(uv add *)"
9
+ ]
10
+ }
11
+ }
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ quality:
17
+ name: Quality and build
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v5
22
+ with:
23
+ python-version: "3.14"
24
+ enable-cache: true
25
+ - name: Install development tools
26
+ run: uv sync --group dev --group lint
27
+ - name: Lint
28
+ run: uv run --no-sync ruff check .
29
+ - name: Check formatting
30
+ run: uv run --no-sync ruff format --check scoped_access tests example
31
+ - name: Check migrations
32
+ run: DJANGO_SETTINGS_MODULE=tests.settings uv run --no-sync python -m django makemigrations --check --dry-run
33
+ - name: Build package
34
+ run: uv build
35
+
36
+ test:
37
+ name: Python ${{ matrix.python-version }} / Django ${{ matrix.django-version }}
38
+ runs-on: ubuntu-latest
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ include:
43
+ - python-version: "3.11"
44
+ django-version: "4.2"
45
+ - python-version: "3.14"
46
+ django-version: "5.2"
47
+ - python-version: "3.14"
48
+ django-version: "6.0"
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+ - uses: astral-sh/setup-uv@v5
52
+ with:
53
+ python-version: ${{ matrix.python-version }}
54
+ enable-cache: true
55
+ - name: Install with pinned Django
56
+ run: |
57
+ uv sync --group dev
58
+ uv pip install "Django~=${{ matrix.django-version }}.0"
59
+ - name: Django system checks on the test project
60
+ run: DJANGO_SETTINGS_MODULE=tests.settings uv run --no-sync python -m django check
61
+ - name: Conformance + unit tests
62
+ run: uv run --no-sync pytest -q
@@ -0,0 +1,25 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ pypi-publish:
13
+ name: Build and publish to PyPI
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ id-token: write # Obligatoire pour le Trusted Publishing PyPI (OIDC)
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v5
20
+ with:
21
+ enable-cache: true
22
+ - name: Build package distributions
23
+ run: uv build
24
+ - name: Publish package distribution to PyPI
25
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
10
+ db.sqlite3
11
+ .DS_Store
12
+ site/
@@ -0,0 +1,10 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Ignored default folder with query files
7
+ /queries/
8
+ # Datasource local storage ignored files
9
+ /dataSources/
10
+ /dataSources.local.xml
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="qAccountSettings">
4
+ <option name="activeRegion" value="us-east-1" />
5
+ <option name="recentlyUsedRegions">
6
+ <list>
7
+ <option value="us-east-1" />
8
+ </list>
9
+ </option>
10
+ </component>
11
+ </project>
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <sourceFolder url="file://$MODULE_DIR$/example" isTestSource="false" />
6
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
7
+ </content>
8
+ <orderEntry type="jdk" jdkName="uv (auth-with-scoped-access)" jdkType="Python SDK" />
9
+ <orderEntry type="sourceFolder" forTests="false" />
10
+ </component>
11
+ <component name="PyDocumentationSettings">
12
+ <option name="format" value="PLAIN" />
13
+ <option name="myDocStringFormat" value="Plain" />
14
+ </component>
15
+ <component name="TestRunnerService">
16
+ <option name="PROJECT_TEST_RUNNER" value="py.test" />
17
+ </component>
18
+ </module>
@@ -0,0 +1,13 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
6
+ <option name="ignoredPackages">
7
+ <list>
8
+ <option value="zenml" />
9
+ </list>
10
+ </option>
11
+ </inspection_tool>
12
+ </profile>
13
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MaterialThemeProjectNewConfig">
4
+ <option name="metadata">
5
+ <MTProjectMetadataState>
6
+ <option name="userId" value="6c267aca:19f218231cd:-7fc4" />
7
+ </MTProjectMetadataState>
8
+ </option>
9
+ </component>
10
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="uv (auth-with-scoped-access)" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/auth-with-scoped-access.iml" filepath="$PROJECT_DIR$/.idea/auth-with-scoped-access.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,71 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ChangeListManager">
4
+ <list default="true" id="e02d799e-46e2-451b-acc9-5f9c6e9efd2d" name="Changes" comment="">
5
+ <change afterPath="$PROJECT_DIR$/example/helpdesk/serializers.py" afterDir="false" />
6
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
7
+ <change beforePath="$PROJECT_DIR$/scoped_access/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/scoped_access/models.py" afterDir="false" />
8
+ <change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
9
+ </list>
10
+ <option name="SHOW_DIALOG" value="false" />
11
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
12
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
13
+ <option name="LAST_RESOLUTION" value="IGNORE" />
14
+ </component>
15
+ <component name="FileTemplateManagerImpl">
16
+ <option name="RECENT_TEMPLATES">
17
+ <list>
18
+ <option value="Python Script" />
19
+ </list>
20
+ </option>
21
+ </component>
22
+ <component name="Git.Settings">
23
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
24
+ </component>
25
+ <component name="ProjectColorInfo"><![CDATA[{
26
+ "associatedIndex": 1,
27
+ "fromUser": false
28
+ }]]></component>
29
+ <component name="ProjectId" id="3G4hOWJ7c1UBLfEF6SFb95bXADr" />
30
+ <component name="ProjectViewState">
31
+ <option name="hideEmptyMiddlePackages" value="true" />
32
+ <option name="showLibraryContents" value="true" />
33
+ </component>
34
+ <component name="PropertiesComponent"><![CDATA[{
35
+ "keyToString": {
36
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
37
+ "RunOnceActivity.ShowReadmeOnStart": "true",
38
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
39
+ "RunOnceActivity.git.unshallow": "true",
40
+ "RunOnceActivity.typescript.service.memoryLimit.init": "true",
41
+ "codeWithMe.voiceChat.enabledByDefault": "false",
42
+ "git-widget-placeholder": "main",
43
+ "last_opened_file_path": "/Users/pc/Code/Projets/perso/auth-with-scoped-access",
44
+ "node.js.detected.package.eslint": "true",
45
+ "node.js.selected.package.eslint": "(autodetect)",
46
+ "nodejs_package_manager_path": "npm",
47
+ "vue.rearranger.settings.migration": "true"
48
+ }
49
+ }]]></component>
50
+ <component name="SharedIndexes">
51
+ <attachedChunks>
52
+ <set>
53
+ <option value="bundled-python-sdk-b63d5a1f7c97-b61e75351b1f-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-261.22158.340" />
54
+ </set>
55
+ </attachedChunks>
56
+ </component>
57
+ <component name="TaskManager">
58
+ <task active="true" id="Default" summary="Default task">
59
+ <changelist id="e02d799e-46e2-451b-acc9-5f9c6e9efd2d" name="Changes" comment="" />
60
+ <created>1783236716432</created>
61
+ <option name="number" value="Default" />
62
+ <option name="presentableId" value="Default" />
63
+ <updated>1783236716432</updated>
64
+ <workItem from="1783236717604" duration="5960000" />
65
+ </task>
66
+ <servers />
67
+ </component>
68
+ <component name="TypeScriptGeneratedFilesManager">
69
+ <option name="version" value="3" />
70
+ </component>
71
+ </project>
@@ -0,0 +1,24 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-merge-conflict
9
+ - id: check-added-large-files
10
+
11
+ - repo: local
12
+ hooks:
13
+ - id: ruff
14
+ name: ruff (uv)
15
+ entry: uv run --group lint -- ruff check --preview --force-exclude
16
+ language: system
17
+ types_or: [python, pyi]
18
+ exclude: '^(migrations/|example/migrations/)'
19
+ - id: ruff-format
20
+ name: ruff-format (uv)
21
+ entry: uv run --group lint -- ruff format --preview --force-exclude
22
+ language: system
23
+ types_or: [python, pyi]
24
+ exclude: '^(migrations/|example/migrations/)'
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ build:
3
+ os: ubuntu-22.04
4
+ tools:
5
+ python: "3.11"
6
+ python:
7
+ install:
8
+ - requirements: docs/requirements.txt
9
+ mkdocs:
10
+ configuration: mkdocs.yml
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,33 @@
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
+ ---
9
+
10
+ ## [0.1.0] - 2026-08-20
11
+
12
+ ### Added
13
+ - Reference implementation of the **Scoped Access Specification** (`SPEC.md`).
14
+ - **Hierarchy Engine**: Declarative hierarchy configuration supporting arbitrary tree depth, parent accessors, and model discriminators.
15
+ - **Resource Registry**: Declarative anchoring mechanism connecting domain models to hierarchy nodes.
16
+ - **RBAC & Multi-Tenant Scoping**: Support for universal system roles and tenant-owned custom roles.
17
+ - **Anti-Escalation (Rule R5)**: Strict checks preventing role managers from granting permissions they do not effectively hold.
18
+ - **Assignment Lifecycle**: State machine (`ACTIVE` โ‡„ `SUSPENDED` โ†’ `REVOKED`) with immutable audit trails and temporal validity (`valid_from` / `valid_until`).
19
+ - **Step-Up Re-Authentication (ReAuth)**: Single-use, time-limited tokens with pluggable verifiers (password, PIN, TOTP, WebAuthn) and automatic invalidation on password changes.
20
+ - **Django REST Framework (DRF) Integration**:
21
+ - `ScopedModelPermission`: Method-to-permission mapping with strict read-access enforcement.
22
+ - `ScopeObjectPermission`: Object-level scope verification.
23
+ - `ScopeQuerySetMixin`: Database-level SQL filtering for collection endpoints.
24
+ - `ScopeWriteGuardMixin`: Target-scope validation on create/update mutations to prevent scope injection.
25
+ - `RequireReAuth`: Gated action permission requiring a valid `X-ReAuth-Token`.
26
+ - `MeAccessView`: Standard `GET /me/access/` introspection endpoint.
27
+ - `ReAuthView`: Standard `POST /auth/reauth/` credential exchange view.
28
+ - **Django Authentication Backend**: `ScopedPermissionBackend` integrating with `user.has_perm()` and Django Admin.
29
+ - **Per-Request Caching**: ContextVar-backed `ScopedAccessCacheMiddleware` with in-request lifecycle invalidation.
30
+ - **Swappable Models**: Support for customizing `Role` (`SCOPED_ACCESS_ROLE_MODEL`) and `ScopeAssignment` (`SCOPED_ACCESS_ASSIGNMENT_MODEL`).
31
+ - **Django System Checks**: Comprehensive startup validation of hierarchy and settings consistency.
32
+ - **Language-Agnostic Conformance Test Suite**: 100% test pass rate across 102 test cases.
33
+ - **Complete Documentation**: Full Material for MkDocs suite with guides, tutorials, threat model, and API references.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joel Edmond Nguemeta
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,266 @@
1
+ Metadata-Version: 2.5
2
+ Name: django-scoped-access
3
+ Version: 0.1.0
4
+ Summary: RBAC + hierarchical scope-based authorization (ABAC) + step-up re-authentication for Django. Reference implementation of the Scoped Access specification.
5
+ Project-URL: Homepage, https://github.com/joelnguemeta/django-auth-with-scoped-access
6
+ Project-URL: Documentation, https://django-scoped-access.readthedocs.io/
7
+ Project-URL: Repository, https://github.com/joelnguemeta/django-auth-with-scoped-access
8
+ Project-URL: Issues, https://github.com/joelnguemeta/django-auth-with-scoped-access/issues
9
+ Project-URL: Changelog, https://github.com/joelnguemeta/django-auth-with-scoped-access/blob/main/CHANGELOG.md
10
+ Author: Joel Edmond Nguemeta
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: abac,authorization,django,hierarchical-roles,multi-tenancy,permissions,rbac,re-authentication,security,step-up-auth
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: Django
16
+ Classifier: Framework :: Django :: 4.2
17
+ Classifier: Framework :: Django :: 5.0
18
+ Classifier: Framework :: Django :: 5.1
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: django>=4.2
29
+ Provides-Extra: drf
30
+ Requires-Dist: djangorestframework>=3.14; extra == 'drf'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # django-scoped-access
34
+
35
+ <p align="center">
36
+ <a href="https://pypi.org/project/django-scoped-access/"><img src="https://img.shields.io/pypi/v/django-scoped-access.svg?color=blue" alt="PyPI version"></a>
37
+ <a href="https://pypi.org/project/django-scoped-access/"><img src="https://img.shields.io/pypi/pyversions/django-scoped-access.svg" alt="Python Versions"></a>
38
+ <a href="https://pypi.org/project/django-scoped-access/"><img src="https://img.shields.io/pypi/djversions/django-scoped-access.svg" alt="Django Versions"></a>
39
+ <a href="https://django-scoped-access.readthedocs.io/"><img src="https://img.shields.io/readthedocs/django-scoped-access.svg" alt="Documentation Status"></a>
40
+ <a href="https://github.com/joelnguemeta/django-auth-with-scoped-access/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License: MIT"></a>
41
+ </p>
42
+
43
+ <p align="center">
44
+ <strong>Hierarchical Scoped Authorization (ABAC) + RBAC + Temporal Lifecycle + Step-Up Re-Authentication for Django & DRF.</strong><br>
45
+ <em>Reference implementation of the <a href="SPEC.md">Scoped Access Specification</a>.</em>
46
+ </p>
47
+
48
+ ---
49
+
50
+ ## ๐Ÿ“– Overview
51
+
52
+ **django-scoped-access** brings fine-grained, hierarchical multi-tenant access control to Django without polluting your business models or hardcoding organizational trees into database schemas.
53
+
54
+ Whether building a single-tenant app (flat RBAC), a multi-tenant B2B SaaS (Organization โ†’ Team), or a complex organizational system (National โ†’ Regional โ†’ District โ†’ Facility โ†’ Department), **the hierarchy is pure configuration**.
55
+
56
+ ```
57
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
58
+ โ”‚ NATIONAL โ”‚ (Root level)
59
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
60
+ โ”‚
61
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
62
+ โ”‚ REGIONAL โ”‚
63
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
64
+ โ”‚
65
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
66
+ โ”‚ DISTRICT โ”‚
67
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
68
+ โ”‚
69
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
70
+ โ”‚ FACILITY โ”‚ (Can manage custom roles)
71
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
72
+ ```
73
+
74
+ ---
75
+
76
+ ## โœจ Key Features
77
+
78
+ - ๐ŸŒฒ **Hierarchy as Configuration, Not Schema**: Configure any arbitrary tree depth or degrade to standard RBAC at depth 0.
79
+ - ๐ŸŽฏ **Domain Agnostic**: The engine never owns your models. Hierarchy nodes and role owners link via generic relations (`GenericForeignKey`).
80
+ - ๐Ÿ”’ **Inclusive Downward Scope Coverage**: A role grant at `REGIONAL` covers all descendant nodes (Districts, Facilities, Units) in that subtree, and never upwards.
81
+ - ๐Ÿ›ก๏ธ **Anti-Escalation Protection (Rule R5)**: Tenant admins can manage custom roles within their scope, but can **never** grant permissions they do not possess themselves.
82
+ - โฑ๏ธ **Temporal Validity & Audit Trail**: Assignments support `valid_from` / `valid_until` windows and status transitions (`ACTIVE` โ‡„ `SUSPENDED` โ†’ `REVOKED`). Assignments are **never hard-deleted**.
83
+ - ๐Ÿ” **Step-Up Re-Authentication (ReAuth)**: Require fresh, single-use proof of identity (passwords, PIN, WebAuthn, TOTP) for high-risk actions. Superusers are not exempt.
84
+ - โšก **Database-Level Query Filtering**: SQL-level filtering (`scope_filter_q()`) for collection viewsโ€”no in-memory Python iteration.
85
+ - ๐Ÿš€ **Full Django REST Framework (DRF) Integration**: Drop-in permissions (`ScopedModelPermission`, `ScopeObjectPermission`), query mixins (`ScopeQuerySetMixin`), write guards (`ScopeWriteGuardMixin`), and introspection endpoints (`GET /me/access/`).
86
+
87
+ ---
88
+
89
+ ## โšก Quickstart (5 Minutes)
90
+
91
+ ### 1. Installation
92
+
93
+ ```bash
94
+ # With pip
95
+ pip install "django-scoped-access[drf]"
96
+
97
+ # With uv
98
+ uv add "django-scoped-access[drf]"
99
+ ```
100
+
101
+ ### 2. Configuration (`settings.py`)
102
+
103
+ ```python
104
+ INSTALLED_APPS = [
105
+ ...,
106
+ "scoped_access",
107
+ "rest_framework",
108
+ ]
109
+
110
+ AUTHENTICATION_BACKENDS = [
111
+ "django.contrib.auth.backends.ModelBackend",
112
+ "scoped_access.backends.ScopedPermissionBackend",
113
+ ]
114
+
115
+ SCOPED_ACCESS = {
116
+ "HIERARCHY": [
117
+ {"level": "GLOBAL"},
118
+ {"level": "ORGANIZATION", "model": "accounts.Organization"},
119
+ {"level": "TEAM", "model": "accounts.Team", "parent": "organization"},
120
+ ],
121
+ "ROLE_OWNER_LEVELS": ["ORGANIZATION"],
122
+ "GRANTABLE_PERMISSIONS": "self",
123
+ "REAUTH": {"ENABLED": True, "TTL": 300},
124
+ }
125
+ ```
126
+
127
+ Apply database migrations:
128
+
129
+ ```bash
130
+ python manage.py migrate
131
+ ```
132
+
133
+ ### 3. Register Resource Anchors
134
+
135
+ Declare the path from your business models to their hierarchy node:
136
+
137
+ ```python
138
+ # helpdesk/apps.py
139
+ from django.apps import AppConfig
140
+
141
+ class HelpdeskConfig(AppConfig):
142
+ name = "helpdesk"
143
+
144
+ def ready(self):
145
+ from scoped_access import register
146
+ from helpdesk.models import Ticket
147
+
148
+ # A Ticket is anchored to a Team
149
+ register(Ticket, anchor="team")
150
+ ```
151
+
152
+ ### 4. Create Roles & Grant Assignments
153
+
154
+ ```python
155
+ from scoped_access import RoleService, ScopeAssignment
156
+ from helpdesk.models import Team
157
+
158
+ support_team = Team.objects.get(name="Support")
159
+
160
+ # 1. Create a System Role
161
+ agent_role = RoleService.create(
162
+ name="Support Agent",
163
+ permissions=[view_ticket_perm, change_ticket_perm],
164
+ by=admin_user,
165
+ )
166
+
167
+ # 2. Grant the role to Alice at the Team scope
168
+ ScopeAssignment.objects.grant(
169
+ user=alice,
170
+ role=agent_role,
171
+ scope=support_team,
172
+ by=admin_user,
173
+ )
174
+ ```
175
+
176
+ ### 5. Protect DRF Endpoints
177
+
178
+ ```python
179
+ # helpdesk/views.py
180
+ from rest_framework.viewsets import ModelViewSet
181
+ from scoped_access.drf import (
182
+ RequireReAuth,
183
+ ScopedModelPermission,
184
+ ScopeObjectPermission,
185
+ ScopeQuerySetMixin,
186
+ ScopeWriteGuardMixin,
187
+ )
188
+
189
+ class TicketViewSet(ScopeWriteGuardMixin, ScopeQuerySetMixin, ModelViewSet):
190
+ queryset = Ticket.objects.all()
191
+ serializer_class = TicketSerializer
192
+ permission_classes = [ScopedModelPermission, ScopeObjectPermission]
193
+
194
+ def get_permissions(self):
195
+ permissions = super().get_permissions()
196
+ if self.action == "destroy":
197
+ # Deleting tickets requires Step-Up Re-Authentication
198
+ permissions.append(RequireReAuth())
199
+ return permissions
200
+ ```
201
+
202
+ ---
203
+
204
+ ## ๐Ÿ” Introspection Endpoint (`GET /me/access/`)
205
+
206
+ Wire the standard introspection and reauth views in `urls.py`:
207
+
208
+ ```python
209
+ from django.urls import path
210
+ from scoped_access.drf import MeAccessView, ReAuthView
211
+
212
+ urlpatterns = [
213
+ path("api/me/access/", MeAccessView.as_view(), name="me-access"),
214
+ path("api/auth/reauth/", ReAuthView.as_view(), name="reauth"),
215
+ ]
216
+ ```
217
+
218
+ Example JSON output from `GET /api/me/access/`:
219
+
220
+ ```json
221
+ {
222
+ "principal": {"id": "42", "superuser": false, "active": true},
223
+ "permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"],
224
+ "assignments": [
225
+ {
226
+ "role": {"id": "7", "name": "Support Agent", "system": true},
227
+ "level": "TEAM",
228
+ "scope": {"id": "12", "label": "Customer Support โ€” Acme Corp"},
229
+ "status": "ACTIVE",
230
+ "valid_until": null,
231
+ "permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"]
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## ๐Ÿงช Testing & Conformance
240
+
241
+ Django Scoped Access is verified against a language-agnostic test suite defined in [SPEC.md](SPEC.md) and [`conformance/`](conformance/):
242
+
243
+ ```bash
244
+ uv run pytest
245
+ ```
246
+
247
+ ---
248
+
249
+ ## ๐Ÿ“š Documentation
250
+
251
+ Complete documentation is available at [https://django-scoped-access.readthedocs.io/](https://django-scoped-access.readthedocs.io/):
252
+
253
+ - [Quickstart Guide](https://django-scoped-access.readthedocs.io/quickstart/)
254
+ - [Core Concepts & Architecture](https://django-scoped-access.readthedocs.io/concepts/)
255
+ - [Configuration Reference](https://django-scoped-access.readthedocs.io/configuration/)
256
+ - [Django REST Framework (DRF) Integration](https://django-scoped-access.readthedocs.io/drf/)
257
+ - [Step-Up Re-Authentication (ReAuth)](https://django-scoped-access.readthedocs.io/reauth/)
258
+ - [Lifecycle & Auditability](https://django-scoped-access.readthedocs.io/lifecycle/)
259
+ - [Security Guide & Threat Model](https://django-scoped-access.readthedocs.io/security/)
260
+ - [Formal Specification (SPEC)](https://django-scoped-access.readthedocs.io/spec/)
261
+
262
+ ---
263
+
264
+ ## ๐Ÿ“„ License
265
+
266
+ This project is licensed under the terms of the [MIT License](LICENSE).