aiohttp-msal 1.0.2__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,16 +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
37
 
38
-
39
- # These keys will be used on the aiohttp session
40
- TOKEN_CACHE = "token_cache"
41
- FLOW_CACHE = "flow_cache"
42
- USER_EMAIL = "mail"
43
-
44
-
45
- @attrs.define()
38
+ @attrs.define(slots=False)
46
39
  class AsyncMSAL:
47
40
  """AsycMSAL class.
48
41
 
@@ -59,37 +52,42 @@ class AsyncMSAL:
59
52
  """Called if the token cache changes. Optional.
60
53
  Not required when the session parameter is an aiohttp_session.Session.
61
54
  """
62
- app: ConfidentialClientApplication = attrs.field(init=False)
63
-
64
- app_kwargs: ClassVar[dict[str, Any] | None] = None
55
+ app_kwargs: dict[str, Any] | None = None
65
56
  """ConfidentialClientApplication kwargs."""
66
57
  client_session: ClassVar[ClientSession | None] = None
67
58
 
68
- def __attrs_post_init__(self) -> None:
69
- """Init."""
70
- kwargs = dict(self.app_kwargs) if self.app_kwargs else {}
71
- for key, val in {
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"]
64
+
65
+ @cached_property
66
+ def app(self) -> ConfidentialClientApplication:
67
+ """Get the app."""
68
+ kwargs = {
72
69
  "client_id": ENV.SP_APP_ID,
73
70
  "client_credential": ENV.SP_APP_PW,
74
71
  "authority": ENV.SP_AUTHORITY,
75
72
  "validate_authority": False,
76
73
  "token_cache": self.token_cache,
77
- }.items():
78
- kwargs.setdefault(key, val)
79
- self.app = ConfidentialClientApplication(**kwargs)
74
+ }
75
+ if self.app_kwargs:
76
+ kwargs.update(self.app_kwargs)
77
+ return ConfidentialClientApplication(**kwargs)
80
78
 
81
79
  @cached_property
82
80
  def token_cache(self) -> SerializableTokenCache:
83
81
  """Get the token_cache."""
84
82
  res = SerializableTokenCache()
85
- if self.session and self.session.get(TOKEN_CACHE):
86
- res.deserialize(self.session[TOKEN_CACHE])
83
+ if tc := self.session.get(self.token_cache_key):
84
+ res.deserialize(tc)
87
85
  return res
88
86
 
89
87
  def save_token_cache(self) -> None:
90
88
  """Save the token cache if it changed."""
91
89
  if self.token_cache.has_state_changed:
92
- self.session[TOKEN_CACHE] = self.token_cache.serialize()
90
+ self.session[self.token_cache_key] = self.token_cache.serialize()
93
91
  if self.save_callback:
94
92
  self.save_callback(self.session)
95
93
 
@@ -101,10 +99,10 @@ class AsyncMSAL:
101
99
  **kwargs: Any,
102
100
  ) -> str:
103
101
  """First step - Start the flow."""
104
- self.session[TOKEN_CACHE] = None
105
- self.session[USER_EMAIL] = None
106
- self.session[FLOW_CACHE] = res = self.app.initiate_auth_code_flow(
107
- scopes or DEFAULT_SCOPES,
102
+ self.session.pop(self.token_cache_key, None)
103
+ self.session.pop(self.user_email_key, None)
104
+ self.session[self.flow_cache_key] = res = self.app.initiate_auth_code_flow(
105
+ scopes or self.default_scopes,
108
106
  redirect_uri=redirect_uri,
109
107
  response_mode="form_post",
110
108
  prompt=prompt,
@@ -121,7 +119,7 @@ class AsyncMSAL:
121
119
  """Second step - Acquire token."""
122
120
  # Assume we have it in the cache (added by /login)
123
121
  # will raise keryerror if no cache
124
- auth_code_flow = self.session.pop(FLOW_CACHE)
122
+ auth_code_flow = self.session.pop(self.flow_cache_key)
125
123
  result = self.app.acquire_token_by_auth_code_flow(
126
124
  auth_code_flow, auth_response, scopes=scopes
127
125
  )
@@ -131,7 +129,7 @@ class AsyncMSAL:
131
129
  raise web.HTTPBadRequest(text=f"Expected id_token_claims in {result}")
132
130
  self.save_token_cache()
133
131
  if tok := result.get("id_token_claims"):
134
- self.session[USER_EMAIL] = tok.get("preferred_username")
132
+ self.session[self.user_email_key] = tok.get("preferred_username")
135
133
 
136
134
  async def async_acquire_token_by_auth_code_flow(self, auth_response: Any) -> None:
137
135
  """Second step - Acquire token, async version."""
@@ -144,7 +142,7 @@ class AsyncMSAL:
144
142
  accounts = self.app.get_accounts()
145
143
  if accounts:
146
144
  result = self.app.acquire_token_silent(
147
- scopes=scopes or DEFAULT_SCOPES, account=accounts[0]
145
+ scopes=scopes or self.default_scopes, account=accounts[0]
148
146
  )
149
147
  self.save_token_cache()
150
148
  return result
@@ -200,27 +198,13 @@ class AsyncMSAL:
200
198
  get = partialmethod(request_ctx, HTTP_GET)
201
199
  post = partialmethod(request_ctx, HTTP_POST)
202
200
 
203
- @property
204
- def mail(self) -> str:
205
- """User email."""
206
- return self.session.get(USER_EMAIL, "")
207
-
208
- @property
209
- def manager_mail(self) -> str:
210
- """User's manager's email."""
211
- return self.session.get("m_mail", "")
212
-
213
- @property
214
- def manager_name(self) -> str:
215
- """User's manager's name."""
216
- return self.session.get("m_name", "")
217
-
218
- @property
219
- def name(self) -> str:
220
- """User's display name."""
221
- return self.session.get("name", "")
222
-
223
201
  @property
224
202
  def authenticated(self) -> bool:
225
203
  """If the user is logged in."""
226
- return bool(self.session.get("mail"))
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.2
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=JUtyTro57rLiHQYqgYq8LFv3keznyhISP1emgN3y31E,8232
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.2.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
10
- aiohttp_msal-1.0.2.dist-info/METADATA,sha256=b9HRcY4HaOKhXVhBQIuYK-h0Cia9g922FjOdTyOuPpw,4514
11
- aiohttp_msal-1.0.2.dist-info/RECORD,,