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.
Files changed (164) hide show
  1. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/MANIFEST.in +1 -1
  2. navigator_auth-0.16.0/Makefile +89 -0
  3. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/PKG-INFO +33 -16
  4. navigator_auth-0.16.0/docs/requirements-dev.txt +21 -0
  5. navigator_auth-0.16.0/navigator_auth/__init__.py +14 -0
  6. navigator_auth-0.16.0/navigator_auth/abac/__init__.py +7 -0
  7. navigator_auth-0.16.0/navigator_auth/abac/audit.py +64 -0
  8. navigator_auth-0.16.0/navigator_auth/abac/context.py +111 -0
  9. navigator_auth-0.16.0/navigator_auth/abac/decorators.py +203 -0
  10. navigator_auth-0.16.0/navigator_auth/abac/errors.py +94 -0
  11. navigator_auth-0.16.0/navigator_auth/abac/guardian.py +299 -0
  12. navigator_auth-0.16.0/navigator_auth/abac/middleware.py +53 -0
  13. navigator_auth-0.16.0/navigator_auth/abac/pdp.py +375 -0
  14. navigator_auth-0.16.0/navigator_auth/abac/policies/__init__.py +22 -0
  15. navigator_auth-0.16.0/navigator_auth/abac/policies/abstract.py +223 -0
  16. navigator_auth-0.16.0/navigator_auth/abac/policies/environment.py +24 -0
  17. navigator_auth-0.16.0/navigator_auth/abac/policies/evaluator.py +481 -0
  18. navigator_auth-0.16.0/navigator_auth/abac/policies/file.py +99 -0
  19. navigator_auth-0.16.0/navigator_auth/abac/policies/obj.py +241 -0
  20. navigator_auth-0.16.0/navigator_auth/abac/policies/policy.py +151 -0
  21. navigator_auth-0.16.0/navigator_auth/abac/policies/resource_policy.py +221 -0
  22. navigator_auth-0.16.0/navigator_auth/abac/policies/resources.py +324 -0
  23. navigator_auth-0.16.0/navigator_auth/abac/policyhandler.py +13 -0
  24. navigator_auth-0.16.0/navigator_auth/abac/storages/abstract.py +22 -0
  25. navigator_auth-0.16.0/navigator_auth/abac/storages/db.py +92 -0
  26. navigator_auth-0.16.0/navigator_auth/abac/storages/pg.py +68 -0
  27. navigator_auth-0.16.0/navigator_auth/auth.py +809 -0
  28. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/__init__.py +6 -1
  29. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/abstract.py +3 -1
  30. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/allow_hosts.py +3 -5
  31. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/authorizations/hosts.py +3 -3
  32. navigator_auth-0.16.0/navigator_auth/authorizations/useragent.py +20 -0
  33. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/__init__.py +4 -1
  34. navigator_auth-0.16.0/navigator_auth/backends/abstract.py +500 -0
  35. navigator_auth-0.16.0/navigator_auth/backends/adfs.py +343 -0
  36. navigator_auth-0.16.0/navigator_auth/backends/api.py +279 -0
  37. navigator_auth-0.16.0/navigator_auth/backends/attributes/__init__.py +4 -0
  38. navigator_auth-0.16.0/navigator_auth/backends/attributes/abstract.py +33 -0
  39. navigator_auth-0.16.0/navigator_auth/backends/attributes/internal.py +13 -0
  40. navigator_auth-0.16.0/navigator_auth/backends/azure.py +472 -0
  41. navigator_auth-0.16.0/navigator_auth/backends/basic.py +273 -0
  42. navigator_auth-0.16.0/navigator_auth/backends/django.py +278 -0
  43. navigator_auth-0.16.0/navigator_auth/backends/external.py +573 -0
  44. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/github.py +37 -32
  45. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/google.py +32 -37
  46. navigator_auth-0.16.0/navigator_auth/backends/idp/__init__.py +444 -0
  47. navigator_auth-0.16.0/navigator_auth/backends/idp/code.py +60 -0
  48. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/jwksutils.py +43 -41
  49. navigator_auth-0.16.0/navigator_auth/backends/noauth.py +79 -0
  50. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/oauth.py +9 -7
  51. navigator_auth-0.16.0/navigator_auth/backends/oauth2/__init__.py +4 -0
  52. navigator_auth-0.16.0/navigator_auth/backends/oauth2/backend.py +388 -0
  53. navigator_auth-0.16.0/navigator_auth/backends/oauth2/models.py +99 -0
  54. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/backends/okta.py +41 -40
  55. navigator_auth-0.16.0/navigator_auth/backends/token.py +238 -0
  56. navigator_auth-0.16.0/navigator_auth/backends/troc.py +289 -0
  57. navigator_auth-0.16.0/navigator_auth/conf.py +379 -0
  58. navigator_auth-0.16.0/navigator_auth/decorators.py +519 -0
  59. navigator_auth-0.16.0/navigator_auth/exceptions.c +17620 -0
  60. navigator_auth-0.16.0/navigator_auth/exceptions.pxd +30 -0
  61. navigator_auth-0.16.0/navigator_auth/exceptions.pyx +62 -0
  62. navigator_auth-0.16.0/navigator_auth/handlers/__init__.py +64 -0
  63. navigator_auth-0.16.0/navigator_auth/handlers/groups.py +31 -0
  64. navigator_auth-0.16.0/navigator_auth/handlers/handler.py +10 -0
  65. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/handlers/model.py +197 -203
  66. navigator_auth-0.16.0/navigator_auth/handlers/partners.py +68 -0
  67. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/handlers/permissions.py +5 -4
  68. navigator_auth-0.16.0/navigator_auth/handlers/userattrs.py +60 -0
  69. navigator_auth-0.16.0/navigator_auth/handlers/users/__init__.py +10 -0
  70. navigator_auth-0.16.0/navigator_auth/handlers/users/passwd.py +39 -0
  71. navigator_auth-0.16.0/navigator_auth/handlers/users/session.py +277 -0
  72. navigator_auth-0.16.0/navigator_auth/handlers/users/user.py +58 -0
  73. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/identities.py +26 -15
  74. navigator_auth-0.16.0/navigator_auth/libs/__init__.py +12 -0
  75. navigator_auth-0.16.0/navigator_auth/libs/cipher.cpp +12722 -0
  76. navigator_auth-0.16.0/navigator_auth/libs/cipher.pyx +83 -0
  77. navigator_auth-0.16.0/navigator_auth/libs/json.cpp +13040 -0
  78. navigator_auth-0.16.0/navigator_auth/libs/json.pyx +87 -0
  79. navigator_auth-0.16.0/navigator_auth/libs/parser.cpp +11265 -0
  80. navigator_auth-0.16.0/navigator_auth/libs/parser.pyx +78 -0
  81. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/__init__.py +1 -6
  82. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/abstract.py +27 -30
  83. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/apikey.py +19 -28
  84. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/django.py +11 -20
  85. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/jwt.py +16 -29
  86. navigator_auth-0.16.0/navigator_auth/middlewares/security.py +42 -0
  87. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/token.py +10 -17
  88. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/middlewares/troc.py +12 -29
  89. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/models.py +136 -175
  90. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/responses.py +17 -21
  91. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/abstract.py +10 -23
  92. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/postgres.py +8 -17
  93. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/redis.py +5 -14
  94. navigator_auth-0.16.0/navigator_auth/templates.py +189 -0
  95. navigator_auth-0.16.0/navigator_auth/uv.py +11 -0
  96. navigator_auth-0.16.0/navigator_auth/version.py +21 -0
  97. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/PKG-INFO +33 -16
  98. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/SOURCES.txt +55 -10
  99. navigator_auth-0.16.0/navigator_auth.egg-info/requires.txt +18 -0
  100. navigator_auth-0.16.0/pyproject.toml +111 -0
  101. navigator_auth-0.16.0/setup.py +50 -0
  102. navigator_auth-0.16.0/tests/test_allowed.py +295 -0
  103. navigator_auth-0.16.0/tests/test_login.py +37 -0
  104. navigator_auth-0.16.0/tests/test_policy.py +85 -0
  105. navigator_auth-0.16.0/tests/test_policy_conditions.py +76 -0
  106. navigator_auth-0.16.0/tests/test_policy_evaluation.py +106 -0
  107. navigator_auth-0.16.0/tests/test_policy_object.py +126 -0
  108. navigator-auth-0.4.7/Makefile +0 -35
  109. navigator-auth-0.4.7/docs/requirements-dev.txt +0 -18
  110. navigator-auth-0.4.7/navigator_auth/__init__.py +0 -11
  111. navigator-auth-0.4.7/navigator_auth/auth.py +0 -608
  112. navigator-auth-0.4.7/navigator_auth/backends/abstract.py +0 -355
  113. navigator-auth-0.4.7/navigator_auth/backends/adfs.py +0 -238
  114. navigator-auth-0.4.7/navigator_auth/backends/api.py +0 -214
  115. navigator-auth-0.4.7/navigator_auth/backends/azure.py +0 -327
  116. navigator-auth-0.4.7/navigator_auth/backends/basic.py +0 -208
  117. navigator-auth-0.4.7/navigator_auth/backends/django.py +0 -334
  118. navigator-auth-0.4.7/navigator_auth/backends/external.py +0 -440
  119. navigator-auth-0.4.7/navigator_auth/backends/noauth.py +0 -80
  120. navigator-auth-0.4.7/navigator_auth/backends/token.py +0 -244
  121. navigator-auth-0.4.7/navigator_auth/backends/troc.py +0 -258
  122. navigator-auth-0.4.7/navigator_auth/conf.py +0 -271
  123. navigator-auth-0.4.7/navigator_auth/decorators.py +0 -272
  124. navigator-auth-0.4.7/navigator_auth/exceptions.c +0 -12225
  125. navigator-auth-0.4.7/navigator_auth/handlers/__init__.py +0 -148
  126. navigator-auth-0.4.7/navigator_auth/handlers/base.py +0 -342
  127. navigator-auth-0.4.7/navigator_auth/handlers/clients.py +0 -10
  128. navigator-auth-0.4.7/navigator_auth/handlers/groups.py +0 -24
  129. navigator-auth-0.4.7/navigator_auth/handlers/orgs.py +0 -17
  130. navigator-auth-0.4.7/navigator_auth/handlers/program.py +0 -33
  131. navigator-auth-0.4.7/navigator_auth/handlers/users.py +0 -862
  132. navigator-auth-0.4.7/navigator_auth/libs/cipher.cpp +0 -7724
  133. navigator-auth-0.4.7/navigator_auth/libs/json.cpp +0 -7194
  134. navigator-auth-0.4.7/navigator_auth/version.py +0 -19
  135. navigator-auth-0.4.7/navigator_auth.egg-info/requires.txt +0 -16
  136. navigator-auth-0.4.7/pyproject.toml +0 -66
  137. navigator-auth-0.4.7/resources/user.py +0 -46
  138. navigator-auth-0.4.7/settings/settings.py +0 -33
  139. navigator-auth-0.4.7/setup.py +0 -143
  140. navigator-auth-0.4.7/tests/__init__.py +0 -0
  141. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/CHANGES.rst +0 -0
  142. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/LICENSE +0 -0
  143. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/README.md +0 -0
  144. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/Makefile +0 -0
  145. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/api.rst +0 -0
  146. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/authors.rst +0 -0
  147. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/changelog.rst +0 -0
  148. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/changes.rst +0 -0
  149. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/code-of-conduct.rst +0 -0
  150. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/conf.py +0 -0
  151. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/config.rst +0 -0
  152. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/index.rst +0 -0
  153. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/make.bat +0 -0
  154. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/policies.py +0 -0
  155. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/requirements.txt +0 -0
  156. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/security.rst +0 -0
  157. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/docs/settings.rst +0 -0
  158. {navigator-auth-0.4.7/navigator_auth/libs → navigator_auth-0.16.0/navigator_auth/abac/storages}/__init__.py +0 -0
  159. /navigator-auth-0.4.7/resources/__init__.py → /navigator_auth-0.16.0/navigator_auth/py.typed +0 -0
  160. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth/storages/__init__.py +0 -0
  161. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/dependency_links.txt +0 -0
  162. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/navigator_auth.egg-info/top_level.txt +0 -0
  163. {navigator-auth-0.4.7 → navigator_auth-0.16.0}/setup.cfg +0 -0
  164. {navigator-auth-0.4.7/settings → navigator_auth-0.16.0/tests}/__init__.py +0 -0
@@ -3,7 +3,7 @@ include LICENSE
3
3
  include CHANGES.rst
4
4
  include README.md
5
5
  include Makefile
6
- graft navigator_session
6
+ graft navigator_auth
7
7
  graft docs
8
8
  graft tests
9
9
  global-exclude *.pyc
@@ -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
1
+ Metadata-Version: 2.4
2
2
  Name: navigator-auth
3
- Version: 0.4.7
3
+ Version: 0.16.0
4
4
  Summary: Navigator Auth is an Authentication/Authorization Toolkit for aiohttp.
5
- Home-page: https://github.com/phenobarbital/navigator-auth
6
- Author: Jesus Lara
7
- Author-email: jesuslarag@gmail.com
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: Say Thanks!, https://saythanks.io/to/phenobarbital
12
- Keywords: asyncio,auth,aioredis,aiohttp,authz,authentication,authorization
13
- Platform: POSIX
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 :: Implementation :: PyPy
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
- Requires-Python: >=3.8.0
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,7 @@
1
+ """ABAC Proof of Concept.
2
+ """
3
+
4
+ from .policies import Policy, PolicyEffect
5
+
6
+
7
+ __all__ = ['Policy', 'PolicyEffect']
@@ -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
+ )