navigator-auth 0.4.7__tar.gz → 0.16.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.
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/MANIFEST.in +1 -1
- navigator_auth-0.16.0/Makefile +89 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/PKG-INFO +33 -16
- navigator_auth-0.16.0/docs/requirements-dev.txt +21 -0
- navigator_auth-0.16.0/navigator_auth/__init__.py +14 -0
- navigator_auth-0.16.0/navigator_auth/abac/__init__.py +7 -0
- navigator_auth-0.16.0/navigator_auth/abac/audit.py +64 -0
- navigator_auth-0.16.0/navigator_auth/abac/context.py +111 -0
- navigator_auth-0.16.0/navigator_auth/abac/decorators.py +203 -0
- navigator_auth-0.16.0/navigator_auth/abac/errors.py +94 -0
- navigator_auth-0.16.0/navigator_auth/abac/guardian.py +299 -0
- navigator_auth-0.16.0/navigator_auth/abac/middleware.py +53 -0
- navigator_auth-0.16.0/navigator_auth/abac/pdp.py +375 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/__init__.py +22 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/abstract.py +223 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/environment.py +24 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/evaluator.py +481 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/file.py +99 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/obj.py +241 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/policy.py +151 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/resource_policy.py +221 -0
- navigator_auth-0.16.0/navigator_auth/abac/policies/resources.py +324 -0
- navigator_auth-0.16.0/navigator_auth/abac/policyhandler.py +13 -0
- navigator_auth-0.16.0/navigator_auth/abac/storages/abstract.py +22 -0
- navigator_auth-0.16.0/navigator_auth/abac/storages/db.py +92 -0
- navigator_auth-0.16.0/navigator_auth/abac/storages/pg.py +68 -0
- navigator_auth-0.16.0/navigator_auth/auth.py +809 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/__init__.py +6 -1
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/abstract.py +3 -1
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/allow_hosts.py +3 -5
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/hosts.py +3 -3
- navigator_auth-0.16.0/navigator_auth/authorizations/useragent.py +20 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/__init__.py +4 -1
- navigator_auth-0.16.0/navigator_auth/backends/abstract.py +500 -0
- navigator_auth-0.16.0/navigator_auth/backends/adfs.py +343 -0
- navigator_auth-0.16.0/navigator_auth/backends/api.py +279 -0
- navigator_auth-0.16.0/navigator_auth/backends/attributes/__init__.py +4 -0
- navigator_auth-0.16.0/navigator_auth/backends/attributes/abstract.py +33 -0
- navigator_auth-0.16.0/navigator_auth/backends/attributes/internal.py +13 -0
- navigator_auth-0.16.0/navigator_auth/backends/azure.py +472 -0
- navigator_auth-0.16.0/navigator_auth/backends/basic.py +273 -0
- navigator_auth-0.16.0/navigator_auth/backends/django.py +278 -0
- navigator_auth-0.16.0/navigator_auth/backends/external.py +573 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/github.py +37 -32
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/google.py +32 -37
- navigator_auth-0.16.0/navigator_auth/backends/idp/__init__.py +444 -0
- navigator_auth-0.16.0/navigator_auth/backends/idp/code.py +60 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/jwksutils.py +43 -41
- navigator_auth-0.16.0/navigator_auth/backends/noauth.py +79 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/oauth.py +9 -7
- navigator_auth-0.16.0/navigator_auth/backends/oauth2/__init__.py +4 -0
- navigator_auth-0.16.0/navigator_auth/backends/oauth2/backend.py +388 -0
- navigator_auth-0.16.0/navigator_auth/backends/oauth2/models.py +99 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/okta.py +41 -40
- navigator_auth-0.16.0/navigator_auth/backends/token.py +238 -0
- navigator_auth-0.16.0/navigator_auth/backends/troc.py +289 -0
- navigator_auth-0.16.0/navigator_auth/conf.py +379 -0
- navigator_auth-0.16.0/navigator_auth/decorators.py +519 -0
- navigator_auth-0.16.0/navigator_auth/exceptions.c +17620 -0
- navigator_auth-0.16.0/navigator_auth/exceptions.pxd +30 -0
- navigator_auth-0.16.0/navigator_auth/exceptions.pyx +62 -0
- navigator_auth-0.16.0/navigator_auth/handlers/__init__.py +64 -0
- navigator_auth-0.16.0/navigator_auth/handlers/groups.py +31 -0
- navigator_auth-0.16.0/navigator_auth/handlers/handler.py +10 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/handlers/model.py +197 -203
- navigator_auth-0.16.0/navigator_auth/handlers/partners.py +68 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/handlers/permissions.py +5 -4
- navigator_auth-0.16.0/navigator_auth/handlers/userattrs.py +60 -0
- navigator_auth-0.16.0/navigator_auth/handlers/users/__init__.py +10 -0
- navigator_auth-0.16.0/navigator_auth/handlers/users/passwd.py +39 -0
- navigator_auth-0.16.0/navigator_auth/handlers/users/session.py +277 -0
- navigator_auth-0.16.0/navigator_auth/handlers/users/user.py +58 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/identities.py +26 -15
- navigator_auth-0.16.0/navigator_auth/libs/__init__.py +12 -0
- navigator_auth-0.16.0/navigator_auth/libs/cipher.cpp +12722 -0
- navigator_auth-0.16.0/navigator_auth/libs/cipher.pyx +83 -0
- navigator_auth-0.16.0/navigator_auth/libs/json.cpp +13040 -0
- navigator_auth-0.16.0/navigator_auth/libs/json.pyx +87 -0
- navigator_auth-0.16.0/navigator_auth/libs/parser.cpp +11265 -0
- navigator_auth-0.16.0/navigator_auth/libs/parser.pyx +78 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/__init__.py +1 -6
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/abstract.py +27 -30
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/apikey.py +19 -28
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/django.py +11 -20
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/jwt.py +16 -29
- navigator_auth-0.16.0/navigator_auth/middlewares/security.py +42 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/token.py +10 -17
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/troc.py +12 -29
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/models.py +136 -175
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/responses.py +17 -21
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/abstract.py +10 -23
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/postgres.py +8 -17
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/redis.py +5 -14
- navigator_auth-0.16.0/navigator_auth/templates.py +189 -0
- navigator_auth-0.16.0/navigator_auth/uv.py +11 -0
- navigator_auth-0.16.0/navigator_auth/version.py +21 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/PKG-INFO +33 -16
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/SOURCES.txt +55 -10
- navigator_auth-0.16.0/navigator_auth.egg-info/requires.txt +18 -0
- navigator_auth-0.16.0/pyproject.toml +111 -0
- navigator_auth-0.16.0/setup.py +50 -0
- navigator_auth-0.16.0/tests/test_allowed.py +295 -0
- navigator_auth-0.16.0/tests/test_login.py +37 -0
- navigator_auth-0.16.0/tests/test_policy.py +85 -0
- navigator_auth-0.16.0/tests/test_policy_conditions.py +76 -0
- navigator_auth-0.16.0/tests/test_policy_evaluation.py +106 -0
- navigator_auth-0.16.0/tests/test_policy_object.py +126 -0
- navigator-auth-0.4.7/Makefile +0 -35
- navigator-auth-0.4.7/docs/requirements-dev.txt +0 -18
- navigator-auth-0.4.7/navigator_auth/__init__.py +0 -11
- navigator-auth-0.4.7/navigator_auth/auth.py +0 -608
- navigator-auth-0.4.7/navigator_auth/backends/abstract.py +0 -355
- navigator-auth-0.4.7/navigator_auth/backends/adfs.py +0 -238
- navigator-auth-0.4.7/navigator_auth/backends/api.py +0 -214
- navigator-auth-0.4.7/navigator_auth/backends/azure.py +0 -327
- navigator-auth-0.4.7/navigator_auth/backends/basic.py +0 -208
- navigator-auth-0.4.7/navigator_auth/backends/django.py +0 -334
- navigator-auth-0.4.7/navigator_auth/backends/external.py +0 -440
- navigator-auth-0.4.7/navigator_auth/backends/noauth.py +0 -80
- navigator-auth-0.4.7/navigator_auth/backends/token.py +0 -244
- navigator-auth-0.4.7/navigator_auth/backends/troc.py +0 -258
- navigator-auth-0.4.7/navigator_auth/conf.py +0 -271
- navigator-auth-0.4.7/navigator_auth/decorators.py +0 -272
- navigator-auth-0.4.7/navigator_auth/exceptions.c +0 -12225
- navigator-auth-0.4.7/navigator_auth/handlers/__init__.py +0 -148
- navigator-auth-0.4.7/navigator_auth/handlers/base.py +0 -342
- navigator-auth-0.4.7/navigator_auth/handlers/clients.py +0 -10
- navigator-auth-0.4.7/navigator_auth/handlers/groups.py +0 -24
- navigator-auth-0.4.7/navigator_auth/handlers/orgs.py +0 -17
- navigator-auth-0.4.7/navigator_auth/handlers/program.py +0 -33
- navigator-auth-0.4.7/navigator_auth/handlers/users.py +0 -862
- navigator-auth-0.4.7/navigator_auth/libs/cipher.cpp +0 -7724
- navigator-auth-0.4.7/navigator_auth/libs/json.cpp +0 -7194
- navigator-auth-0.4.7/navigator_auth/version.py +0 -19
- navigator-auth-0.4.7/navigator_auth.egg-info/requires.txt +0 -16
- navigator-auth-0.4.7/pyproject.toml +0 -66
- navigator-auth-0.4.7/resources/user.py +0 -46
- navigator-auth-0.4.7/settings/settings.py +0 -33
- navigator-auth-0.4.7/setup.py +0 -143
- navigator-auth-0.4.7/tests/__init__.py +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/CHANGES.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/LICENSE +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/README.md +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/Makefile +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/api.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/authors.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/changelog.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/changes.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/code-of-conduct.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/conf.py +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/config.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/index.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/make.bat +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/policies.py +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/requirements.txt +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/security.rst +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/settings.rst +0 -0
- {navigator-auth-0.4.7/navigator_auth/libs → navigator_auth-0.16.0/navigator_auth/abac/storages}/__init__.py +0 -0
- /navigator-auth-0.4.7/resources/__init__.py → /navigator_auth-0.16.0/navigator_auth/py.typed +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/__init__.py +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/dependency_links.txt +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/top_level.txt +0 -0
- {navigator-auth-0.4.7 → navigator_auth-0.16.0}/setup.cfg +0 -0
- {navigator-auth-0.4.7/settings → navigator_auth-0.16.0/tests}/__init__.py +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Navigator-Auth Makefile
|
|
2
|
+
|
|
3
|
+
.PHONY: venv install develop release format lint test clean distclean lock sync check-deps
|
|
4
|
+
|
|
5
|
+
# Python version to use
|
|
6
|
+
PYTHON_VERSION := 3.11
|
|
7
|
+
|
|
8
|
+
# Auto-detect available tools
|
|
9
|
+
HAS_UV := $(shell command -v uv 2> /dev/null)
|
|
10
|
+
|
|
11
|
+
# Install uv if missing
|
|
12
|
+
install-uv:
|
|
13
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
14
|
+
@echo "uv installed! You may need to restart your shell or run 'source ~/.bashrc'"
|
|
15
|
+
|
|
16
|
+
# Create virtual environment
|
|
17
|
+
venv:
|
|
18
|
+
uv venv --python $(PYTHON_VERSION) .venv
|
|
19
|
+
@echo 'run `source .venv/bin/activate` to start develop Navigator-Auth'
|
|
20
|
+
|
|
21
|
+
# Install production dependencies using lock file
|
|
22
|
+
install:
|
|
23
|
+
uv sync --frozen --no-dev
|
|
24
|
+
@echo "Production dependencies installed."
|
|
25
|
+
|
|
26
|
+
# Generate lock files
|
|
27
|
+
lock:
|
|
28
|
+
ifdef HAS_UV
|
|
29
|
+
uv lock
|
|
30
|
+
else
|
|
31
|
+
@echo "Lock files require uv. Install with: make install-uv"
|
|
32
|
+
endif
|
|
33
|
+
|
|
34
|
+
# Install all dependencies including dev dependencies
|
|
35
|
+
develop:
|
|
36
|
+
uv sync --frozen --extra uvloop --dev
|
|
37
|
+
|
|
38
|
+
# Alternative: install without lock file (faster for development)
|
|
39
|
+
develop-fast:
|
|
40
|
+
uv pip install -e .[uvloop]
|
|
41
|
+
uv pip install -e .[dev]
|
|
42
|
+
|
|
43
|
+
# Compile Cython extensions
|
|
44
|
+
compile:
|
|
45
|
+
python setup.py build_ext --inplace
|
|
46
|
+
|
|
47
|
+
# Build and publish release
|
|
48
|
+
release: lint test clean
|
|
49
|
+
uv build
|
|
50
|
+
uv publish
|
|
51
|
+
|
|
52
|
+
# Format code
|
|
53
|
+
format:
|
|
54
|
+
uv run black navigator_auth
|
|
55
|
+
|
|
56
|
+
# Lint code
|
|
57
|
+
lint:
|
|
58
|
+
uv run pylint --rcfile .pylintrc navigator_auth/*.py
|
|
59
|
+
uv run black --check navigator_auth
|
|
60
|
+
|
|
61
|
+
# Run tests
|
|
62
|
+
test:
|
|
63
|
+
uv run coverage run -m pytest tests
|
|
64
|
+
uv run coverage report
|
|
65
|
+
uv run mypy navigator_auth/*.py
|
|
66
|
+
|
|
67
|
+
# Performance tests
|
|
68
|
+
perf:
|
|
69
|
+
uv run python -m unittest -v navigator_auth.tests.perf
|
|
70
|
+
|
|
71
|
+
# Clean build artifacts
|
|
72
|
+
clean:
|
|
73
|
+
rm -rf build/
|
|
74
|
+
rm -rf dist/
|
|
75
|
+
rm -rf *.egg-info/
|
|
76
|
+
find . -name "*.pyc" -delete
|
|
77
|
+
find . -name "*.pyo" -delete
|
|
78
|
+
find . -name "*.so" -delete
|
|
79
|
+
find . -type d -name __pycache__ -delete
|
|
80
|
+
@echo "Clean complete."
|
|
81
|
+
|
|
82
|
+
# Remove virtual environment
|
|
83
|
+
distclean:
|
|
84
|
+
rm -rf .venv
|
|
85
|
+
rm -rf uv.lock
|
|
86
|
+
|
|
87
|
+
# Show project info
|
|
88
|
+
info:
|
|
89
|
+
uv tree
|
|
@@ -1,33 +1,52 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: navigator-auth
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: Navigator Auth is an Authentication/Authorization Toolkit for aiohttp.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
License: Apache 2.0 license
|
|
5
|
+
Author-email: Jesus Lara Gimenez <jesuslarag@gmail.com>
|
|
6
|
+
License: Apache 2.0 License
|
|
7
|
+
Project-URL: Homepage, https://github.com/phenobarbital/navigator-auth
|
|
9
8
|
Project-URL: Source, https://github.com/phenobarbital/navigator-auth
|
|
10
9
|
Project-URL: Funding, https://paypal.me/phenobarbital
|
|
11
|
-
Project-URL:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Project-URL: Tracker, https://github.com/phenobarbital/navigator-auth/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/phenobarbital/navigator-auth
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
15
|
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
16
|
Classifier: Environment :: Web Environment
|
|
19
17
|
Classifier: Topic :: Software Development :: Build Tools
|
|
20
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
19
|
Classifier: Topic :: System :: Systems Administration
|
|
22
20
|
Classifier: Topic :: System :: Networking
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
24
21
|
Classifier: Programming Language :: Python :: 3.9
|
|
25
|
-
Classifier: Programming Language :: Python ::
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
26
|
Classifier: Framework :: AsyncIO
|
|
27
27
|
Classifier: Framework :: aiohttp
|
|
28
|
-
|
|
28
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
29
|
+
Requires-Python: >=3.9.16
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
License-File: LICENSE
|
|
32
|
+
Requires-Dist: Cython>=3.0.11
|
|
33
|
+
Requires-Dist: PyNaCl>=1.5.0
|
|
34
|
+
Requires-Dist: aiohttp>=3.9.5
|
|
35
|
+
Requires-Dist: aiohttp-cors==0.8.1
|
|
36
|
+
Requires-Dist: PyJWT>=2.10.1
|
|
37
|
+
Requires-Dist: pycryptodome>=3.21.0
|
|
38
|
+
Requires-Dist: rncryptor>=3.3.0
|
|
39
|
+
Requires-Dist: msal<=1.32.0,>=1.28.0
|
|
40
|
+
Requires-Dist: aiogoogle>=5.17.0
|
|
41
|
+
Requires-Dist: okta-jwt-verifier>=0.2.5
|
|
42
|
+
Requires-Dist: python-slugify>=8.0.1
|
|
43
|
+
Requires-Dist: asyncdb>=2.8.0
|
|
44
|
+
Requires-Dist: navconfig>=1.7.0
|
|
45
|
+
Requires-Dist: navigator-session>=0.6.5
|
|
46
|
+
Requires-Dist: navigator-api>=0.5.0
|
|
47
|
+
Provides-Extra: uvloop
|
|
48
|
+
Requires-Dist: uvloop>=0.20.0; extra == "uvloop"
|
|
49
|
+
Dynamic: license-file
|
|
31
50
|
|
|
32
51
|
# Navigator Auth #
|
|
33
52
|
|
|
@@ -65,5 +84,3 @@ Navigator Auth is an Authentication/Authorization toolkit for aiohttp or Navigat
|
|
|
65
84
|
### License ###
|
|
66
85
|
|
|
67
86
|
Navigator_Auth is copyright of Jesus Lara (https://phenobarbital.info) and is under Apache 2 license. I am providing code in this repository under an open source license, remember, this is my personal repository; the license that you receive is from me and not from my employeer.
|
|
68
|
-
|
|
69
|
-
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
aiounittest==1.4.2
|
|
2
|
+
black==24.3.0
|
|
3
|
+
build==1.0.3
|
|
4
|
+
coverage[toml]==7.2.7
|
|
5
|
+
flit==3.9.0
|
|
6
|
+
hypothesis==6.111.0
|
|
7
|
+
ipython==8.22.1
|
|
8
|
+
lint==1.2.1
|
|
9
|
+
mypy==1.7.1
|
|
10
|
+
pylint==3.2.6
|
|
11
|
+
pyperf==2.6.2
|
|
12
|
+
pytest>=6.0.0
|
|
13
|
+
pytest-asyncio==0.23.8
|
|
14
|
+
pytest-cov==4.0.0
|
|
15
|
+
pytest-cython==0.3.1
|
|
16
|
+
pytest-xdist==3.5.0
|
|
17
|
+
pytest-assume==2.4.3
|
|
18
|
+
sdist==0.0.0
|
|
19
|
+
sphinx==6.1.3
|
|
20
|
+
tox==4.18.0
|
|
21
|
+
twine==5.1.1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Navigator Auth.
|
|
3
|
+
|
|
4
|
+
Basic Toolkit for authentication/Authorization for aiohttp,
|
|
5
|
+
using AsyncDB for Storage access.
|
|
6
|
+
"""
|
|
7
|
+
from .version import __author__, __description__, __title__, __version__, get_version
|
|
8
|
+
|
|
9
|
+
from .auth import AuthHandler
|
|
10
|
+
from .uv import install_uvloop
|
|
11
|
+
|
|
12
|
+
install_uvloop()
|
|
13
|
+
|
|
14
|
+
__all__ = ("AuthHandler", )
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import socket
|
|
4
|
+
from navconfig.logging import logger
|
|
5
|
+
from asyncdb import AsyncDB
|
|
6
|
+
from asyncdb.exceptions import DriverError
|
|
7
|
+
from navigator_auth.exceptions import ConfigError
|
|
8
|
+
from navigator_auth.conf import (
|
|
9
|
+
ENVIRONMENT,
|
|
10
|
+
DOMAIN,
|
|
11
|
+
AUDIT_BACKEND,
|
|
12
|
+
AUDIT_CREDENTIALS,
|
|
13
|
+
ENABLE_AUDIT_LOG
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuditLog:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.loop = asyncio.get_event_loop()
|
|
20
|
+
if ENABLE_AUDIT_LOG is True:
|
|
21
|
+
try:
|
|
22
|
+
self.logger = AsyncDB(
|
|
23
|
+
AUDIT_BACKEND,
|
|
24
|
+
params=AUDIT_CREDENTIALS,
|
|
25
|
+
loop=self.loop
|
|
26
|
+
)
|
|
27
|
+
self.host = socket.gethostbyname(socket.gethostname())
|
|
28
|
+
except DriverError as ex:
|
|
29
|
+
raise ConfigError(
|
|
30
|
+
f"Unable to start Audit Backend: {ex}"
|
|
31
|
+
) from ex
|
|
32
|
+
else:
|
|
33
|
+
self.logger = logger
|
|
34
|
+
|
|
35
|
+
async def log(self, answer, status, user):
|
|
36
|
+
start_time = datetime.utcnow()
|
|
37
|
+
if ENABLE_AUDIT_LOG is True:
|
|
38
|
+
async with await self.logger.connection() as conn:
|
|
39
|
+
try:
|
|
40
|
+
data = {
|
|
41
|
+
"measurement": 'audit',
|
|
42
|
+
"location": ENVIRONMENT,
|
|
43
|
+
"domain": DOMAIN,
|
|
44
|
+
"timestamp": start_time,
|
|
45
|
+
"fields": {
|
|
46
|
+
"status": status
|
|
47
|
+
},
|
|
48
|
+
"tags": {
|
|
49
|
+
"host": self.host,
|
|
50
|
+
"region": ENVIRONMENT,
|
|
51
|
+
"message": answer.response,
|
|
52
|
+
"answer": answer,
|
|
53
|
+
"username": user.username,
|
|
54
|
+
"user": user.id
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await conn.write(data, bucket=AUDIT_CREDENTIALS['bucket'])
|
|
58
|
+
except (TypeError, AttributeError, ValueError, DriverError) as ex:
|
|
59
|
+
logger.error(
|
|
60
|
+
f'Error saving Audit Log: {ex}'
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
message = f'Access {status} by: {answer.response} to user {user.username}'
|
|
64
|
+
self.logger.info(message)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from collections.abc import MutableMapping, Iterator
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
from datamodel import BaseModel
|
|
5
|
+
|
|
6
|
+
class EvalContext(dict, MutableMapping):
|
|
7
|
+
"""EvalContext.
|
|
8
|
+
|
|
9
|
+
Build The Evaluation Context from Request and User Data.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
request: web.Request,
|
|
14
|
+
user: Any,
|
|
15
|
+
userinfo: Any,
|
|
16
|
+
session: Any,
|
|
17
|
+
*args,
|
|
18
|
+
**kwargs
|
|
19
|
+
):
|
|
20
|
+
## initialize the mutable mapping:
|
|
21
|
+
self.store = dict()
|
|
22
|
+
self.store['request'] = request
|
|
23
|
+
self.store['ip_addr'] = request.remote
|
|
24
|
+
self.store['method'] = request.method
|
|
25
|
+
self.store['referer'] = request.headers.get('referer', None)
|
|
26
|
+
self.store['path_qs'] = request.path_qs
|
|
27
|
+
self.store['path'] = request.path
|
|
28
|
+
self.store['headers'] = request.headers
|
|
29
|
+
self.store['url'] = request.rel_url
|
|
30
|
+
try:
|
|
31
|
+
self.store['is_authenticated'] = request.is_authenticated
|
|
32
|
+
except AttributeError:
|
|
33
|
+
if user is not None:
|
|
34
|
+
self.store['is_authenticated'] = True
|
|
35
|
+
else:
|
|
36
|
+
self.store['is_authenticated'] = False
|
|
37
|
+
self.store['user'] = user
|
|
38
|
+
if isinstance(user, BaseModel):
|
|
39
|
+
self.store['user_keys'] = user.get_fields()
|
|
40
|
+
else:
|
|
41
|
+
self.store['user_keys'] = user.__dict__.keys()
|
|
42
|
+
self.store['userinfo'] = userinfo
|
|
43
|
+
if isinstance(userinfo, dict):
|
|
44
|
+
self.store['userinfo_keys'] = list(userinfo.keys())
|
|
45
|
+
else:
|
|
46
|
+
try:
|
|
47
|
+
self.store['userinfo_keys'] = userinfo.__dict__.keys()
|
|
48
|
+
except AttributeError:
|
|
49
|
+
self.store['userinfo_keys'] = []
|
|
50
|
+
self.store['session'] = session
|
|
51
|
+
self.update(*args, **kwargs)
|
|
52
|
+
self._columns = list(self.store.keys())
|
|
53
|
+
|
|
54
|
+
def __missing__(self, key):
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def items(self) -> zip: # type: ignore
|
|
58
|
+
return zip(self._columns, self.store)
|
|
59
|
+
|
|
60
|
+
def keys(self) -> list:
|
|
61
|
+
return self._columns
|
|
62
|
+
|
|
63
|
+
def set(self, key, value) -> None:
|
|
64
|
+
self.store[key] = value
|
|
65
|
+
if key not in self._columns:
|
|
66
|
+
self._columns.append(key)
|
|
67
|
+
|
|
68
|
+
### Section: Simple magic methods
|
|
69
|
+
def __len__(self) -> int:
|
|
70
|
+
return len(self.store)
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return f"<{type(self).__name__}({self.store})>"
|
|
74
|
+
|
|
75
|
+
def __repr__(self) -> str:
|
|
76
|
+
return f"<{type(self).__name__}({self.store})>"
|
|
77
|
+
|
|
78
|
+
def __contains__(self, key: str) -> bool:
|
|
79
|
+
return key in self._columns
|
|
80
|
+
|
|
81
|
+
def __iter__(self) -> Iterator:
|
|
82
|
+
for value in self.store:
|
|
83
|
+
yield value
|
|
84
|
+
|
|
85
|
+
def __delitem__(self, key) -> None:
|
|
86
|
+
value = self.store[key]
|
|
87
|
+
del self.store[key]
|
|
88
|
+
self._columns.pop(value, None)
|
|
89
|
+
|
|
90
|
+
def __setitem__(self, key, value):
|
|
91
|
+
self.store[key] = value
|
|
92
|
+
if key not in self._columns:
|
|
93
|
+
self._columns.append(key)
|
|
94
|
+
|
|
95
|
+
def __getattr__(self, key):
|
|
96
|
+
try:
|
|
97
|
+
return super().__getattribute__('store')[key]
|
|
98
|
+
except KeyError as ex:
|
|
99
|
+
raise AttributeError(key) from ex
|
|
100
|
+
|
|
101
|
+
def __setattr__(self, key, value):
|
|
102
|
+
if key == 'store':
|
|
103
|
+
super().__setattr__(key, value)
|
|
104
|
+
else:
|
|
105
|
+
self.store[key] = value
|
|
106
|
+
|
|
107
|
+
def __delattr__(self, key):
|
|
108
|
+
try:
|
|
109
|
+
del self.store[key]
|
|
110
|
+
except KeyError as ex:
|
|
111
|
+
raise AttributeError(key) from ex
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, TypeVar
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from aiohttp import web, hdrs
|
|
5
|
+
from aiohttp.abc import AbstractView
|
|
6
|
+
import inspect
|
|
7
|
+
from navigator_session import get_session
|
|
8
|
+
from navigator_auth.conf import AUTH_SESSION_OBJECT
|
|
9
|
+
from navigator_auth.decorators import _apply_decorator
|
|
10
|
+
|
|
11
|
+
from navigator_auth.abac.context import EvalContext
|
|
12
|
+
from navigator_auth.abac.policies.resources import ResourceType
|
|
13
|
+
|
|
14
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
15
|
+
|
|
16
|
+
def groups_protected(groups: list, content_type: str = "application/json") -> Callable:
|
|
17
|
+
"""Restrict the Handler only to certain Groups in User information."""
|
|
18
|
+
|
|
19
|
+
def _wrapper(handler: F):
|
|
20
|
+
@wraps(handler)
|
|
21
|
+
async def _wrap(*args, **kwargs) -> web.StreamResponse:
|
|
22
|
+
# Supports class based views see web.View
|
|
23
|
+
if isinstance(args[0], AbstractView):
|
|
24
|
+
request = args[0].request
|
|
25
|
+
else:
|
|
26
|
+
request = args[-1]
|
|
27
|
+
if request is None:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"web.Request was not found in arguments. {handler!s}"
|
|
30
|
+
)
|
|
31
|
+
if request.get("authenticated", False) is False:
|
|
32
|
+
# check credentials:
|
|
33
|
+
raise web.HTTPUnauthorized(
|
|
34
|
+
reason="Access Denied",
|
|
35
|
+
headers={
|
|
36
|
+
hdrs.CONTENT_TYPE: content_type,
|
|
37
|
+
hdrs.CONNECTION: "keep-alive",
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
session = await get_session(request)
|
|
42
|
+
member = False
|
|
43
|
+
try:
|
|
44
|
+
userinfo = session[AUTH_SESSION_OBJECT]
|
|
45
|
+
except KeyError:
|
|
46
|
+
member = False
|
|
47
|
+
if "groups" in userinfo:
|
|
48
|
+
member = bool(not set(userinfo["groups"]).isdisjoint(groups))
|
|
49
|
+
else:
|
|
50
|
+
user = session.decode("user")
|
|
51
|
+
for group in user.groups:
|
|
52
|
+
if group.group in groups:
|
|
53
|
+
member = True
|
|
54
|
+
if member is True:
|
|
55
|
+
## Check Groups belong to User
|
|
56
|
+
return await handler(*args, **kwargs)
|
|
57
|
+
else:
|
|
58
|
+
raise web.HTTPUnauthorized(
|
|
59
|
+
reason="Access Denied",
|
|
60
|
+
headers={
|
|
61
|
+
hdrs.CONTENT_TYPE: content_type,
|
|
62
|
+
hdrs.CONNECTION: "keep-alive",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return _wrap
|
|
67
|
+
return _wrapper
|
|
68
|
+
|
|
69
|
+
def requires_permission(
|
|
70
|
+
resource_type: ResourceType,
|
|
71
|
+
action: str,
|
|
72
|
+
resource_name_param: str = None
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Decorator for methods that require permission checks.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
@requires_permission(ResourceType.KB, "kb:query", "kb_name")
|
|
79
|
+
async def query_knowledge_base(self, kb_name: str, question: str, ctx: EvalContext):
|
|
80
|
+
...
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def _func_wrapper(handler):
|
|
84
|
+
@wraps(handler)
|
|
85
|
+
async def _wrap(*args, **kwargs) -> web.StreamResponse:
|
|
86
|
+
# Safe way: inspect args for EvalContext
|
|
87
|
+
ctx = kwargs.get('ctx')
|
|
88
|
+
if ctx is None:
|
|
89
|
+
for arg in args:
|
|
90
|
+
if isinstance(arg, EvalContext):
|
|
91
|
+
ctx = arg
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
# If not found, we cannot proceed as per spec
|
|
95
|
+
if ctx is None:
|
|
96
|
+
raise ValueError("EvalContext required for permission check")
|
|
97
|
+
|
|
98
|
+
# Resource Name logic
|
|
99
|
+
resource_name = "*"
|
|
100
|
+
if resource_name_param:
|
|
101
|
+
resource_name = kwargs.get(resource_name_param)
|
|
102
|
+
if resource_name is None and args:
|
|
103
|
+
try:
|
|
104
|
+
# Inspect the handler to find parameter index
|
|
105
|
+
sig = inspect.signature(handler)
|
|
106
|
+
params = list(sig.parameters.keys())
|
|
107
|
+
if resource_name_param in params:
|
|
108
|
+
idx = params.index(resource_name_param)
|
|
109
|
+
if idx < len(args):
|
|
110
|
+
resource_name = args[idx]
|
|
111
|
+
except ValueError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Policy Evaluator retrieval
|
|
115
|
+
# For function handlers, we check if the first arg has an evaluator (e.g. self-like object)
|
|
116
|
+
# or if the handler itself has one attached?
|
|
117
|
+
# In aiohttp cbv, the view instance is args[0] for method calls, but here we are in _func_wrapper.
|
|
118
|
+
# However, _apply_decorator might pass the view instance if logic allows?
|
|
119
|
+
# Actually, _apply_decorator uses _method_wrapper for methods.
|
|
120
|
+
# So _func_wrapper handles pure functions. Pure functions likely rely on context or global evaluators?
|
|
121
|
+
# Or maybe args[0] is 'self' if it's a bound method passed as function?
|
|
122
|
+
|
|
123
|
+
evaluator = None
|
|
124
|
+
if args and hasattr(args[0], '_policy_evaluator'):
|
|
125
|
+
evaluator = getattr(args[0], '_policy_evaluator')
|
|
126
|
+
elif args and hasattr(args[0], 'policy_evaluator'):
|
|
127
|
+
evaluator = getattr(args[0], 'policy_evaluator')
|
|
128
|
+
|
|
129
|
+
if evaluator is None:
|
|
130
|
+
# fallback/warning
|
|
131
|
+
return await handler(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
result = evaluator.check_access(
|
|
134
|
+
ctx=ctx,
|
|
135
|
+
resource_type=resource_type,
|
|
136
|
+
resource_name=resource_name,
|
|
137
|
+
action=action
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if not result.allowed:
|
|
141
|
+
raise web.HTTPForbidden(
|
|
142
|
+
reason=f"Access denied: {resource_type.value}:{resource_name} - {result.reason}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return await handler(*args, **kwargs)
|
|
146
|
+
return _wrap
|
|
147
|
+
|
|
148
|
+
def _method_wrapper(method):
|
|
149
|
+
@wraps(method)
|
|
150
|
+
async def _wrap(self, *args, **kwargs):
|
|
151
|
+
# Find context
|
|
152
|
+
ctx = kwargs.get('ctx')
|
|
153
|
+
if ctx is None:
|
|
154
|
+
for arg in args:
|
|
155
|
+
if isinstance(arg, EvalContext):
|
|
156
|
+
ctx = arg
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
if ctx is None:
|
|
160
|
+
raise ValueError("EvalContext required for permission check")
|
|
161
|
+
|
|
162
|
+
# Resource name
|
|
163
|
+
resource_name = "*"
|
|
164
|
+
if resource_name_param:
|
|
165
|
+
resource_name = kwargs.get(resource_name_param)
|
|
166
|
+
if resource_name is None and args:
|
|
167
|
+
try:
|
|
168
|
+
sig = inspect.signature(method)
|
|
169
|
+
params = list(sig.parameters.keys())
|
|
170
|
+
if resource_name_param in params:
|
|
171
|
+
idx = params.index(resource_name_param)
|
|
172
|
+
# method signature usually has self as first param?
|
|
173
|
+
# If bound method is inspected, self is matching params[0]?
|
|
174
|
+
if params and params[0] == 'self':
|
|
175
|
+
idx = idx - 1
|
|
176
|
+
|
|
177
|
+
if idx >= 0 and idx < len(args):
|
|
178
|
+
resource_name = args[idx]
|
|
179
|
+
except ValueError:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
# Evaluator from self
|
|
183
|
+
evaluator = getattr(self, '_policy_evaluator', getattr(self, 'policy_evaluator', None))
|
|
184
|
+
|
|
185
|
+
if evaluator is None:
|
|
186
|
+
return await method(self, *args, **kwargs)
|
|
187
|
+
|
|
188
|
+
result = evaluator.check_access(
|
|
189
|
+
ctx=ctx,
|
|
190
|
+
resource_type=resource_type,
|
|
191
|
+
resource_name=resource_name,
|
|
192
|
+
action=action
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not result.allowed:
|
|
196
|
+
raise web.HTTPForbidden(
|
|
197
|
+
reason=f"Access denied: {resource_type.value}:{resource_name} - {result.reason}"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return await method(self, *args, **kwargs)
|
|
201
|
+
return _wrap
|
|
202
|
+
|
|
203
|
+
return lambda handler: _apply_decorator(handler, _func_wrapper, _method_wrapper)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import Union, Optional, Any
|
|
2
|
+
from aiohttp import web, hdrs
|
|
3
|
+
from navconfig.logging import logger
|
|
4
|
+
from navigator_auth.libs.json import json_encoder
|
|
5
|
+
|
|
6
|
+
def default_headers(message: str, exception: BaseException = None) -> dict:
|
|
7
|
+
headers = {
|
|
8
|
+
"X-ABAC-RESPONSE": message,
|
|
9
|
+
hdrs.CONNECTION: "keep-alive",
|
|
10
|
+
}
|
|
11
|
+
if exception:
|
|
12
|
+
headers['X-ERROR'] = str(exception)
|
|
13
|
+
return headers
|
|
14
|
+
|
|
15
|
+
def auth_error(
|
|
16
|
+
reason: Optional[Union[str, dict]] = None,
|
|
17
|
+
exception: Exception = None,
|
|
18
|
+
status: int = 400,
|
|
19
|
+
headers: dict = None,
|
|
20
|
+
content_type: str = 'application/json',
|
|
21
|
+
**kwargs,
|
|
22
|
+
) -> web.HTTPError:
|
|
23
|
+
"""auth_error.
|
|
24
|
+
|
|
25
|
+
Basic Function to build an HTTP Error object.
|
|
26
|
+
Args:
|
|
27
|
+
reason (dict, optional): message passed as error.
|
|
28
|
+
exception (Exception, optional): exception raised. Defaults to None.
|
|
29
|
+
status (int, optional): current HTTP Status. Defaults to 400.
|
|
30
|
+
headers (dict, optional): dictionary of headers. Defaults to None.
|
|
31
|
+
content_type (str, optional): default mime type. Defaults to 'application/json'.
|
|
32
|
+
kwargs: (dict, optional): any other argument passed to HTTPError.
|
|
33
|
+
Returns:
|
|
34
|
+
web.HTTPError: _description_
|
|
35
|
+
"""
|
|
36
|
+
if headers:
|
|
37
|
+
headers = {**default_headers(message=str(reason), exception=exception), **headers}
|
|
38
|
+
else:
|
|
39
|
+
headers = default_headers(message=str(reason), exception=exception)
|
|
40
|
+
# TODO: process the exception object
|
|
41
|
+
response_obj = {
|
|
42
|
+
"status": status
|
|
43
|
+
}
|
|
44
|
+
if exception:
|
|
45
|
+
response_obj["error"] = str(exception)
|
|
46
|
+
args = {
|
|
47
|
+
"content_type": content_type,
|
|
48
|
+
"headers": headers,
|
|
49
|
+
**kwargs
|
|
50
|
+
}
|
|
51
|
+
if isinstance(reason, dict):
|
|
52
|
+
response_obj = {**response_obj, **reason}
|
|
53
|
+
# args["content_type"] = "application/json"
|
|
54
|
+
args["body"] = json_encoder(response_obj)
|
|
55
|
+
else:
|
|
56
|
+
response_obj['reason'] = reason
|
|
57
|
+
args["body"] = json_encoder(response_obj)
|
|
58
|
+
# defining the error
|
|
59
|
+
if status == 400: # bad request
|
|
60
|
+
obj = web.HTTPBadRequest(**args)
|
|
61
|
+
elif status == 401: # unauthorized
|
|
62
|
+
obj = web.HTTPUnauthorized(**args)
|
|
63
|
+
elif status == 403: # forbidden
|
|
64
|
+
obj = web.HTTPForbidden(**args)
|
|
65
|
+
elif status == 404: # not found
|
|
66
|
+
obj = web.HTTPNotFound(**args)
|
|
67
|
+
elif status == 406: # Not acceptable
|
|
68
|
+
obj = web.HTTPNotAcceptable(**args)
|
|
69
|
+
elif status == 412:
|
|
70
|
+
obj = web.HTTPPreconditionFailed(**args)
|
|
71
|
+
elif status == 428:
|
|
72
|
+
obj = web.HTTPPreconditionRequired(**args)
|
|
73
|
+
else:
|
|
74
|
+
obj = web.HTTPBadRequest(**args)
|
|
75
|
+
return obj
|
|
76
|
+
|
|
77
|
+
def PreconditionFailed(reason: Union[str, dict], **kwargs) -> web.HTTPError:
|
|
78
|
+
msg = f"Error: Some preconditions failed: {reason}"
|
|
79
|
+
return auth_error(
|
|
80
|
+
reason=msg, **kwargs, status=428
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def AccessDenied(reason: Union[str, dict], **kwargs) -> web.HTTPError:
|
|
84
|
+
return auth_error(
|
|
85
|
+
reason=reason, **kwargs, status=403
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def Unauthorized(reason: Union[str, dict], **kwargs) -> web.HTTPError:
|
|
89
|
+
logger.error(
|
|
90
|
+
reason
|
|
91
|
+
)
|
|
92
|
+
return auth_error(
|
|
93
|
+
reason=reason, **kwargs, status=401
|
|
94
|
+
)
|