aiohttp-msal 0.6.6__tar.gz → 0.6.8__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.
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/LICENSE +1 -1
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/PKG-INFO +12 -8
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/README.md +6 -2
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/__init__.py +16 -12
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/msal_async.py +20 -45
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/redis_tools.py +69 -9
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/routes.py +1 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/settings.py +9 -3
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/settings_base.py +3 -1
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal/user_info.py +1 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/PKG-INFO +13 -9
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/requires.txt +3 -3
- aiohttp_msal-0.6.8/pyproject.toml +65 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/setup.cfg +5 -20
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/tests/test_init.py +1 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/tests/test_msal_async.py +1 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/tests/test_redis_tools.py +1 -0
- aiohttp_msal-0.6.6/pyproject.toml +0 -4
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/SOURCES.txt +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/dependency_links.txt +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/top_level.txt +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/aiohttp_msal.egg-info/zip-safe +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/setup.py +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/tests/__init__.py +0 -0
- {aiohttp_msal-0.6.6 → aiohttp_msal-0.6.8}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aiohttp_msal
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
|
|
5
5
|
Home-page: https://github.com/kellerza/aiohttp_msal
|
|
6
6
|
Author: Johann Kellerman
|
|
@@ -12,20 +12,20 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: msal>=1.
|
|
21
|
+
Requires-Dist: msal>=1.30.0
|
|
22
22
|
Requires-Dist: aiohttp_session>=2.12
|
|
23
23
|
Requires-Dist: aiohttp>=3.8
|
|
24
24
|
Provides-Extra: redis
|
|
25
25
|
Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
|
|
26
26
|
Provides-Extra: tests
|
|
27
|
-
Requires-Dist: black==
|
|
28
|
-
Requires-Dist: pylint; extra == "tests"
|
|
27
|
+
Requires-Dist: black==24.8.0; extra == "tests"
|
|
28
|
+
Requires-Dist: pylint==3.2.6; extra == "tests"
|
|
29
29
|
Requires-Dist: flake8; extra == "tests"
|
|
30
30
|
Requires-Dist: pytest-aiohttp; extra == "tests"
|
|
31
31
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -35,9 +35,13 @@ Requires-Dist: pytest-env; extra == "tests"
|
|
|
35
35
|
|
|
36
36
|
# aiohttp_msal Python library
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Authorization Code Flow Helper. Learn more about auth-code-flow at
|
|
39
|
+
<https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow>
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
Async based OAuth using the Microsoft Authentication Library (MSAL) for Python.
|
|
42
|
+
|
|
43
|
+
Blocking MSAL functions are executed in the executor thread.
|
|
44
|
+
Should be useful until such time as MSAL Python gets a true async version.
|
|
41
45
|
|
|
42
46
|
Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
|
|
43
47
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# aiohttp_msal Python library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Authorization Code Flow Helper. Learn more about auth-code-flow at
|
|
4
|
+
<https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow>
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Async based OAuth using the Microsoft Authentication Library (MSAL) for Python.
|
|
7
|
+
|
|
8
|
+
Blocking MSAL functions are executed in the executor thread.
|
|
9
|
+
Should be useful until such time as MSAL Python gets a true async version.
|
|
6
10
|
|
|
7
11
|
Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
|
|
8
12
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""aiohttp_msal."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
4
|
+
import typing
|
|
3
5
|
from functools import wraps
|
|
4
6
|
from inspect import getfullargspec, iscoroutinefunction
|
|
5
|
-
from typing import Any, Awaitable, Callable, Union
|
|
6
7
|
|
|
7
8
|
from aiohttp import ClientSession, web
|
|
8
9
|
from aiohttp_session import get_session
|
|
@@ -13,26 +14,29 @@ from aiohttp_msal.settings import ENV
|
|
|
13
14
|
|
|
14
15
|
_LOGGER = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
|
-
VERSION = "0.6.
|
|
17
|
+
VERSION = "0.6.8"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def msal_session(
|
|
20
|
+
def msal_session(
|
|
21
|
+
*callbacks: typing.Callable[[AsyncMSAL], bool | typing.Awaitable[bool]],
|
|
22
|
+
at_least_one: bool | None = False,
|
|
23
|
+
) -> typing.Callable:
|
|
20
24
|
"""Session decorator.
|
|
21
25
|
|
|
22
26
|
Arguments can include a list of function to perform login tests etc.
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
|
-
def _session(func: Callable) -> Callable:
|
|
29
|
+
def _session(func: typing.Callable) -> typing.Callable:
|
|
26
30
|
@wraps(func)
|
|
27
|
-
async def __session(request: web.Request) -> Callable:
|
|
28
|
-
|
|
29
|
-
for
|
|
30
|
-
if iscoroutinefunction(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
async def __session(request: web.Request) -> typing.Callable:
|
|
32
|
+
ses = AsyncMSAL(session=await get_session(request))
|
|
33
|
+
for c_b in callbacks:
|
|
34
|
+
_ok = await c_b(ses) if iscoroutinefunction(c_b) else c_b(ses)
|
|
35
|
+
if at_least_one and _ok:
|
|
36
|
+
break
|
|
37
|
+
if not at_least_one and not _ok:
|
|
34
38
|
raise web.HTTPForbidden
|
|
35
|
-
return await func(request=request, ses=
|
|
39
|
+
return await func(request=request, ses=ses)
|
|
36
40
|
|
|
37
41
|
assert iscoroutinefunction(func), f"Function needs to be a coroutine: {func}"
|
|
38
42
|
spec = getfullargspec(func)
|
|
@@ -4,10 +4,11 @@ The AsyncMSAL class contains more info to perform OAuth & get the required token
|
|
|
4
4
|
Once you have the OAuth tokens store in the session, you are free to make requests
|
|
5
5
|
(typically from an aiohttp server's inside a request)
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import asyncio
|
|
8
9
|
import json
|
|
9
10
|
from functools import partial, wraps
|
|
10
|
-
from typing import Any, Callable,
|
|
11
|
+
from typing import Any, Callable, Literal
|
|
11
12
|
|
|
12
13
|
from aiohttp import web
|
|
13
14
|
from aiohttp.client import ClientResponse, ClientSession, _RequestContextManager
|
|
@@ -32,7 +33,7 @@ def async_wrap(func: Callable) -> Callable:
|
|
|
32
33
|
@wraps(func)
|
|
33
34
|
async def run(
|
|
34
35
|
*args: Any,
|
|
35
|
-
loop:
|
|
36
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
36
37
|
executor: Any = None,
|
|
37
38
|
**kwargs: dict[str, Any],
|
|
38
39
|
) -> Callable:
|
|
@@ -53,44 +54,12 @@ USER_EMAIL = "mail"
|
|
|
53
54
|
class AsyncMSAL:
|
|
54
55
|
"""AsycMSAL class.
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Use until such time as MSAL Python gets a true async version...
|
|
59
|
-
|
|
60
|
-
Tested with MSAL Python 1.13.0
|
|
61
|
-
https://github.com/AzureAD/microsoft-authentication-library-for-python
|
|
62
|
-
|
|
63
|
-
AsyncMSAL is based on the following example app
|
|
64
|
-
https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76
|
|
65
|
-
|
|
66
|
-
Use as follows:
|
|
67
|
-
|
|
68
|
-
Get the tokens via oauth
|
|
69
|
-
|
|
70
|
-
1. initiate_auth_code_flow
|
|
71
|
-
https://msal-python.readthedocs.io/en/latest/#msal.ClientApplication.initiate_auth_code_flow
|
|
72
|
-
|
|
73
|
-
The caller is expected to:
|
|
74
|
-
1. somehow store this content, typically inside the current session of the
|
|
75
|
-
server,
|
|
76
|
-
2. guide the end user (i.e. resource owner) to visit that auth_uri,
|
|
77
|
-
typically with a redirect
|
|
78
|
-
3. and then relay this dict and subsequent auth response to
|
|
79
|
-
acquire_token_by_auth_code_flow().
|
|
80
|
-
|
|
81
|
-
[1. and part of 3.] is stored by this class in the aiohttp_session
|
|
82
|
-
|
|
83
|
-
2. acquire_token_by_auth_code_flow
|
|
84
|
-
https://msal-python.readthedocs.io/en/latest/#msal.ClientApplication.acquire_token_by_auth_code_flow
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Now you are free to make requests (typically from an aiohttp server)
|
|
88
|
-
|
|
89
|
-
session = await get_session(request)
|
|
90
|
-
aiomsal = AsyncMSAL(session)
|
|
91
|
-
async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
|
|
92
|
-
res = await res.json()
|
|
57
|
+
Authorization Code Flow Helper. Learn more about auth-code-flow at
|
|
58
|
+
https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
93
59
|
|
|
60
|
+
Async based OAuth using the Microsoft Authentication Library (MSAL) for Python.
|
|
61
|
+
Blocking MSAL functions are executed in the executor thread.
|
|
62
|
+
Use until such time as MSAL Python gets a true async version.
|
|
94
63
|
"""
|
|
95
64
|
|
|
96
65
|
_token_cache: SerializableTokenCache = None
|
|
@@ -99,8 +68,8 @@ class AsyncMSAL:
|
|
|
99
68
|
|
|
100
69
|
def __init__(
|
|
101
70
|
self,
|
|
102
|
-
session:
|
|
103
|
-
save_cache:
|
|
71
|
+
session: Session | dict[str, str],
|
|
72
|
+
save_cache: Callable[[Session | dict[str, str]], None] | None = None,
|
|
104
73
|
):
|
|
105
74
|
"""Init the class.
|
|
106
75
|
|
|
@@ -149,7 +118,11 @@ class AsyncMSAL:
|
|
|
149
118
|
self.save_token_cache(self.token_cache)
|
|
150
119
|
|
|
151
120
|
def build_auth_code_flow(
|
|
152
|
-
self,
|
|
121
|
+
self,
|
|
122
|
+
redirect_uri: str,
|
|
123
|
+
scopes: list[str] | None = None,
|
|
124
|
+
prompt: Literal["login", "consent", "select_account", "none"] | None = None,
|
|
125
|
+
**kwargs: Any,
|
|
153
126
|
) -> str:
|
|
154
127
|
"""First step - Start the flow."""
|
|
155
128
|
self.session[TOKEN_CACHE] = None # type: ignore
|
|
@@ -157,7 +130,9 @@ class AsyncMSAL:
|
|
|
157
130
|
self.session[FLOW_CACHE] = res = self.app.initiate_auth_code_flow(
|
|
158
131
|
scopes or DEFAULT_SCOPES,
|
|
159
132
|
redirect_uri=redirect_uri,
|
|
160
|
-
response_mode="form_post"
|
|
133
|
+
response_mode="form_post",
|
|
134
|
+
prompt=prompt,
|
|
135
|
+
**kwargs,
|
|
161
136
|
# max_age=1209600,
|
|
162
137
|
# max allowed 86400 - 1 day
|
|
163
138
|
)
|
|
@@ -185,7 +160,7 @@ class AsyncMSAL:
|
|
|
185
160
|
None, self.acquire_token_by_auth_code_flow, auth_response
|
|
186
161
|
)
|
|
187
162
|
|
|
188
|
-
def get_token(self, scopes:
|
|
163
|
+
def get_token(self, scopes: list[str] | None = None) -> dict[str, Any] | None:
|
|
189
164
|
"""Acquire a token based on username."""
|
|
190
165
|
accounts = self.app.get_accounts()
|
|
191
166
|
if accounts:
|
|
@@ -196,7 +171,7 @@ class AsyncMSAL:
|
|
|
196
171
|
return result
|
|
197
172
|
return None
|
|
198
173
|
|
|
199
|
-
async def async_get_token(self) ->
|
|
174
|
+
async def async_get_token(self) -> dict[str, Any] | None:
|
|
200
175
|
"""Acquire a token based on username."""
|
|
201
176
|
return await asyncio.get_event_loop().run_in_executor(None, self.get_token)
|
|
202
177
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Redis tools for sessions."""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
@@ -9,7 +10,7 @@ from typing import Any, AsyncGenerator, Optional
|
|
|
9
10
|
from redis.asyncio import Redis, from_url
|
|
10
11
|
|
|
11
12
|
from aiohttp_msal.msal_async import AsyncMSAL
|
|
12
|
-
from aiohttp_msal.settings import ENV
|
|
13
|
+
from aiohttp_msal.settings import ENV as MENV
|
|
13
14
|
|
|
14
15
|
_LOGGER = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -19,15 +20,17 @@ SES_KEYS = ("mail", "name", "m_mail", "m_name")
|
|
|
19
20
|
@asynccontextmanager
|
|
20
21
|
async def get_redis() -> AsyncGenerator[Redis, None]:
|
|
21
22
|
"""Get a Redis connection."""
|
|
22
|
-
if
|
|
23
|
+
if MENV.database:
|
|
23
24
|
_LOGGER.debug("Using redis from environment")
|
|
24
|
-
yield
|
|
25
|
+
yield MENV.database
|
|
25
26
|
return
|
|
26
|
-
_LOGGER.info("Connect to Redis %s",
|
|
27
|
-
redis = from_url(
|
|
27
|
+
_LOGGER.info("Connect to Redis %s", MENV.REDIS)
|
|
28
|
+
redis = from_url(MENV.REDIS) # decode_responses=True not allowed aiohttp_session
|
|
29
|
+
MENV.database = redis
|
|
28
30
|
try:
|
|
29
31
|
yield redis
|
|
30
32
|
finally:
|
|
33
|
+
MENV.database = None # type:ignore
|
|
31
34
|
await redis.close()
|
|
32
35
|
|
|
33
36
|
|
|
@@ -45,10 +48,11 @@ async def session_iter(
|
|
|
45
48
|
if match and not all(isinstance(v, str) for v in match.values()):
|
|
46
49
|
raise ValueError("match values must be strings")
|
|
47
50
|
async for key in redis.scan_iter(
|
|
48
|
-
count=100, match=key_match or f"{
|
|
51
|
+
count=100, match=key_match or f"{MENV.COOKIE_NAME}*"
|
|
49
52
|
):
|
|
53
|
+
if not isinstance(key, str):
|
|
54
|
+
key = key.decode()
|
|
50
55
|
sval = await redis.get(key)
|
|
51
|
-
_LOGGER.debug("Session: %s = %s", key, sval)
|
|
52
56
|
created, ses = 0, {}
|
|
53
57
|
try:
|
|
54
58
|
val = json.loads(sval) # type: ignore
|
|
@@ -110,11 +114,67 @@ def _session_factory(key: str, created: str, session: dict) -> AsyncMSAL:
|
|
|
110
114
|
return AsyncMSAL(session, save_cache=save_cache)
|
|
111
115
|
|
|
112
116
|
|
|
113
|
-
async def get_session(
|
|
117
|
+
async def get_session(
|
|
118
|
+
email: str, *, redis: Optional[Redis] = None, scope: str = ""
|
|
119
|
+
) -> AsyncMSAL:
|
|
114
120
|
"""Get a session from Redis."""
|
|
121
|
+
cnt = 0
|
|
115
122
|
async with AsyncExitStack() as stack:
|
|
116
123
|
if redis is None:
|
|
117
124
|
redis = await stack.enter_async_context(get_redis())
|
|
118
125
|
async for key, created, session in session_iter(redis, match={"mail": email}):
|
|
126
|
+
cnt += 1
|
|
127
|
+
if scope and scope not in str(session.get("token_cache")).lower():
|
|
128
|
+
continue
|
|
119
129
|
return _session_factory(key, str(created), session)
|
|
120
|
-
|
|
130
|
+
msg = f"Session for {email}"
|
|
131
|
+
if not scope:
|
|
132
|
+
raise ValueError(f"{msg} not found")
|
|
133
|
+
raise ValueError(f"{msg} with scope {scope} not found ({cnt} checked)")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def redis_get_json(key: str) -> list | dict | None:
|
|
137
|
+
"""Get a key from redis."""
|
|
138
|
+
res = await MENV.database.get(key)
|
|
139
|
+
if isinstance(res, (str, bytes, bytearray)):
|
|
140
|
+
return json.loads(res)
|
|
141
|
+
if res is not None:
|
|
142
|
+
_LOGGER.warning("Unexpected type for %s: %s", key, type(res))
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def redis_get(key: str) -> str | None:
|
|
147
|
+
"""Get a key from redis."""
|
|
148
|
+
res = await MENV.database.get(key)
|
|
149
|
+
if isinstance(res, str):
|
|
150
|
+
return res
|
|
151
|
+
if isinstance(res, (bytes, bytearray)):
|
|
152
|
+
return res.decode()
|
|
153
|
+
if res is not None:
|
|
154
|
+
_LOGGER.warning("Unexpected type for %s: %s", key, type(res))
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def redis_set_set(key: str, new_set: set[str]) -> None:
|
|
159
|
+
"""Set the value of a set in redis."""
|
|
160
|
+
cur_set = set(
|
|
161
|
+
s if isinstance(s, str) else s.decode()
|
|
162
|
+
for s in await MENV.database.smembers(key)
|
|
163
|
+
)
|
|
164
|
+
dif = list(cur_set - new_set)
|
|
165
|
+
if dif:
|
|
166
|
+
_LOGGER.warning("%s: removing %s", key, dif)
|
|
167
|
+
await MENV.database.srem(key, *dif)
|
|
168
|
+
|
|
169
|
+
dif = list(new_set - cur_set)
|
|
170
|
+
if dif:
|
|
171
|
+
_LOGGER.info("%s: adding %s", key, dif)
|
|
172
|
+
await MENV.database.sadd(key, *dif)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def redis_scan(match_str: str) -> list[str]:
|
|
176
|
+
"""Return a list of matching keys."""
|
|
177
|
+
return [
|
|
178
|
+
s if isinstance(s, str) else s.decode()
|
|
179
|
+
async for s in MENV.database.scan_iter(match=match_str)
|
|
180
|
+
]
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"""Settings."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
|
3
4
|
|
|
4
5
|
from aiohttp_msal.settings_base import SettingsBase, Var
|
|
5
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from redis.asyncio import Redis
|
|
9
|
+
else:
|
|
10
|
+
Redis = Any
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
class MSALSettings(SettingsBase):
|
|
8
14
|
"""Settings."""
|
|
@@ -26,12 +32,12 @@ class MSALSettings(SettingsBase):
|
|
|
26
32
|
|
|
27
33
|
login_callback: list[Callable[[Any], Awaitable[Any]]] = []
|
|
28
34
|
"""A list of callbacks to execute on successful login."""
|
|
29
|
-
info: dict[str, Callable[[Any],
|
|
35
|
+
info: dict[str, Callable[[Any], Any | Awaitable[Any]]] = {}
|
|
30
36
|
"""List of attributes to return in /user/info."""
|
|
31
37
|
|
|
32
38
|
REDIS = "redis://redis1:6379"
|
|
33
39
|
"""OPTIONAL: Redis database connection used by app_init_redis_session()."""
|
|
34
|
-
database:
|
|
40
|
+
database: Redis
|
|
35
41
|
"""Store the Redis connection when using app_init_redis_session()."""
|
|
36
42
|
|
|
37
43
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Settings Base."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
2
4
|
import logging
|
|
3
5
|
import os
|
|
4
6
|
from pathlib import Path
|
|
@@ -9,7 +11,7 @@ class Var: # pylint: disable=too-few-public-methods
|
|
|
9
11
|
"""Variable settings."""
|
|
10
12
|
|
|
11
13
|
@staticmethod
|
|
12
|
-
def from_value(val: Any)
|
|
14
|
+
def from_value(val: Any) -> Var:
|
|
13
15
|
"""Ensure the return is an instance of Var."""
|
|
14
16
|
return val if isinstance(val, Var) else Var(type(val))
|
|
15
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.6.
|
|
2
|
+
Name: aiohttp_msal
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
|
|
5
5
|
Home-page: https://github.com/kellerza/aiohttp_msal
|
|
6
6
|
Author: Johann Kellerman
|
|
@@ -12,20 +12,20 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: msal>=1.
|
|
21
|
+
Requires-Dist: msal>=1.30.0
|
|
22
22
|
Requires-Dist: aiohttp_session>=2.12
|
|
23
23
|
Requires-Dist: aiohttp>=3.8
|
|
24
24
|
Provides-Extra: redis
|
|
25
25
|
Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
|
|
26
26
|
Provides-Extra: tests
|
|
27
|
-
Requires-Dist: black==
|
|
28
|
-
Requires-Dist: pylint; extra == "tests"
|
|
27
|
+
Requires-Dist: black==24.8.0; extra == "tests"
|
|
28
|
+
Requires-Dist: pylint==3.2.6; extra == "tests"
|
|
29
29
|
Requires-Dist: flake8; extra == "tests"
|
|
30
30
|
Requires-Dist: pytest-aiohttp; extra == "tests"
|
|
31
31
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -35,9 +35,13 @@ Requires-Dist: pytest-env; extra == "tests"
|
|
|
35
35
|
|
|
36
36
|
# aiohttp_msal Python library
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Authorization Code Flow Helper. Learn more about auth-code-flow at
|
|
39
|
+
<https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow>
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
Async based OAuth using the Microsoft Authentication Library (MSAL) for Python.
|
|
42
|
+
|
|
43
|
+
Blocking MSAL functions are executed in the executor thread.
|
|
44
|
+
Should be useful until such time as MSAL Python gets a true async version.
|
|
41
45
|
|
|
42
46
|
Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
|
|
43
47
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
[tool.black]
|
|
3
|
+
line-length = 88
|
|
4
|
+
target-version = ['py36', 'py37', 'py38']
|
|
5
|
+
include = '\.pyi?$'
|
|
6
|
+
|
|
7
|
+
[tool.ruff]
|
|
8
|
+
line-length = 121
|
|
9
|
+
# pyflakes, pycodestyle, isort
|
|
10
|
+
include = ["tests/*.py", "aiohttp_msal/**/*.py"]
|
|
11
|
+
|
|
12
|
+
[tool.ruff.lint]
|
|
13
|
+
select = ["F", "E", "W", "I001"]
|
|
14
|
+
|
|
15
|
+
[tool.ruff.lint.flake8-import-conventions]
|
|
16
|
+
# Declare the banned `from` imports.
|
|
17
|
+
banned-from = ["typing"]
|
|
18
|
+
|
|
19
|
+
[tool.ruff.lint.isort]
|
|
20
|
+
no-lines-before = ["future", "standard-library"]
|
|
21
|
+
|
|
22
|
+
[tool.mypy]
|
|
23
|
+
disallow_untyped_defs = true
|
|
24
|
+
ignore_missing_imports = true
|
|
25
|
+
|
|
26
|
+
# https://stackoverflow.com/questions/64162504/settings-for-pylint-in-setup-cfg-are-not-getting-used
|
|
27
|
+
[tool.pylint.'MESSAGES CONTROL']
|
|
28
|
+
max-line-length = 120
|
|
29
|
+
good-names = ["db", "fr", "cr", "k", "i"]
|
|
30
|
+
disable = [
|
|
31
|
+
"line-too-long",
|
|
32
|
+
"unsubscriptable-object",
|
|
33
|
+
"unused-argument",
|
|
34
|
+
"too-many-branches",
|
|
35
|
+
"too-many-locals",
|
|
36
|
+
"too-many-statements",
|
|
37
|
+
"too-many-instance-attributes",
|
|
38
|
+
"too-few-public-methods",
|
|
39
|
+
"R0401",
|
|
40
|
+
"R0801",
|
|
41
|
+
"wrong-import-order",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.pylint.design]
|
|
45
|
+
# limiting the number of returns might discourage
|
|
46
|
+
# the use of guard clauses. So we increase the
|
|
47
|
+
# allowed number of returns from 6 to 8
|
|
48
|
+
max-returns = 8
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
pythonpath = [".", "src"]
|
|
52
|
+
filterwarnings = "ignore:.+@coroutine.+deprecated.+"
|
|
53
|
+
testpaths = "tests"
|
|
54
|
+
norecursedirs = [".git", "modules"]
|
|
55
|
+
log_cli = true
|
|
56
|
+
log_cli_level = "DEBUG"
|
|
57
|
+
asyncio_mode = "auto"
|
|
58
|
+
env = [
|
|
59
|
+
"X_SP_APP_PW=p1",
|
|
60
|
+
"X_SP_APP_ID=i1",
|
|
61
|
+
"X_SP_AUTHORITY=a1",
|
|
62
|
+
"SP_APP_PW=p2",
|
|
63
|
+
"SP_APP_ID=i2",
|
|
64
|
+
"SP_AUTHORITY=a2",
|
|
65
|
+
]
|
|
@@ -15,18 +15,17 @@ classifiers =
|
|
|
15
15
|
Natural Language :: English
|
|
16
16
|
Programming Language :: Python :: 3
|
|
17
17
|
Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Programming Language :: Python :: 3.9
|
|
19
18
|
Programming Language :: Python :: 3.10
|
|
20
19
|
Programming Language :: Python :: 3.11
|
|
20
|
+
Programming Language :: Python :: 3.12
|
|
21
21
|
keywords = msal, oauth, aiohttp, asyncio
|
|
22
22
|
|
|
23
23
|
[options]
|
|
24
24
|
packages = find:
|
|
25
|
-
python_requires = >=3.
|
|
25
|
+
python_requires = >=3.10
|
|
26
26
|
include_package_data = True
|
|
27
|
-
tests_requires = file: requirements_test.txt
|
|
28
27
|
install_requires =
|
|
29
|
-
msal>=1.
|
|
28
|
+
msal>=1.30.0
|
|
30
29
|
aiohttp_session>=2.12
|
|
31
30
|
aiohttp>=3.8
|
|
32
31
|
zip_safe = true
|
|
@@ -35,8 +34,8 @@ zip_safe = true
|
|
|
35
34
|
redis =
|
|
36
35
|
aiohttp_session[aioredis]>=2.12
|
|
37
36
|
tests =
|
|
38
|
-
black==
|
|
39
|
-
pylint
|
|
37
|
+
black==24.8.0
|
|
38
|
+
pylint==3.2.6
|
|
40
39
|
flake8
|
|
41
40
|
pytest-aiohttp
|
|
42
41
|
pytest
|
|
@@ -50,20 +49,6 @@ disallow_untyped_defs = True
|
|
|
50
49
|
[mypy-msal.*]
|
|
51
50
|
ignore_missing_imports = True
|
|
52
51
|
|
|
53
|
-
[tool:pytest]
|
|
54
|
-
filterwarnings =
|
|
55
|
-
ignore:.+@coroutine.+deprecated.+
|
|
56
|
-
log_cli = True
|
|
57
|
-
log_cli_level = DEBUG
|
|
58
|
-
asyncio_mode = auto
|
|
59
|
-
env =
|
|
60
|
-
X_SP_APP_PW=p1
|
|
61
|
-
X_SP_APP_ID=i1
|
|
62
|
-
X_SP_AUTHORITY=a1
|
|
63
|
-
SP_APP_PW=p2
|
|
64
|
-
SP_APP_ID=i2
|
|
65
|
-
SP_AUTHORITY=a2
|
|
66
|
-
|
|
67
52
|
[egg_info]
|
|
68
53
|
tag_build =
|
|
69
54
|
tag_date = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|