aiohttp-msal 1.0.3__py3-none-any.whl → 1.0.4__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 CHANGED
@@ -1,10 +1,11 @@
1
1
  """aiohttp_msal."""
2
2
 
3
+ import json
3
4
  import logging
4
5
  from collections.abc import Awaitable, Callable
5
6
  from functools import wraps
6
7
  from inspect import getfullargspec, iscoroutinefunction
7
- from typing import TypeVar, TypeVarTuple, cast
8
+ from typing import Any, TypeVar, TypeVarTuple, cast
8
9
 
9
10
  from aiohttp import ClientSession, web
10
11
  from aiohttp_session import get_session
@@ -12,6 +13,7 @@ from aiohttp_session import setup as _setup
12
13
 
13
14
  from aiohttp_msal.msal_async import AsyncMSAL
14
15
  from aiohttp_msal.settings import ENV
16
+ from aiohttp_msal.utils import retry
15
17
 
16
18
  _LOGGER = logging.getLogger(__name__)
17
19
 
@@ -87,7 +89,11 @@ def auth_or(
87
89
 
88
90
 
89
91
  async def app_init_redis_session(
90
- app: web.Application, max_age: int = 3600 * 24 * 90
92
+ app: web.Application,
93
+ max_age: int = 3600 * 24 * 90,
94
+ check_proxy_cb: Callable[[], Awaitable[None]] | None = None,
95
+ encoder: Callable[[object], str] = json.dumps,
96
+ decoder: Callable[[str], Any] = json.loads,
91
97
  ) -> None:
92
98
  """Init an aiohttp_session with Redis storage helper.
93
99
 
@@ -96,7 +102,10 @@ async def app_init_redis_session(
96
102
  from aiohttp_session import redis_storage
97
103
  from redis.asyncio import from_url
98
104
 
99
- await check_proxy()
105
+ if check_proxy_cb:
106
+ await check_proxy_cb()
107
+ else:
108
+ await check_proxy()
100
109
 
101
110
  _LOGGER.info("Connect to Redis %s", ENV.REDIS)
102
111
  try:
@@ -114,10 +123,13 @@ async def app_init_redis_session(
114
123
  secure=True,
115
124
  domain=ENV.DOMAIN,
116
125
  cookie_name=ENV.COOKIE_NAME,
126
+ encoder=encoder,
127
+ decoder=decoder,
117
128
  )
118
129
  _setup(app, storage)
119
130
 
120
131
 
132
+ @retry
121
133
  async def check_proxy() -> None:
122
134
  """Test if we have Internet connectivity through proxies etc."""
123
135
  try:
@@ -0,0 +1,136 @@
1
+ """Graph User Info."""
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any, Literal, TypeVar
5
+
6
+ from aiohttp import web
7
+ from aiohttp_session import get_session
8
+
9
+ from aiohttp_msal import ENV
10
+ from aiohttp_msal.msal_async import AsyncMSAL
11
+ from aiohttp_msal.utils import retry
12
+
13
+
14
+ @retry
15
+ async def get_user_info(aiomsal: AsyncMSAL) -> None:
16
+ """Load user info from MS graph API. Requires User.Read permissions."""
17
+ async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
18
+ body = await res.json()
19
+ try:
20
+ aiomsal.mail = body["mail"]
21
+ aiomsal.name = body["displayName"]
22
+ except KeyError as err:
23
+ raise KeyError(
24
+ f"Unexpected return from Graph endpoint: {body}: {err}"
25
+ ) from err
26
+
27
+
28
+ @retry
29
+ async def get_manager_info(aiomsal: AsyncMSAL) -> None:
30
+ """Load manager info from MS graph API. Requires User.Read.All permissions."""
31
+ async with aiomsal.get("https://graph.microsoft.com/v1.0/me/manager") as res:
32
+ body = await res.json()
33
+ try:
34
+ aiomsal.manager_mail = body["mail"]
35
+ aiomsal.manager_name = body["displayName"]
36
+ except KeyError as err:
37
+ raise KeyError(
38
+ f"Unexpected return from Graph endpoint: {body}: {err}"
39
+ ) from err
40
+
41
+
42
+ def html_table(items: Mapping[Any, Any]) -> str:
43
+ """Return a table HTML."""
44
+ res = "<table style='width:80%;border:1px solid black;'>"
45
+ for key, val in items.items():
46
+ res += f"<tr><td>{key}</td><td>{val}</td></tr>"
47
+ res += "</table>"
48
+ return res
49
+
50
+
51
+ def html_wrap(msgs: Sequence[str]) -> str:
52
+ """Return proper HTML when login fails."""
53
+ html = "</li><li>".join(msgs)
54
+ return f"""
55
+ <h2>Login failed</h2>
56
+
57
+ <p>Retry at <a href='/user/login'>/user/login</a></p>
58
+
59
+ <p>Try clearing the cookies for <b>.{ENV.DOMAIN}<b> by navigating to the correct
60
+ address for your browser:
61
+ <ul>
62
+ <li>chrome://settings/siteData?searchSubpage={ENV.DOMAIN}</li>
63
+ <li>brave://settings/siteData?searchSubpage={ENV.DOMAIN}</li>
64
+ <li>edge://settings/siteData (you will have to search for {ENV.DOMAIN} cookies)</li>
65
+ </ul></p>
66
+
67
+ <h4>Debug info</h4>
68
+ <ul><li>{html}</li></ul>
69
+ """
70
+
71
+
72
+ TA = TypeVar("TA", bound=AsyncMSAL)
73
+
74
+
75
+ async def check_auth_response(
76
+ request: web.Request,
77
+ asyncmsal_class: type[TA] = AsyncMSAL, # type:ignore[assignment]
78
+ get_info: Literal["user", "manager", ""] = "manager",
79
+ ) -> tuple[TA | None, list[str]]:
80
+ """Parse the MS auth response."""
81
+ # Expecting response_mode="form_post"
82
+ auth_response = dict(await request.post())
83
+
84
+ msg = list[str]()
85
+
86
+ # Ensure all expected variables were returned...
87
+ if not all(auth_response.get(k) for k in ["code", "session_state", "state"]):
88
+ msg.append("Expected 'code', 'session_state', 'state' in auth_response")
89
+ msg.append(f"Received auth_response: {list(auth_response)}")
90
+ return None, msg
91
+
92
+ if not request.cookies.get(ENV.COOKIE_NAME):
93
+ cookies = dict(request.cookies.items())
94
+ msg.append(f"<b>Expected '{ENV.COOKIE_NAME}' in cookies</b>")
95
+ msg.append(html_table(cookies))
96
+ msg.append("Cookie should be set with Samesite:None")
97
+
98
+ session = await get_session(request)
99
+ if session.new:
100
+ msg.append(
101
+ "Warning: This is a new session and may not have all expected values."
102
+ )
103
+
104
+ if not session.get(asyncmsal_class.flow_cache_key):
105
+ msg.append(f"<b>Expected '{asyncmsal_class.flow_cache_key}' in session</b>")
106
+ msg.append(html_table(session))
107
+
108
+ aiomsal = asyncmsal_class(session)
109
+ aiomsal.redirect = "/" + aiomsal.redirect.lstrip("/")
110
+
111
+ if msg:
112
+ return aiomsal, msg
113
+
114
+ try:
115
+ await aiomsal.async_acquire_token_by_auth_code_flow(auth_response)
116
+ except Exception as err:
117
+ msg.append("<b>Could not get token</b> - async_acquire_token_by_auth_code_flow")
118
+ msg.append(str(err))
119
+
120
+ if not msg:
121
+ try:
122
+ if get_info in ("user", "manager"):
123
+ await get_user_info(aiomsal)
124
+ if get_info == "manager":
125
+ await get_manager_info(aiomsal)
126
+ except Exception as err:
127
+ msg.append("Could not get org info from MS graph")
128
+ msg.append(str(err))
129
+ aiomsal.mail = ""
130
+ aiomsal.name = ""
131
+
132
+ if session.get("mail"):
133
+ for lcb in ENV.login_callback:
134
+ await lcb(aiomsal)
135
+
136
+ return aiomsal, msg
@@ -9,7 +9,7 @@ import asyncio
9
9
  import json
10
10
  from collections.abc import Callable
11
11
  from functools import cached_property, partialmethod
12
- from typing import Any, ClassVar, Literal, Unpack
12
+ from typing import Any, ClassVar, Literal, Unpack, cast
13
13
 
14
14
  import attrs
15
15
  from aiohttp import web
@@ -24,6 +24,7 @@ from aiohttp_session import Session
24
24
  from msal import ConfidentialClientApplication, SerializableTokenCache
25
25
 
26
26
  from aiohttp_msal.settings import ENV
27
+ from aiohttp_msal.utils import dict_property
27
28
 
28
29
  HttpMethods = Literal["get", "post", "put", "patch", "delete"]
29
30
  HTTP_GET = "get"
@@ -33,11 +34,8 @@ HTTP_PATCH = "patch"
33
34
  HTTP_DELETE = "delete"
34
35
  HTTP_ALLOWED = [HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE]
35
36
 
36
- DEFAULT_SCOPES = ["User.Read", "User.Read.All"]
37
- FLOW_CACHE = "flow_cache"
38
37
 
39
-
40
- @attrs.define()
38
+ @attrs.define(slots=False)
41
39
  class AsyncMSAL:
42
40
  """AsycMSAL class.
43
41
 
@@ -58,8 +56,11 @@ class AsyncMSAL:
58
56
  """ConfidentialClientApplication kwargs."""
59
57
  client_session: ClassVar[ClientSession | None] = None
60
58
 
61
- token_cache_key: str = "token_cache"
62
- user_email_key: str = "mail"
59
+ token_cache_key: ClassVar[str] = "token_cache"
60
+ user_email_key: ClassVar[str] = "mail"
61
+ flow_cache_key: ClassVar[str] = "flow_cache"
62
+ redirect_key = "redirect"
63
+ default_scopes: ClassVar[list[str]] = ["User.Read", "User.Read.All"]
63
64
 
64
65
  @cached_property
65
66
  def app(self) -> ConfidentialClientApplication:
@@ -100,8 +101,8 @@ class AsyncMSAL:
100
101
  """First step - Start the flow."""
101
102
  self.session.pop(self.token_cache_key, None)
102
103
  self.session.pop(self.user_email_key, None)
103
- self.session[FLOW_CACHE] = res = self.app.initiate_auth_code_flow(
104
- scopes or DEFAULT_SCOPES,
104
+ self.session[self.flow_cache_key] = res = self.app.initiate_auth_code_flow(
105
+ scopes or self.default_scopes,
105
106
  redirect_uri=redirect_uri,
106
107
  response_mode="form_post",
107
108
  prompt=prompt,
@@ -118,7 +119,7 @@ class AsyncMSAL:
118
119
  """Second step - Acquire token."""
119
120
  # Assume we have it in the cache (added by /login)
120
121
  # will raise keryerror if no cache
121
- auth_code_flow = self.session.pop(FLOW_CACHE)
122
+ auth_code_flow = self.session.pop(self.flow_cache_key)
122
123
  result = self.app.acquire_token_by_auth_code_flow(
123
124
  auth_code_flow, auth_response, scopes=scopes
124
125
  )
@@ -141,7 +142,7 @@ class AsyncMSAL:
141
142
  accounts = self.app.get_accounts()
142
143
  if accounts:
143
144
  result = self.app.acquire_token_silent(
144
- scopes=scopes or DEFAULT_SCOPES, account=accounts[0]
145
+ scopes=scopes or self.default_scopes, account=accounts[0]
145
146
  )
146
147
  self.save_token_cache()
147
148
  return result
@@ -197,27 +198,13 @@ class AsyncMSAL:
197
198
  get = partialmethod(request_ctx, HTTP_GET)
198
199
  post = partialmethod(request_ctx, HTTP_POST)
199
200
 
200
- @property
201
- def mail(self) -> str:
202
- """User email."""
203
- return self.session.get(self.user_email_key, "")
204
-
205
- @property
206
- def manager_mail(self) -> str:
207
- """User's manager's email."""
208
- return self.session.get("m_mail", "")
209
-
210
- @property
211
- def manager_name(self) -> str:
212
- """User's manager's name."""
213
- return self.session.get("m_name", "")
214
-
215
- @property
216
- def name(self) -> str:
217
- """User's display name."""
218
- return self.session.get("name", "")
219
-
220
201
  @property
221
202
  def authenticated(self) -> bool:
222
203
  """If the user is logged in."""
223
204
  return bool(self.session.get(self.user_email_key))
205
+
206
+ name = cast(str, dict_property("session", "name"))
207
+ mail = dict_property("session", user_email_key)
208
+ manager_name = dict_property("session", "m_name")
209
+ manager_mail = dict_property("session", "m_mail")
210
+ redirect = dict_property("session", redirect_key)
aiohttp_msal/routes.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """The user blueprint."""
2
2
 
3
3
  import time
4
- from collections.abc import Mapping, Sequence
5
4
  from inspect import iscoroutinefunction
6
5
  from typing import Any
7
6
  from urllib.parse import urljoin
@@ -9,9 +8,14 @@ from urllib.parse import urljoin
9
8
  from aiohttp import web
10
9
  from aiohttp_session import get_session, new_session
11
10
 
12
- from aiohttp_msal import _LOGGER, ENV, auth_ok, msal_session
13
- from aiohttp_msal.msal_async import FLOW_CACHE, AsyncMSAL
14
- from aiohttp_msal.user_info import get_manager_info, get_user_info
11
+ from aiohttp_msal import ENV, auth_ok, msal_session
12
+ from aiohttp_msal.helpers import (
13
+ check_auth_response,
14
+ get_manager_info,
15
+ get_user_info,
16
+ html_wrap,
17
+ )
18
+ from aiohttp_msal.msal_async import AsyncMSAL
15
19
 
16
20
  ROUTES = web.RouteTableDef()
17
21
 
@@ -27,7 +31,7 @@ def get_route(request: web.Request, url: str) -> str:
27
31
  """
28
32
  url = str(request.url.origin() / url)
29
33
  if "localhost" not in url:
30
- url = url.replace("p:", "ps:", 1)
34
+ url = url.replace("http:", "https:", 1)
31
35
  return url
32
36
 
33
37
 
@@ -45,83 +49,35 @@ async def user_login(request: web.Request) -> web.Response:
45
49
 
46
50
  msredirect = get_route(request, URI_USER_AUTHORIZED.lstrip("/"))
47
51
  redir = AsyncMSAL(session).initiate_auth_code_flow(redirect_uri=msredirect)
48
- return web.HTTPFound(redir)
52
+ raise web.HTTPFound(redir)
49
53
 
50
54
 
51
55
  @ROUTES.post(URI_USER_AUTHORIZED)
52
56
  async def user_authorized(request: web.Request) -> web.Response:
53
57
  """Complete the auth code flow."""
54
- session = await get_session(request)
55
-
56
- # build a plain dict from the aiohttp server request's url parameters
57
- # pre-0.1.18. Now we have response_mode="form_post"
58
- # auth_response = dict(request.rel_url.query.items())
59
- auth_response = dict(await request.post())
60
-
61
- msg = []
62
- response_cookie = 0
63
-
64
- # Ensure all expected variables were returned...
65
- if not all(auth_response.get(k) for k in ["code", "session_state", "state"]):
66
- msg.append(
67
- f"<b>Expecting code,state,session_state in post body.</b>auth_response: {auth_response}"
68
- )
58
+ aiomsal, msg = await check_auth_response(request, AsyncMSAL)
69
59
 
70
- if not request.cookies.get(ENV.COOKIE_NAME):
71
- cookies = dict(request.cookies.items())
72
- msg.append(f"<b>Expecting '{ENV.COOKIE_NAME}' in cookies</b>")
73
- _LOGGER.fatal("Cookie should be set with Samesite:None")
74
- msg.append(html_table(cookies))
75
-
76
- elif not session.get(FLOW_CACHE):
77
- msg.append(f"<b>Expecting '{FLOW_CACHE}' in session</b>")
78
- msg.append(f"- Session.new: {session.new}")
79
- msg.append(html_table(session))
80
-
81
- aiomsal = AsyncMSAL(session)
82
-
83
- if not msg:
60
+ if aiomsal and not msg:
84
61
  try:
85
- await aiomsal.async_acquire_token_by_auth_code_flow(auth_response)
86
- except Exception as err:
87
- msg.append(
88
- "<b>Could not get token</b> - async_acquire_token_by_auth_code_flow"
89
- )
90
- msg.append(str(err))
91
-
92
- if not msg:
93
- session.pop("mail", None)
94
- session.pop("name", None)
95
- try:
96
- await get_user_info(aiomsal)
97
- await get_manager_info(aiomsal)
98
- except Exception as err:
99
- msg.append("Could not get org info from MS graph")
100
- msg.append(str(err))
101
- if session.get("mail"):
102
- for lcb in ENV.login_callback:
103
- await lcb(aiomsal)
104
-
105
- if msg:
106
- resp = web.Response(
107
- body=html_wrap(msg),
108
- content_type="text/html",
62
+ raise web.HTTPFound(aiomsal.redirect)
63
+ finally:
64
+ aiomsal.redirect = ""
65
+
66
+ resp = web.Response(
67
+ body=html_wrap(msg),
68
+ content_type="text/html",
69
+ )
70
+ if aiomsal is None:
71
+ resp.set_cookie(
72
+ ENV.COOKIE_NAME,
73
+ "",
74
+ path="/",
75
+ httponly=True,
76
+ secure=True,
77
+ samesite="Strict",
78
+ domain=ENV.DOMAIN,
109
79
  )
110
- if response_cookie:
111
- resp.set_cookie(
112
- ENV.COOKIE_NAME,
113
- "",
114
- path="/",
115
- httponly=True,
116
- secure=True,
117
- samesite="Strict",
118
- domain=ENV.DOMAIN,
119
- )
120
- return resp
121
-
122
- redirect = session.pop(SESSION_REDIRECT, "") or "/fr/"
123
-
124
- return web.HTTPFound(redirect)
80
+ return resp
125
81
 
126
82
 
127
83
  @ROUTES.get("/user/debug")
@@ -226,33 +182,3 @@ async def user_photo(request: web.Request, ses: AsyncMSAL) -> web.StreamResponse
226
182
 
227
183
  # await response.write_eof()
228
184
  return response
229
-
230
-
231
- def html_table(items: Mapping[Any, Any]) -> str:
232
- """Return a table HTML."""
233
- res = "<table style='width:80%;border:1px solid black;'>"
234
- for key, val in items.items():
235
- res += f"<tr><td>{key}</td><td>{val}</td></tr>"
236
- res += "</table>"
237
- return res
238
-
239
-
240
- def html_wrap(msgs: Sequence[str]) -> str:
241
- """Return proper HTML when login fails."""
242
- html = "</li><li>".join(msgs)
243
- return f"""
244
- <h2>Login failed</h2>
245
-
246
- <p>Retry at <a href='/user/login'>/user/login</a></p>
247
-
248
- <p>Try clearing the cookies for <b>.{ENV.DOMAIN}<b> by navigating to the correct
249
- address for your browser:
250
- <ul>
251
- <li>chrome://settings/siteData?searchSubpage={ENV.DOMAIN}</li>
252
- <li>brave://settings/siteData?searchSubpage={ENV.DOMAIN}</li>
253
- <li>edge://settings/siteData (you will have to search for {ENV.DOMAIN} cookies)</li>
254
- </ul></p>
255
-
256
- <h4>Debug info</h4>
257
- <ul><li>{html}</li></ul>
258
- """
aiohttp_msal/utils.py CHANGED
@@ -2,10 +2,8 @@
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import Awaitable, Callable
5
- from functools import wraps
6
- from typing import ParamSpec, TypeVar, Any
7
- from functools import partial
8
-
5
+ from functools import partial, wraps
6
+ from typing import Any, ParamSpec, TypeVar
9
7
 
10
8
  T = TypeVar("T")
11
9
  P = ParamSpec("P")
@@ -31,6 +29,36 @@ def async_wrap(
31
29
  return run
32
30
 
33
31
 
32
+ class dict_property(property):
33
+ """Property."""
34
+
35
+ def __init__(self, dict_name: str, prop_name: str) -> None:
36
+ """Initialize the property."""
37
+ self.dict_name = dict_name
38
+ self.prop_name = prop_name
39
+
40
+ def __get__(self, instance: Any, owner: type | None = None, /) -> Any:
41
+ """Getter."""
42
+ return getattr(instance, self.dict_name, {}).get(self.prop_name, "")
43
+
44
+ def __set__(self, instance: Any, value: Any, /) -> None:
45
+ """Setter."""
46
+ if value == "":
47
+ getattr(instance, self.dict_name, {}).pop(self.prop_name, None)
48
+ else:
49
+ getattr(instance, self.dict_name, {}).__setitem__(self.prop_name, value)
50
+
51
+
52
+ # def dict_property(dict_name: str, prop_name: str) -> property:
53
+ # """Create properties for a dictionary."""
54
+ # return property(
55
+ # fget=lambda self: str(getattr(self, dict_name).get(prop_name, "")),
56
+ # fset=lambda self, v: getattr(self, dict_name).set(prop_name, v),
57
+ # fdel=lambda self: getattr(self, dict_name).pop(prop_name, None),
58
+ # doc=f'self.{dict_name}["{prop_name}"]',
59
+ # )
60
+
61
+
34
62
  def retry(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
35
63
  """Retry if tenacity is installed."""
36
64
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: aiohttp-msal
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
5
5
  Keywords: aiohttp,asyncio,msal,oauth
6
6
  Author: Johann Kellerman
@@ -0,0 +1,11 @@
1
+ aiohttp_msal/__init__.py,sha256=nrjPAIy0kNbBvHZsy9qUSKs5r_-Kiea-KJM17bsBIkw,4375
2
+ aiohttp_msal/helpers.py,sha256=8sW-7FW4t1Ztazql11ng67SqdKBlbOWuOMjOBRfBc_c,4495
3
+ aiohttp_msal/msal_async.py,sha256=RUV_XlhNNCOZslT1sEkEPyaYcHDOmsIyiGmRpZuA3I0,8097
4
+ aiohttp_msal/redis_tools.py,sha256=6kCw0_zDQcvIcsJaPfG-zHUvT3vzkrNySNTV5y1tckE,6539
5
+ aiohttp_msal/routes.py,sha256=3rAejWcHfL5czVA91TY3wZGT5EkRxmfyvc8vr6rhyxU,5438
6
+ aiohttp_msal/settings.py,sha256=sArlq9vBDMsikLf9sTRw-UXE2_QRK_G-kzmtHvZcbwA,1559
7
+ aiohttp_msal/settings_base.py,sha256=WBI7HS780i9zKWUy1ZnztDbRsfoDMVr3K-otHZOhNCc,3026
8
+ aiohttp_msal/utils.py,sha256=ll303J58nwCEB9QCAm13urUOa6cPqsAE7z_iP9OlRJw,2390
9
+ aiohttp_msal-1.0.4.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
10
+ aiohttp_msal-1.0.4.dist-info/METADATA,sha256=OBl_U9OTy602OfSL9-d5myNJPUYuaGjtBF7voW5lu2Y,4514
11
+ aiohttp_msal-1.0.4.dist-info/RECORD,,
aiohttp_msal/user_info.py DELETED
@@ -1,32 +0,0 @@
1
- """Graph User Info."""
2
-
3
- from aiohttp_msal.msal_async import AsyncMSAL
4
- from aiohttp_msal.utils import retry
5
-
6
-
7
- @retry
8
- async def get_user_info(aiomsal: AsyncMSAL) -> None:
9
- """Load user info from MS graph API. Requires User.Read permissions."""
10
- async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
11
- body = await res.json()
12
- try:
13
- aiomsal.session["mail"] = body["mail"]
14
- aiomsal.session["name"] = body["displayName"]
15
- except KeyError as err:
16
- raise KeyError(
17
- f"Unexpected return from Graph endpoint: {body}: {err}"
18
- ) from err
19
-
20
-
21
- @retry
22
- async def get_manager_info(aiomsal: AsyncMSAL) -> None:
23
- """Load manager info from MS graph API. Requires User.Read.All permissions."""
24
- async with aiomsal.get("https://graph.microsoft.com/v1.0/me/manager") as res:
25
- body = await res.json()
26
- try:
27
- aiomsal.session["m_mail"] = body["mail"]
28
- aiomsal.session["m_name"] = body["displayName"]
29
- except KeyError as err:
30
- raise KeyError(
31
- f"Unexpected return from Graph endpoint: {body}: {err}"
32
- ) from err
@@ -1,11 +0,0 @@
1
- aiohttp_msal/__init__.py,sha256=hnyifyJykI7NMvM93KrHIsTlrrfCVUrpKdbRKL6Gubw,4027
2
- aiohttp_msal/msal_async.py,sha256=urQvaMTi0mJnCboCsj8A9F9VpWcjPOYaechpZ5XrnbY,8153
3
- aiohttp_msal/redis_tools.py,sha256=6kCw0_zDQcvIcsJaPfG-zHUvT3vzkrNySNTV5y1tckE,6539
4
- aiohttp_msal/routes.py,sha256=WyLBuoPMkkG6Cx4gFUu_ER71FyJbeXKhOQRQu5ALG2M,8138
5
- aiohttp_msal/settings.py,sha256=sArlq9vBDMsikLf9sTRw-UXE2_QRK_G-kzmtHvZcbwA,1559
6
- aiohttp_msal/settings_base.py,sha256=WBI7HS780i9zKWUy1ZnztDbRsfoDMVr3K-otHZOhNCc,3026
7
- aiohttp_msal/user_info.py,sha256=lxjFxjm16rvC-0LS81y7SG5pCOa5Zl0s62uxi97yu_k,1171
8
- aiohttp_msal/utils.py,sha256=SgGpE1eFdVh48FaKvtbnQqJKTReXa9OPBKiYGY7SYq8,1303
9
- aiohttp_msal-1.0.3.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
10
- aiohttp_msal-1.0.3.dist-info/METADATA,sha256=raujaCawOODO6HYM79crhTECU28f_IjlkzIPmR8ck48,4514
11
- aiohttp_msal-1.0.3.dist-info/RECORD,,