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.
- django_scoped_access-0.1.0/.claude/settings.json +9 -0
- django_scoped_access-0.1.0/.claude/settings.local.json +11 -0
- django_scoped_access-0.1.0/.github/workflows/ci.yml +62 -0
- django_scoped_access-0.1.0/.github/workflows/publish.yml +25 -0
- django_scoped_access-0.1.0/.gitignore +12 -0
- django_scoped_access-0.1.0/.idea/.gitignore +10 -0
- django_scoped_access-0.1.0/.idea/amazonq.xml +11 -0
- django_scoped_access-0.1.0/.idea/auth-with-scoped-access.iml +18 -0
- django_scoped_access-0.1.0/.idea/inspectionProfiles/Project_Default.xml +13 -0
- django_scoped_access-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- django_scoped_access-0.1.0/.idea/material_theme_project_new.xml +10 -0
- django_scoped_access-0.1.0/.idea/misc.xml +6 -0
- django_scoped_access-0.1.0/.idea/modules.xml +8 -0
- django_scoped_access-0.1.0/.idea/vcs.xml +6 -0
- django_scoped_access-0.1.0/.idea/workspace.xml +71 -0
- django_scoped_access-0.1.0/.pre-commit-config.yaml +24 -0
- django_scoped_access-0.1.0/.readthedocs.yaml +10 -0
- django_scoped_access-0.1.0/AGENTS.md +1 -0
- django_scoped_access-0.1.0/CHANGELOG.md +33 -0
- django_scoped_access-0.1.0/LICENSE +21 -0
- django_scoped_access-0.1.0/PKG-INFO +266 -0
- django_scoped_access-0.1.0/README.md +234 -0
- django_scoped_access-0.1.0/SPEC.md +317 -0
- django_scoped_access-0.1.0/conformance/README.md +33 -0
- django_scoped_access-0.1.0/conformance/cases/access-summary.json +45 -0
- django_scoped_access-0.1.0/conformance/cases/coverage.json +89 -0
- django_scoped_access-0.1.0/conformance/cases/flat-rbac.json +41 -0
- django_scoped_access-0.1.0/conformance/cases/lifecycle.json +83 -0
- django_scoped_access-0.1.0/conformance/cases/reauth.json +45 -0
- django_scoped_access-0.1.0/conformance/cases/tenancy.json +65 -0
- django_scoped_access-0.1.0/conformance/cases/write-guard.json +60 -0
- django_scoped_access-0.1.0/docs/concepts.md +126 -0
- django_scoped_access-0.1.0/docs/configuration.md +134 -0
- django_scoped_access-0.1.0/docs/django-and-admin.md +114 -0
- django_scoped_access-0.1.0/docs/drf.md +117 -0
- django_scoped_access-0.1.0/docs/index.md +125 -0
- django_scoped_access-0.1.0/docs/lifecycle.md +162 -0
- django_scoped_access-0.1.0/docs/quickstart.md +235 -0
- django_scoped_access-0.1.0/docs/reauth.md +117 -0
- django_scoped_access-0.1.0/docs/requirements.txt +2 -0
- django_scoped_access-0.1.0/docs/security.md +104 -0
- django_scoped_access-0.1.0/docs/spec.md +317 -0
- django_scoped_access-0.1.0/docs/swappable-models.md +98 -0
- django_scoped_access-0.1.0/example/config/__init__.py +0 -0
- django_scoped_access-0.1.0/example/config/asgi.py +15 -0
- django_scoped_access-0.1.0/example/config/settings.py +145 -0
- django_scoped_access-0.1.0/example/config/urls.py +19 -0
- django_scoped_access-0.1.0/example/config/wsgi.py +15 -0
- django_scoped_access-0.1.0/example/helpdesk/__init__.py +0 -0
- django_scoped_access-0.1.0/example/helpdesk/admin.py +1 -0
- django_scoped_access-0.1.0/example/helpdesk/apps.py +11 -0
- django_scoped_access-0.1.0/example/helpdesk/migrations/0001_initial.py +52 -0
- django_scoped_access-0.1.0/example/helpdesk/migrations/__init__.py +0 -0
- django_scoped_access-0.1.0/example/helpdesk/models.py +42 -0
- django_scoped_access-0.1.0/example/helpdesk/serializers.py +10 -0
- django_scoped_access-0.1.0/example/helpdesk/tests.py +1 -0
- django_scoped_access-0.1.0/example/helpdesk/views.py +23 -0
- django_scoped_access-0.1.0/example/manage.py +23 -0
- django_scoped_access-0.1.0/example/seed.py +110 -0
- django_scoped_access-0.1.0/mkdocs.yml +76 -0
- django_scoped_access-0.1.0/pyproject.toml +120 -0
- django_scoped_access-0.1.0/scoped_access/__init__.py +41 -0
- django_scoped_access-0.1.0/scoped_access/apps.py +16 -0
- django_scoped_access-0.1.0/scoped_access/backends.py +36 -0
- django_scoped_access-0.1.0/scoped_access/cache.py +58 -0
- django_scoped_access-0.1.0/scoped_access/checks.py +145 -0
- django_scoped_access-0.1.0/scoped_access/conf.py +116 -0
- django_scoped_access-0.1.0/scoped_access/drf/__init__.py +22 -0
- django_scoped_access-0.1.0/scoped_access/drf/mixins.py +80 -0
- django_scoped_access-0.1.0/scoped_access/drf/permissions.py +67 -0
- django_scoped_access-0.1.0/scoped_access/drf/views.py +68 -0
- django_scoped_access-0.1.0/scoped_access/engine.py +496 -0
- django_scoped_access-0.1.0/scoped_access/exceptions.py +42 -0
- django_scoped_access-0.1.0/scoped_access/migrations/0001_initial.py +87 -0
- django_scoped_access-0.1.0/scoped_access/migrations/0002_global_assignment_uniqueness.py +32 -0
- django_scoped_access-0.1.0/scoped_access/migrations/0003_protect_assignment_history.py +22 -0
- django_scoped_access-0.1.0/scoped_access/migrations/__init__.py +0 -0
- django_scoped_access-0.1.0/scoped_access/models.py +562 -0
- django_scoped_access-0.1.0/scoped_access/mutations.py +35 -0
- django_scoped_access-0.1.0/scoped_access/reauth/__init__.py +10 -0
- django_scoped_access-0.1.0/scoped_access/reauth/receivers.py +42 -0
- django_scoped_access-0.1.0/scoped_access/reauth/service.py +115 -0
- django_scoped_access-0.1.0/scoped_access/reauth/verifiers.py +37 -0
- django_scoped_access-0.1.0/scoped_access/registry.py +109 -0
- django_scoped_access-0.1.0/scoped_access/role_permissions.py +36 -0
- django_scoped_access-0.1.0/scoped_access/services.py +72 -0
- django_scoped_access-0.1.0/scoped_access/signals.py +20 -0
- django_scoped_access-0.1.0/tests/__init__.py +0 -0
- django_scoped_access-0.1.0/tests/settings.py +22 -0
- django_scoped_access-0.1.0/tests/test_cache.py +76 -0
- django_scoped_access-0.1.0/tests/test_checks.py +140 -0
- django_scoped_access-0.1.0/tests/test_conformance.py +232 -0
- django_scoped_access-0.1.0/tests/test_drf.py +210 -0
- django_scoped_access-0.1.0/tests/test_lifecycle.py +296 -0
- django_scoped_access-0.1.0/tests/test_node_filtering.py +86 -0
- django_scoped_access-0.1.0/tests/test_orphan_references.py +143 -0
- django_scoped_access-0.1.0/tests/test_public_api.py +46 -0
- django_scoped_access-0.1.0/tests/test_reauth.py +60 -0
- django_scoped_access-0.1.0/tests/test_role_management.py +188 -0
- django_scoped_access-0.1.0/tests/testapp/__init__.py +0 -0
- django_scoped_access-0.1.0/tests/testapp/apps.py +7 -0
- django_scoped_access-0.1.0/tests/testapp/models.py +30 -0
- django_scoped_access-0.1.0/tests/urls.py +1 -0
- django_scoped_access-0.1.0/uv.lock +899 -0
|
@@ -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,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,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,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,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 @@
|
|
|
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).
|