aiohttp-msal 0.6.4__py3-none-any.whl → 0.6.6__py3-none-any.whl
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/__init__.py +3 -3
- aiohttp_msal/msal_async.py +10 -6
- aiohttp_msal/redis_tools.py +9 -1
- aiohttp_msal/routes.py +2 -8
- aiohttp_msal/settings.py +1 -1
- aiohttp_msal/user_info.py +1 -1
- {aiohttp_msal-0.6.4.dist-info → aiohttp_msal-0.6.6.dist-info}/METADATA +1 -1
- aiohttp_msal-0.6.6.dist-info/RECORD +18 -0
- tests/test_msal_async.py +1 -0
- tests/test_redis_tools.py +59 -0
- aiohttp_msal-0.6.4.dist-info/RECORD +0 -17
- {aiohttp_msal-0.6.4.dist-info → aiohttp_msal-0.6.6.dist-info}/LICENSE +0 -0
- {aiohttp_msal-0.6.4.dist-info → aiohttp_msal-0.6.6.dist-info}/WHEEL +0 -0
- {aiohttp_msal-0.6.4.dist-info → aiohttp_msal-0.6.6.dist-info}/top_level.txt +0 -0
- {aiohttp_msal-0.6.4.dist-info → aiohttp_msal-0.6.6.dist-info}/zip-safe +0 -0
aiohttp_msal/__init__.py
CHANGED
|
@@ -8,12 +8,12 @@ from aiohttp import ClientSession, web
|
|
|
8
8
|
from aiohttp_session import get_session
|
|
9
9
|
from aiohttp_session import setup as _setup
|
|
10
10
|
|
|
11
|
-
from .msal_async import AsyncMSAL
|
|
12
|
-
from .settings import ENV
|
|
11
|
+
from aiohttp_msal.msal_async import AsyncMSAL
|
|
12
|
+
from aiohttp_msal.settings import ENV
|
|
13
13
|
|
|
14
14
|
_LOGGER = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
VERSION = "0.6.
|
|
16
|
+
VERSION = "0.6.6"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def msal_session(*args: Callable[[AsyncMSAL], Union[Any, Awaitable[Any]]]) -> Callable:
|
aiohttp_msal/msal_async.py
CHANGED
|
@@ -14,7 +14,7 @@ from aiohttp.client import ClientResponse, ClientSession, _RequestContextManager
|
|
|
14
14
|
from aiohttp_session import Session
|
|
15
15
|
from msal import ConfidentialClientApplication, SerializableTokenCache
|
|
16
16
|
|
|
17
|
-
from .settings import ENV
|
|
17
|
+
from aiohttp_msal.settings import ENV
|
|
18
18
|
|
|
19
19
|
HTTP_GET = "get"
|
|
20
20
|
HTTP_POST = "post"
|
|
@@ -23,7 +23,7 @@ HTTP_PATCH = "patch"
|
|
|
23
23
|
HTTP_DELETE = "delete"
|
|
24
24
|
HTTP_ALLOWED = [HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE]
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
DEFAULT_SCOPES = ["User.Read", "User.Read.All"]
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def async_wrap(func: Callable) -> Callable:
|
|
@@ -148,12 +148,14 @@ class AsyncMSAL:
|
|
|
148
148
|
if hasattr(self, "save_token_cache"):
|
|
149
149
|
self.save_token_cache(self.token_cache)
|
|
150
150
|
|
|
151
|
-
def build_auth_code_flow(
|
|
151
|
+
def build_auth_code_flow(
|
|
152
|
+
self, redirect_uri: str, scopes: Optional[list[str]] = None
|
|
153
|
+
) -> str:
|
|
152
154
|
"""First step - Start the flow."""
|
|
153
155
|
self.session[TOKEN_CACHE] = None # type: ignore
|
|
154
156
|
self.session[USER_EMAIL] = None # type: ignore
|
|
155
157
|
self.session[FLOW_CACHE] = res = self.app.initiate_auth_code_flow(
|
|
156
|
-
|
|
158
|
+
scopes or DEFAULT_SCOPES,
|
|
157
159
|
redirect_uri=redirect_uri,
|
|
158
160
|
response_mode="form_post"
|
|
159
161
|
# max_age=1209600,
|
|
@@ -183,11 +185,13 @@ class AsyncMSAL:
|
|
|
183
185
|
None, self.acquire_token_by_auth_code_flow, auth_response
|
|
184
186
|
)
|
|
185
187
|
|
|
186
|
-
def get_token(self) -> Optional[dict[str, Any]]:
|
|
188
|
+
def get_token(self, scopes: Optional[list[str]] = None) -> Optional[dict[str, Any]]:
|
|
187
189
|
"""Acquire a token based on username."""
|
|
188
190
|
accounts = self.app.get_accounts()
|
|
189
191
|
if accounts:
|
|
190
|
-
result = self.app.acquire_token_silent(
|
|
192
|
+
result = self.app.acquire_token_silent(
|
|
193
|
+
scopes=scopes or DEFAULT_SCOPES, account=accounts[0]
|
|
194
|
+
)
|
|
191
195
|
self._save_token_cache()
|
|
192
196
|
return result
|
|
193
197
|
return None
|
aiohttp_msal/redis_tools.py
CHANGED
|
@@ -42,10 +42,13 @@ async def session_iter(
|
|
|
42
42
|
match: Filter based on session content (i.e. mail/name)
|
|
43
43
|
key_match: Filter the Redis keys. Defaults to ENV.cookie_name
|
|
44
44
|
"""
|
|
45
|
+
if match and not all(isinstance(v, str) for v in match.values()):
|
|
46
|
+
raise ValueError("match values must be strings")
|
|
45
47
|
async for key in redis.scan_iter(
|
|
46
48
|
count=100, match=key_match or f"{ENV.COOKIE_NAME}*"
|
|
47
49
|
):
|
|
48
50
|
sval = await redis.get(key)
|
|
51
|
+
_LOGGER.debug("Session: %s = %s", key, sval)
|
|
49
52
|
created, ses = 0, {}
|
|
50
53
|
try:
|
|
51
54
|
val = json.loads(sval) # type: ignore
|
|
@@ -55,7 +58,12 @@ async def session_iter(
|
|
|
55
58
|
pass
|
|
56
59
|
if match:
|
|
57
60
|
# Ensure we match all the supplied terms
|
|
58
|
-
|
|
61
|
+
matches = 0
|
|
62
|
+
for mkey, mval in match.items():
|
|
63
|
+
if not (isinstance(ses.get(mkey), str) and mval in ses[mkey]):
|
|
64
|
+
break
|
|
65
|
+
matches += 1
|
|
66
|
+
if matches != len(match):
|
|
59
67
|
continue
|
|
60
68
|
yield key, created, ses
|
|
61
69
|
|
aiohttp_msal/routes.py
CHANGED
|
@@ -7,19 +7,16 @@ from urllib.parse import urljoin
|
|
|
7
7
|
from aiohttp import web
|
|
8
8
|
from aiohttp_session import get_session, new_session
|
|
9
9
|
|
|
10
|
+
from aiohttp_msal import _LOGGER, ENV, authenticated, msal_session
|
|
11
|
+
from aiohttp_msal.msal_async import FLOW_CACHE, AsyncMSAL
|
|
10
12
|
from aiohttp_msal.user_info import get_manager_info, get_user_info
|
|
11
13
|
|
|
12
|
-
from . import _LOGGER, ENV, authenticated, msal_session
|
|
13
|
-
from .msal_async import FLOW_CACHE, AsyncMSAL
|
|
14
|
-
|
|
15
14
|
ROUTES = web.RouteTableDef()
|
|
16
15
|
|
|
17
16
|
URI_USER_LOGIN = "/user/login"
|
|
18
17
|
URI_USER_AUTHORIZED = "/user/authorized"
|
|
19
18
|
SESSION_REDIRECT = "redirect"
|
|
20
19
|
|
|
21
|
-
# ic.disable() # remove debug prints
|
|
22
|
-
|
|
23
20
|
|
|
24
21
|
def get_route(request: web.Request, url: str) -> str:
|
|
25
22
|
"""Retrieve server route from request.
|
|
@@ -70,14 +67,12 @@ async def user_authorized(request: web.Request) -> web.Response:
|
|
|
70
67
|
)
|
|
71
68
|
|
|
72
69
|
if not request.cookies.get(ENV.COOKIE_NAME):
|
|
73
|
-
# ic(request.cookies.keys())
|
|
74
70
|
cookies = dict(request.cookies.items())
|
|
75
71
|
msg.append(f"<b>Expecting '{ENV.COOKIE_NAME}' in cookies</b>")
|
|
76
72
|
_LOGGER.fatal("Cookie should be set with Samesite:None")
|
|
77
73
|
msg.append(html_table(cookies))
|
|
78
74
|
|
|
79
75
|
elif not session.get(FLOW_CACHE):
|
|
80
|
-
# ic(session)
|
|
81
76
|
msg.append(f"<b>Expecting '{FLOW_CACHE}' in session</b>")
|
|
82
77
|
msg.append(f"- Session.new: {session.new}")
|
|
83
78
|
msg.append(html_table(session))
|
|
@@ -145,7 +140,6 @@ async def user_debug(request: web.Request) -> web.Response:
|
|
|
145
140
|
"X-Forw-For IP": request.headers.get("X-Forwarded-For", ""),
|
|
146
141
|
},
|
|
147
142
|
}
|
|
148
|
-
# ic(debug)
|
|
149
143
|
session["debug_previous"] = time.time()
|
|
150
144
|
return web.json_response(debug)
|
|
151
145
|
|
aiohttp_msal/settings.py
CHANGED
aiohttp_msal/user_info.py
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
aiohttp_msal/__init__.py,sha256=R-OEq-FJnT_uYiiSYeltrUqTSnPTcFqR8SKSbVszues,3025
|
|
2
|
+
aiohttp_msal/msal_async.py,sha256=HjLJ5gOengQNjuN1Nod1U7J4sqChTN_B4QuXjVJkheA,9964
|
|
3
|
+
aiohttp_msal/redis_tools.py,sha256=3Zyl3-sdT_SgxACdWeeBtWGywBzwyF9Ekm9CC2oCb90,4082
|
|
4
|
+
aiohttp_msal/routes.py,sha256=UmUQkFFubFK2N1IAryDPwN7UBMCgow87H6zhNbKiE1I,8227
|
|
5
|
+
aiohttp_msal/settings.py,sha256=Nz0GO62IMtQdfdZqJ8o8w331i1sxfGuaIo4j533u4NM,1243
|
|
6
|
+
aiohttp_msal/settings_base.py,sha256=pmVmzTtaGEgRh-AMGy0HdhF1JvoZhZp42G3PL_ILHLw,2892
|
|
7
|
+
aiohttp_msal/user_info.py,sha256=02DYVbP8lnLNCm1HQEGZY7tktWRHvEAuHf6fLj2Kcp8,1765
|
|
8
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
tests/test_init.py,sha256=sHmt7yNDlcu5JHrpM_gim0NeLce0NwUAMM3HAdGoo58,75
|
|
10
|
+
tests/test_msal_async.py,sha256=Vi2Jx8_GG-iMfKPY4UCvmX-0U1Bl2xswrAyWGIRP-Ac,364
|
|
11
|
+
tests/test_redis_tools.py,sha256=x9Yd9WOGd2ZW5fJwyiAta5wMRkSM3prsCPhkVe6CS3Q,1868
|
|
12
|
+
tests/test_settings.py,sha256=z-qtUs1zl5Q9NEux051eebyPnArLZ_OfZu65FKz0N4Y,333
|
|
13
|
+
aiohttp_msal-0.6.6.dist-info/LICENSE,sha256=H1aGfkSfZFwK3q4INn9mUldOJGZy-ZXu5-65K9Glunw,1080
|
|
14
|
+
aiohttp_msal-0.6.6.dist-info/METADATA,sha256=XDKJEJXQkOyLveVhob8NukGwprF14ZRLX_geNu8rJm4,4565
|
|
15
|
+
aiohttp_msal-0.6.6.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
16
|
+
aiohttp_msal-0.6.6.dist-info/top_level.txt,sha256=QPWOi5JtacVEdbaU5bJExc9o-cCT2Lufx0QhUpsv5_E,19
|
|
17
|
+
aiohttp_msal-0.6.6.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
|
+
aiohttp_msal-0.6.6.dist-info/RECORD,,
|
tests/test_msal_async.py
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Test redis tools."""
|
|
2
|
+
from json import dumps
|
|
3
|
+
from typing import AsyncGenerator
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock, Mock, call
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from aiohttp_msal.redis_tools import Redis, session_iter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def redis() -> Redis:
|
|
13
|
+
"""Get a redis Mock instance."""
|
|
14
|
+
testdata = {
|
|
15
|
+
"a": dumps({"created": 1, "session": {"key": "a", "a": 1, "b": "2a"}}),
|
|
16
|
+
"b": dumps({"created": 2, "session": {"key": "b", "a": 1, "b": "2b"}}),
|
|
17
|
+
"c": dumps({"created": 3, "session": {"key": "c", "a": 5, "b": "6c"}}),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async def scan_iter(*, count: int, match: str) -> AsyncGenerator[str, None]:
|
|
21
|
+
"""Mock keys."""
|
|
22
|
+
assert count == 100
|
|
23
|
+
assert match == "a*"
|
|
24
|
+
for key in testdata:
|
|
25
|
+
yield key
|
|
26
|
+
|
|
27
|
+
red = Mock()
|
|
28
|
+
red.scan_iter = MagicMock(side_effect=scan_iter)
|
|
29
|
+
red.get = AsyncMock(side_effect=list(testdata.values()))
|
|
30
|
+
return red
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_session_iter_fail(redis: Redis) -> None:
|
|
35
|
+
"""Test session iter."""
|
|
36
|
+
match = {"a": 1}
|
|
37
|
+
with pytest.raises(ValueError):
|
|
38
|
+
async for _ in session_iter(redis, match=match, key_match="a*"):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
match = {"a": "1"}
|
|
42
|
+
async for _ in session_iter(redis, match=match, key_match="a*"):
|
|
43
|
+
assert False, "no match expected"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_session_iter(redis: Redis) -> None:
|
|
48
|
+
"""Test session iter."""
|
|
49
|
+
match = {"b": "2"}
|
|
50
|
+
expected = ["a", "b"]
|
|
51
|
+
async for key, created, ses in session_iter(redis, match=match, key_match="a*"):
|
|
52
|
+
assert expected.pop(0) == key
|
|
53
|
+
assert key == ses["key"]
|
|
54
|
+
assert created in (1, 2)
|
|
55
|
+
assert key in ("a", "b")
|
|
56
|
+
|
|
57
|
+
assert redis.scan_iter.call_args[1]["match"] == "a*"
|
|
58
|
+
assert redis.scan_iter.call_args[1]["count"] == 100
|
|
59
|
+
assert redis.scan_iter.call_args_list == [call(count=100, match="a*")]
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
aiohttp_msal/__init__.py,sha256=78mjldM51U-GlDF4XnAqMJKEsUveWQSknwKPazzXv3o,3001
|
|
2
|
-
aiohttp_msal/msal_async.py,sha256=lyiNFS8iJpg2WTwOz7ogmrTwN3WYaRQXos9hYiPLLwI,9798
|
|
3
|
-
aiohttp_msal/redis_tools.py,sha256=eEYGJTCWtpyBvmI7IAs2jInypsyzbQP_RAVQnAyRgtE,3737
|
|
4
|
-
aiohttp_msal/routes.py,sha256=c-w5wHaLAYGEqZvfZ8PnzzRh60asLqdUa30lvSANdYM,8319
|
|
5
|
-
aiohttp_msal/settings.py,sha256=ZZn7D6QmIyQSvuqCAoTacKRXYfopqK4P74eVdPCw-uI,1231
|
|
6
|
-
aiohttp_msal/settings_base.py,sha256=pmVmzTtaGEgRh-AMGy0HdhF1JvoZhZp42G3PL_ILHLw,2892
|
|
7
|
-
aiohttp_msal/user_info.py,sha256=oIu90lgi71G27MVkw4pGH8wZqjXk8kxmW0fn2LKxOHc,1753
|
|
8
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
tests/test_init.py,sha256=sHmt7yNDlcu5JHrpM_gim0NeLce0NwUAMM3HAdGoo58,75
|
|
10
|
-
tests/test_msal_async.py,sha256=31MCoAbUiyUhc4SkebUKpjLDHozEBko-QgEBSHjfSoM,332
|
|
11
|
-
tests/test_settings.py,sha256=z-qtUs1zl5Q9NEux051eebyPnArLZ_OfZu65FKz0N4Y,333
|
|
12
|
-
aiohttp_msal-0.6.4.dist-info/LICENSE,sha256=H1aGfkSfZFwK3q4INn9mUldOJGZy-ZXu5-65K9Glunw,1080
|
|
13
|
-
aiohttp_msal-0.6.4.dist-info/METADATA,sha256=67u7FqAWttPQktDdb20TQyxIsv-zDEXcmOFmdDILcmk,4565
|
|
14
|
-
aiohttp_msal-0.6.4.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
15
|
-
aiohttp_msal-0.6.4.dist-info/top_level.txt,sha256=QPWOi5JtacVEdbaU5bJExc9o-cCT2Lufx0QhUpsv5_E,19
|
|
16
|
-
aiohttp_msal-0.6.4.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
17
|
-
aiohttp_msal-0.6.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|