aiohttp-msal 0.6.8__py3-none-any.whl → 0.7.1__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 +32 -5
- aiohttp_msal/routes.py +5 -5
- aiohttp_msal/settings.py +1 -1
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/METADATA +1 -1
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/RECORD +10 -10
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/WHEEL +1 -1
- tests/test_init.py +76 -1
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/LICENSE +0 -0
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/top_level.txt +0 -0
- {aiohttp_msal-0.6.8.dist-info → aiohttp_msal-0.7.1.dist-info}/zip-safe +0 -0
aiohttp_msal/__init__.py
CHANGED
|
@@ -14,7 +14,7 @@ from aiohttp_msal.settings import ENV
|
|
|
14
14
|
|
|
15
15
|
_LOGGER = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
-
VERSION = "0.
|
|
17
|
+
VERSION = "0.7.1"
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def msal_session(
|
|
@@ -32,10 +32,17 @@ def msal_session(
|
|
|
32
32
|
ses = AsyncMSAL(session=await get_session(request))
|
|
33
33
|
for c_b in callbacks:
|
|
34
34
|
_ok = await c_b(ses) if iscoroutinefunction(c_b) else c_b(ses)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
|
|
36
|
+
if at_least_one:
|
|
37
|
+
if _ok:
|
|
38
|
+
return await func(request=request, ses=ses)
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
if not _ok:
|
|
38
42
|
raise web.HTTPForbidden
|
|
43
|
+
|
|
44
|
+
if at_least_one:
|
|
45
|
+
raise web.HTTPForbidden
|
|
39
46
|
return await func(request=request, ses=ses)
|
|
40
47
|
|
|
41
48
|
assert iscoroutinefunction(func), f"Function needs to be a coroutine: {func}"
|
|
@@ -46,11 +53,31 @@ def msal_session(
|
|
|
46
53
|
return _session
|
|
47
54
|
|
|
48
55
|
|
|
49
|
-
def
|
|
56
|
+
def auth_ok(ses: AsyncMSAL) -> bool:
|
|
50
57
|
"""Test if session was authenticated."""
|
|
51
58
|
return bool(ses.mail)
|
|
52
59
|
|
|
53
60
|
|
|
61
|
+
def auth_or(
|
|
62
|
+
*args: typing.Callable[[AsyncMSAL], bool | typing.Awaitable[bool]]
|
|
63
|
+
) -> typing.Callable[[AsyncMSAL], typing.Awaitable[bool]]:
|
|
64
|
+
"""Ensure either of the methods is valid. An alternative to at_least_one=True.
|
|
65
|
+
|
|
66
|
+
Arguments can include a list of function to perform login tests etc."""
|
|
67
|
+
|
|
68
|
+
async def or_auth(ses: AsyncMSAL) -> bool:
|
|
69
|
+
"""Or."""
|
|
70
|
+
for arg in args:
|
|
71
|
+
if iscoroutinefunction(arg):
|
|
72
|
+
if await arg(ses):
|
|
73
|
+
return True
|
|
74
|
+
elif arg(ses):
|
|
75
|
+
return True
|
|
76
|
+
raise web.HTTPForbidden
|
|
77
|
+
|
|
78
|
+
return or_auth
|
|
79
|
+
|
|
80
|
+
|
|
54
81
|
async def app_init_redis_session(
|
|
55
82
|
app: web.Application, max_age: int = 3600 * 24 * 90
|
|
56
83
|
) -> None:
|
aiohttp_msal/routes.py
CHANGED
|
@@ -8,7 +8,7 @@ from urllib.parse import urljoin
|
|
|
8
8
|
from aiohttp import web
|
|
9
9
|
from aiohttp_session import get_session, new_session
|
|
10
10
|
|
|
11
|
-
from aiohttp_msal import _LOGGER, ENV,
|
|
11
|
+
from aiohttp_msal import _LOGGER, ENV, auth_ok, msal_session
|
|
12
12
|
from aiohttp_msal.msal_async import FLOW_CACHE, AsyncMSAL
|
|
13
13
|
from aiohttp_msal.user_info import get_manager_info, get_user_info
|
|
14
14
|
|
|
@@ -145,14 +145,14 @@ async def user_debug(request: web.Request) -> web.Response:
|
|
|
145
145
|
return web.json_response(debug)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
ENV.info["authenticated"] =
|
|
148
|
+
ENV.info["authenticated"] = auth_ok
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
@ROUTES.get("/user/info")
|
|
152
152
|
@msal_session()
|
|
153
153
|
async def user_info(request: web.Request, ses: AsyncMSAL) -> web.Response:
|
|
154
154
|
"""User info handler."""
|
|
155
|
-
if not
|
|
155
|
+
if not auth_ok(ses):
|
|
156
156
|
return web.json_response({"authenticated": False})
|
|
157
157
|
|
|
158
158
|
debug = request.query.get("debug", False)
|
|
@@ -182,7 +182,7 @@ async def user_info(request: web.Request, ses: AsyncMSAL) -> web.Response:
|
|
|
182
182
|
|
|
183
183
|
@ROUTES.get("/user/logout")
|
|
184
184
|
@ROUTES.get("/user/logout/{to:.+$}")
|
|
185
|
-
@msal_session(
|
|
185
|
+
@msal_session(auth_ok)
|
|
186
186
|
async def user_logout(request: web.Request, ses: AsyncMSAL) -> web.Response:
|
|
187
187
|
"""Redirect to MS graph login page."""
|
|
188
188
|
ses.session.clear()
|
|
@@ -202,7 +202,7 @@ async def user_logout(request: web.Request, ses: AsyncMSAL) -> web.Response:
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
@ROUTES.get("/user/photo")
|
|
205
|
-
@msal_session(
|
|
205
|
+
@msal_session(auth_ok)
|
|
206
206
|
async def user_photo(request: web.Request, ses: AsyncMSAL) -> web.StreamResponse:
|
|
207
207
|
"""Photo."""
|
|
208
208
|
async with ses.get("https://graph.microsoft.com/v1.0/me/photo/$value") as res:
|
aiohttp_msal/settings.py
CHANGED
|
@@ -37,7 +37,7 @@ class MSALSettings(SettingsBase):
|
|
|
37
37
|
|
|
38
38
|
REDIS = "redis://redis1:6379"
|
|
39
39
|
"""OPTIONAL: Redis database connection used by app_init_redis_session()."""
|
|
40
|
-
database: Redis
|
|
40
|
+
database: Redis = None # type: ignore
|
|
41
41
|
"""Store the Redis connection when using app_init_redis_session()."""
|
|
42
42
|
|
|
43
43
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
aiohttp_msal/__init__.py,sha256
|
|
1
|
+
aiohttp_msal/__init__.py,sha256=-8b6kR9wbqLoWmAVw4MsU5GYv5EK8IWoPFJJLr_UsHk,3850
|
|
2
2
|
aiohttp_msal/msal_async.py,sha256=afvfh7gZrXk5KO7Umb9jAnQu4jdg_iVlgaTnxS3JgNM,8899
|
|
3
3
|
aiohttp_msal/redis_tools.py,sha256=zgRACVxm2wPkbEHtA6VmArsd-QQlKn-crlq1XlFbjEY,5919
|
|
4
|
-
aiohttp_msal/routes.py,sha256=
|
|
5
|
-
aiohttp_msal/settings.py,sha256=
|
|
4
|
+
aiohttp_msal/routes.py,sha256=gUkNJknrR0_9ohdgMejWCQXfcobPltuAl_3C4wthZAM,8198
|
|
5
|
+
aiohttp_msal/settings.py,sha256=hWVJdtqcdAkqqN5I4GINJIZSFGhEuoBImM26NrhqY_M,1341
|
|
6
6
|
aiohttp_msal/settings_base.py,sha256=m4tmurnq8xipVNAa-Dh4ii9Rsu6gg39F4aDJNHPLwiI,2919
|
|
7
7
|
aiohttp_msal/user_info.py,sha256=fijBUbl5g1AVgrpOl-2ZY-eQCCWcu4YqcA0QaMQrcWw,1766
|
|
8
8
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
tests/test_init.py,sha256=
|
|
9
|
+
tests/test_init.py,sha256=EXq56_E2FUePXVFilAauHXoqItXe5Lvlpz8hBSUh6cU,1832
|
|
10
10
|
tests/test_msal_async.py,sha256=7-G6dO3_qWb8zxqC6_SqMMubCBbEERZy513P7UM4vmw,365
|
|
11
11
|
tests/test_redis_tools.py,sha256=uFpPSe6atbDVAuh1_OUtFgeZwyuLDspp42_EECJDSPg,1869
|
|
12
12
|
tests/test_settings.py,sha256=z-qtUs1zl5Q9NEux051eebyPnArLZ_OfZu65FKz0N4Y,333
|
|
13
|
-
aiohttp_msal-0.
|
|
14
|
-
aiohttp_msal-0.
|
|
15
|
-
aiohttp_msal-0.
|
|
16
|
-
aiohttp_msal-0.
|
|
17
|
-
aiohttp_msal-0.
|
|
18
|
-
aiohttp_msal-0.
|
|
13
|
+
aiohttp_msal-0.7.1.dist-info/LICENSE,sha256=BwqFEcF0Ij49hDZx4A_5CzsKnfU_twRjrm87JFwydFc,1080
|
|
14
|
+
aiohttp_msal-0.7.1.dist-info/METADATA,sha256=mxa80vNNCVI7s7I5htk1OHqVBdfgy1TuPCm8_ydV8x4,4724
|
|
15
|
+
aiohttp_msal-0.7.1.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
|
16
|
+
aiohttp_msal-0.7.1.dist-info/top_level.txt,sha256=QPWOi5JtacVEdbaU5bJExc9o-cCT2Lufx0QhUpsv5_E,19
|
|
17
|
+
aiohttp_msal-0.7.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
|
+
aiohttp_msal-0.7.1.dist-info/RECORD,,
|
tests/test_init.py
CHANGED
|
@@ -1,4 +1,79 @@
|
|
|
1
1
|
"""Init."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
4
7
|
import aiohttp_msal.routes # noqa
|
|
8
|
+
from aiohttp_msal import auth_ok, msal_session
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def a_yes(ses):
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def a_no(ses):
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@msal_session(a_yes, a_yes)
|
|
20
|
+
async def t_2yes(request, ses):
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@msal_session(a_no, a_yes, at_least_one=True)
|
|
25
|
+
async def t_1no1yes_one(request, ses):
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@msal_session(a_no, a_no, at_least_one=True)
|
|
30
|
+
async def t_2no_one(request, ses):
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@patch("aiohttp_msal.get_session")
|
|
35
|
+
async def test_include_any(get_session: MagicMock):
|
|
36
|
+
get_session.return_value = {}
|
|
37
|
+
|
|
38
|
+
assert await t_2yes({})
|
|
39
|
+
|
|
40
|
+
with pytest.raises(Exception):
|
|
41
|
+
await t_1no1yes_one
|
|
42
|
+
|
|
43
|
+
with pytest.raises(Exception):
|
|
44
|
+
await t_2no_one
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def func(request, ses):
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@patch("aiohttp_msal.get_session")
|
|
52
|
+
async def test_msal_session_auth(get_session: MagicMock):
|
|
53
|
+
get_session.return_value = {}
|
|
54
|
+
|
|
55
|
+
assert await msal_session(a_yes, a_yes)(func)({})
|
|
56
|
+
assert await msal_session(a_yes, a_no, at_least_one=True)(func)({})
|
|
57
|
+
assert await msal_session(a_no, a_yes, at_least_one=True)(func)({})
|
|
58
|
+
assert await msal_session(a_no, a_no, a_no, a_yes, at_least_one=True)(func)({})
|
|
59
|
+
|
|
60
|
+
with pytest.raises(Exception):
|
|
61
|
+
await msal_session(a_yes, a_no)(func)({})
|
|
62
|
+
|
|
63
|
+
with pytest.raises(Exception):
|
|
64
|
+
await msal_session(a_yes, a_yes, a_no)(func)({})
|
|
65
|
+
|
|
66
|
+
with pytest.raises(Exception):
|
|
67
|
+
await msal_session(a_no, a_no, at_least_one=True)(func)({})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@patch("aiohttp_msal.get_session")
|
|
71
|
+
async def test_auth_ok(get_session: MagicMock):
|
|
72
|
+
get_session.return_value = {"mail": "yes!"}
|
|
73
|
+
|
|
74
|
+
assert await msal_session(a_yes)(func)({})
|
|
75
|
+
|
|
76
|
+
get_session.return_value = {}
|
|
77
|
+
|
|
78
|
+
with pytest.raises(Exception):
|
|
79
|
+
assert await msal_session(a_yes, auth_ok)(func)({})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|