aiohttp-msal 0.5.4__tar.gz → 0.6__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.
Files changed (22) hide show
  1. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/PKG-INFO +24 -13
  2. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/README.md +7 -9
  3. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/__init__.py +1 -1
  4. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/msal_async.py +35 -12
  5. aiohttp_msal-0.6/aiohttp_msal/redis_tools.py +87 -0
  6. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/PKG-INFO +24 -13
  7. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/SOURCES.txt +1 -0
  8. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/requires.txt +2 -2
  9. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/setup.cfg +5 -4
  10. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/setup.py +2 -2
  11. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/tests/test_init.py +1 -0
  12. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/LICENSE +0 -0
  13. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/routes.py +0 -0
  14. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/settings.py +0 -0
  15. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/settings_base.py +0 -0
  16. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal/user_info.py +0 -0
  17. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/dependency_links.txt +0 -0
  18. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/top_level.txt +0 -0
  19. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/aiohttp_msal.egg-info/zip-safe +0 -0
  20. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/tests/__init__.py +0 -0
  21. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/tests/test_msal_async.py +0 -0
  22. {aiohttp_msal-0.5.4 → aiohttp_msal-0.6}/tests/test_settings.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aiohttp_msal
3
- Version: 0.5.4
4
- Summary: Helper Library to use MSAL with aiohttp
3
+ Version: 0.6
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
7
7
  Author-email: kellerza@gmail.com
8
8
  License: MIT
9
9
  Keywords: msal,oauth,aiohttp,asyncio
10
- Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Programming Language :: Python :: 3
@@ -15,11 +15,24 @@ Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
18
19
  Requires-Python: >=3.9
19
20
  Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: msal>=1.24.1
23
+ Requires-Dist: aiohttp_session>=2.12
24
+ Requires-Dist: aiohttp>=3.8
20
25
  Provides-Extra: redis
26
+ Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
21
27
  Provides-Extra: tests
22
- License-File: LICENSE
28
+ Requires-Dist: black==23.9.1; extra == "tests"
29
+ Requires-Dist: pylint; extra == "tests"
30
+ Requires-Dist: flake8; extra == "tests"
31
+ Requires-Dist: pytest-aiohttp; extra == "tests"
32
+ Requires-Dist: pytest; extra == "tests"
33
+ Requires-Dist: pytest-cov; extra == "tests"
34
+ Requires-Dist: pytest-asyncio; extra == "tests"
35
+ Requires-Dist: pytest-env; extra == "tests"
23
36
 
24
37
  # aiohttp_msal Python library
25
38
 
@@ -29,11 +42,10 @@ Blocking MSAL functions are executed in the executor thread. Should be useful un
29
42
 
30
43
  Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
31
44
 
32
-
33
45
  ## AsycMSAL class
34
46
 
35
47
  The AsyncMSAL class wraps the behavior in the following example app
36
- https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76
48
+ <https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76>
37
49
 
38
50
  It is responsible to manage tokens & token refreshes and as a client to retrieve data using these tokens.
39
51
 
@@ -41,7 +53,7 @@ It is responsible to manage tokens & token refreshes and as a client to retrieve
41
53
 
42
54
  Firstly you should get the tokens via OAuth
43
55
 
44
- 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
56
+ 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
45
57
 
46
58
  The caller is expected to:
47
59
  1. somehow store this content, typically inside the current session of the server,
@@ -51,8 +63,7 @@ Firstly you should get the tokens via OAuth
51
63
 
52
64
  **Step 1** and part of **Step 3** is stored by this class in the aiohttp_session
53
65
 
54
- 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
55
-
66
+ 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
56
67
 
57
68
  ### Use the token
58
69
 
@@ -65,11 +76,11 @@ async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
65
76
  res = await res.json()
66
77
  ```
67
78
 
68
- # Example web server
79
+ ## Example web server
69
80
 
70
81
  Complete routes can be found in [routes.py](./aiohttp_msal/routes.py)
71
82
 
72
- ## Start the login process
83
+ ### Start the login process
73
84
 
74
85
  ```python
75
86
  @ROUTES.get("/user/login")
@@ -84,7 +95,7 @@ async def user_login(request: web.Request) -> web.Response:
84
95
  return web.HTTPFound(redir)
85
96
  ```
86
97
 
87
- ## Acquire the token after being redirected back to the server
98
+ ### Acquire the token after being redirected back to the server
88
99
 
89
100
  ```python
90
101
  @ROUTES.post(URI_USER_AUTHORIZED)
@@ -101,7 +112,7 @@ async def user_authorized(request: web.Request) -> web.Response:
101
112
 
102
113
  - `@ROUTES.get("/user/photo")`
103
114
 
104
- Serve the user's photo from his Microsoft profile
115
+ Serve the user's photo from their Microsoft profile
105
116
 
106
117
  - `get_user_info`
107
118
 
@@ -6,11 +6,10 @@ Blocking MSAL functions are executed in the executor thread. Should be useful un
6
6
 
7
7
  Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
8
8
 
9
-
10
9
  ## AsycMSAL class
11
10
 
12
11
  The AsyncMSAL class wraps the behavior in the following example app
13
- https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76
12
+ <https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76>
14
13
 
15
14
  It is responsible to manage tokens & token refreshes and as a client to retrieve data using these tokens.
16
15
 
@@ -18,7 +17,7 @@ It is responsible to manage tokens & token refreshes and as a client to retrieve
18
17
 
19
18
  Firstly you should get the tokens via OAuth
20
19
 
21
- 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
20
+ 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
22
21
 
23
22
  The caller is expected to:
24
23
  1. somehow store this content, typically inside the current session of the server,
@@ -28,8 +27,7 @@ Firstly you should get the tokens via OAuth
28
27
 
29
28
  **Step 1** and part of **Step 3** is stored by this class in the aiohttp_session
30
29
 
31
- 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
32
-
30
+ 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
33
31
 
34
32
  ### Use the token
35
33
 
@@ -42,11 +40,11 @@ async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
42
40
  res = await res.json()
43
41
  ```
44
42
 
45
- # Example web server
43
+ ## Example web server
46
44
 
47
45
  Complete routes can be found in [routes.py](./aiohttp_msal/routes.py)
48
46
 
49
- ## Start the login process
47
+ ### Start the login process
50
48
 
51
49
  ```python
52
50
  @ROUTES.get("/user/login")
@@ -61,7 +59,7 @@ async def user_login(request: web.Request) -> web.Response:
61
59
  return web.HTTPFound(redir)
62
60
  ```
63
61
 
64
- ## Acquire the token after being redirected back to the server
62
+ ### Acquire the token after being redirected back to the server
65
63
 
66
64
  ```python
67
65
  @ROUTES.post(URI_USER_AUTHORIZED)
@@ -78,7 +76,7 @@ async def user_authorized(request: web.Request) -> web.Response:
78
76
 
79
77
  - `@ROUTES.get("/user/photo")`
80
78
 
81
- Serve the user's photo from his Microsoft profile
79
+ Serve the user's photo from their Microsoft profile
82
80
 
83
81
  - `get_user_info`
84
82
 
@@ -13,7 +13,7 @@ from .settings import ENV
13
13
 
14
14
  _LOGGER = logging.getLogger(__name__)
15
15
 
16
- VERSION = "0.5.4"
16
+ VERSION = "0.6"
17
17
 
18
18
 
19
19
  def msal_session(*args: Callable[[AsyncMSAL], Union[Any, Awaitable[Any]]]) -> Callable:
@@ -7,7 +7,7 @@ Once you have the OAuth tokens store in the session, you are free to make reques
7
7
  import asyncio
8
8
  import json
9
9
  from functools import partial, wraps
10
- from typing import Any, Callable, Optional
10
+ from typing import Any, Callable, Optional, Union
11
11
 
12
12
  from aiohttp import web
13
13
  from aiohttp.client import ClientResponse, ClientSession, _RequestContextManager
@@ -96,11 +96,21 @@ class AsyncMSAL:
96
96
  _app: ConfidentialClientApplication = None
97
97
  _clientsession: ClientSession = None # type: ignore
98
98
 
99
- def __init__(self, session: Session):
100
- """Init the class."""
99
+ def __init__(
100
+ self,
101
+ session: Union[Session, dict[str, str]],
102
+ save_cache: Optional[Callable[[Union[Session, dict[str, str]]], None]] = None,
103
+ ):
104
+ """Init the class.
105
+
106
+ **save_token_cache** will be called if the token cache changes. Optional.
107
+ Not required when the session parameter is an aiohttp_session.Session.
108
+ """
101
109
  self.session = session
102
- if not isinstance(session, Session):
103
- raise ValueError(f"session required {session}")
110
+ if save_cache:
111
+ self.save_token_cache = save_cache
112
+ if not isinstance(session, (Session, dict)):
113
+ raise ValueError(f"session or dict-like object required {session}")
104
114
 
105
115
  @property
106
116
  def token_cache(self) -> SerializableTokenCache:
@@ -134,11 +144,13 @@ class AsyncMSAL:
134
144
  """Save the token cache if it changed."""
135
145
  if self.token_cache.has_state_changed:
136
146
  self.session[TOKEN_CACHE] = self.token_cache.serialize()
147
+ if hasattr(self, "save_token_cache"):
148
+ self.save_token_cache(self.token_cache)
137
149
 
138
150
  def build_auth_code_flow(self, redirect_uri: str) -> str:
139
151
  """First step - Start the flow."""
140
- self.session[TOKEN_CACHE] = None
141
- self.session[USER_EMAIL] = None
152
+ self.session[TOKEN_CACHE] = None # type: ignore
153
+ self.session[USER_EMAIL] = None # type: ignore
142
154
  self.session[FLOW_CACHE] = res = self.app.initiate_auth_code_flow(
143
155
  MY_SCOPE,
144
156
  redirect_uri=redirect_uri,
@@ -149,8 +161,7 @@ class AsyncMSAL:
149
161
  # https://msal-python.readthedocs.io/en/latest/#msal.ClientApplication.initiate_auth_code_flow
150
162
  return str(res["auth_uri"])
151
163
 
152
- @async_wrap
153
- def async_acquire_token_by_auth_code_flow(self, auth_response: Any) -> None:
164
+ def acquire_token_by_auth_code_flow(self, auth_response: Any) -> None:
154
165
  """Second step - Acquire token."""
155
166
  # Assume we have it in the cache (added by /login)
156
167
  # will raise keryerror if no cache
@@ -165,8 +176,13 @@ class AsyncMSAL:
165
176
  "preferred_username"
166
177
  )
167
178
 
168
- @async_wrap
169
- def async_get_token(self) -> Optional[dict[str, Any]]:
179
+ async def async_acquire_token_by_auth_code_flow(self, auth_response: Any) -> None:
180
+ """Second step - Acquire token, async version."""
181
+ await asyncio.get_event_loop().run_in_executor(
182
+ None, self.acquire_token_by_auth_code_flow, auth_response
183
+ )
184
+
185
+ def get_token(self) -> Optional[dict[str, Any]]:
170
186
  """Acquire a token based on username."""
171
187
  accounts = self.app.get_accounts()
172
188
  if accounts:
@@ -175,6 +191,10 @@ class AsyncMSAL:
175
191
  return result
176
192
  return None
177
193
 
194
+ async def async_get_token(self) -> Optional[dict[str, Any]]:
195
+ """Acquire a token based on username."""
196
+ return await asyncio.get_event_loop().run_in_executor(None, self.get_token)
197
+
178
198
  async def request(self, method: str, url: str, **kwargs: Any) -> ClientResponse:
179
199
  """Make a request to url using an oauth session.
180
200
 
@@ -188,6 +208,8 @@ class AsyncMSAL:
188
208
  AsyncMSAL._clientsession = ClientSession(trust_env=True)
189
209
 
190
210
  token = await self.async_get_token()
211
+ if token is None:
212
+ raise web.HTTPClientError(text="No login token available.")
191
213
 
192
214
  kwargs = kwargs.copy()
193
215
  # Ensure headers exist & make a copy
@@ -195,7 +217,8 @@ class AsyncMSAL:
195
217
 
196
218
  headers["Authorization"] = "Bearer " + token["access_token"]
197
219
 
198
- assert method in HTTP_ALLOWED, "Method must be one of the allowed ones"
220
+ if method not in HTTP_ALLOWED:
221
+ raise web.HTTPClientError(text=f"HTTP method {method} not allowed")
199
222
 
200
223
  if method == HTTP_GET:
201
224
  kwargs.setdefault("allow_redirects", True)
@@ -0,0 +1,87 @@
1
+ """Redis tools for sessions."""
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ import time
6
+ from typing import Any, AsyncGenerator, Optional
7
+
8
+ from redis.asyncio import Redis, from_url
9
+
10
+ from aiohttp_msal.msal_async import AsyncMSAL
11
+ from aiohttp_msal.settings import ENV
12
+
13
+ _LOGGER = logging.getLogger(__name__)
14
+
15
+ SES_KEYS = ("mail", "name", "m_mail", "m_name")
16
+
17
+
18
+ def get_redis() -> Redis:
19
+ """Get a Redis connection."""
20
+ _LOGGER.info("Connect to Redis %s", ENV.REDIS)
21
+ ENV.database = from_url(ENV.REDIS) # pylint: disable=no-member
22
+ return ENV.database
23
+
24
+
25
+ async def iter_redis(
26
+ redis: Redis, *, clean: bool = False, match: Optional[dict[str, str]] = None
27
+ ) -> AsyncGenerator[tuple[str, str, dict], None]:
28
+ """Iterate over the Redis keys to find a specific session."""
29
+ async for key in redis.scan_iter(count=100, match=f"{ENV.COOKIE_NAME}*"):
30
+ sval = await redis.get(key)
31
+ if not isinstance(sval, str):
32
+ if clean:
33
+ await redis.delete(key)
34
+ continue
35
+ val = json.loads(sval)
36
+ ses = val.get("session")
37
+ created = val.get("created")
38
+ if clean and not ses or not created:
39
+ await redis.delete(key)
40
+ continue
41
+ if match:
42
+ for mkey, mval in match.items():
43
+ if mval not in ses[mkey]:
44
+ continue
45
+ created = val.get("created") or "0"
46
+ session = val.get("session") or {}
47
+ yield key, created, session
48
+
49
+
50
+ async def clean_redis(redis: Redis, max_age: int = 90) -> None:
51
+ """Clear session entries older than max_age days."""
52
+ expire = int(time.time() - max_age * 24 * 60 * 60)
53
+ async for key, created, ses in iter_redis(redis, clean=True):
54
+ for key in SES_KEYS:
55
+ if not ses.get(key):
56
+ await redis.delete(key)
57
+ continue
58
+ if int(created) < expire:
59
+ await redis.delete(key)
60
+
61
+
62
+ def _session_factory(key: str, created: str, session: dict) -> AsyncMSAL:
63
+ """Create a session with a save callback."""
64
+
65
+ async def async_save_cache(_: dict) -> None:
66
+ """Save the token cache to Redis."""
67
+ rd2 = get_redis()
68
+ try:
69
+ await rd2.set(key, json.dumps({"created": created, "session": session}))
70
+ finally:
71
+ await rd2.close()
72
+
73
+ def save_cache(*args: Any) -> None:
74
+ """Save the token cache to Redis."""
75
+ try:
76
+ asyncio.get_event_loop().create_task(async_save_cache(*args))
77
+ except RuntimeError:
78
+ asyncio.run(async_save_cache(*args))
79
+
80
+ return AsyncMSAL(session, save_cache=save_cache)
81
+
82
+
83
+ async def get_session(red: Redis, email: str) -> AsyncMSAL:
84
+ """Get a session from Redis."""
85
+ async for key, created, session in iter_redis(red, match={"mail": email}):
86
+ return _session_factory(key, created, session)
87
+ raise ValueError(f"Session for {email} not found")
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aiohttp-msal
3
- Version: 0.5.4
4
- Summary: Helper Library to use MSAL with aiohttp
3
+ Version: 0.6
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
7
7
  Author-email: kellerza@gmail.com
8
8
  License: MIT
9
9
  Keywords: msal,oauth,aiohttp,asyncio
10
- Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Programming Language :: Python :: 3
@@ -15,11 +15,24 @@ Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
18
19
  Requires-Python: >=3.9
19
20
  Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: msal>=1.24.1
23
+ Requires-Dist: aiohttp_session>=2.12
24
+ Requires-Dist: aiohttp>=3.8
20
25
  Provides-Extra: redis
26
+ Requires-Dist: aiohttp_session[aioredis]>=2.12; extra == "redis"
21
27
  Provides-Extra: tests
22
- License-File: LICENSE
28
+ Requires-Dist: black==23.9.1; extra == "tests"
29
+ Requires-Dist: pylint; extra == "tests"
30
+ Requires-Dist: flake8; extra == "tests"
31
+ Requires-Dist: pytest-aiohttp; extra == "tests"
32
+ Requires-Dist: pytest; extra == "tests"
33
+ Requires-Dist: pytest-cov; extra == "tests"
34
+ Requires-Dist: pytest-asyncio; extra == "tests"
35
+ Requires-Dist: pytest-env; extra == "tests"
23
36
 
24
37
  # aiohttp_msal Python library
25
38
 
@@ -29,11 +42,10 @@ Blocking MSAL functions are executed in the executor thread. Should be useful un
29
42
 
30
43
  Tested with MSAL Python 1.21.0 onward - [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python)
31
44
 
32
-
33
45
  ## AsycMSAL class
34
46
 
35
47
  The AsyncMSAL class wraps the behavior in the following example app
36
- https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76
48
+ <https://github.com/Azure-Samples/ms-identity-python-webapp/blob/master/app.py#L76>
37
49
 
38
50
  It is responsible to manage tokens & token refreshes and as a client to retrieve data using these tokens.
39
51
 
@@ -41,7 +53,7 @@ It is responsible to manage tokens & token refreshes and as a client to retrieve
41
53
 
42
54
  Firstly you should get the tokens via OAuth
43
55
 
44
- 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
56
+ 1. `initiate_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
45
57
 
46
58
  The caller is expected to:
47
59
  1. somehow store this content, typically inside the current session of the server,
@@ -51,8 +63,7 @@ Firstly you should get the tokens via OAuth
51
63
 
52
64
  **Step 1** and part of **Step 3** is stored by this class in the aiohttp_session
53
65
 
54
- 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
55
-
66
+ 2. `acquire_token_by_auth_code_flow` [referernce](https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.initiate_auth_code_flow)
56
67
 
57
68
  ### Use the token
58
69
 
@@ -65,11 +76,11 @@ async with aiomsal.get("https://graph.microsoft.com/v1.0/me") as res:
65
76
  res = await res.json()
66
77
  ```
67
78
 
68
- # Example web server
79
+ ## Example web server
69
80
 
70
81
  Complete routes can be found in [routes.py](./aiohttp_msal/routes.py)
71
82
 
72
- ## Start the login process
83
+ ### Start the login process
73
84
 
74
85
  ```python
75
86
  @ROUTES.get("/user/login")
@@ -84,7 +95,7 @@ async def user_login(request: web.Request) -> web.Response:
84
95
  return web.HTTPFound(redir)
85
96
  ```
86
97
 
87
- ## Acquire the token after being redirected back to the server
98
+ ### Acquire the token after being redirected back to the server
88
99
 
89
100
  ```python
90
101
  @ROUTES.post(URI_USER_AUTHORIZED)
@@ -101,7 +112,7 @@ async def user_authorized(request: web.Request) -> web.Response:
101
112
 
102
113
  - `@ROUTES.get("/user/photo")`
103
114
 
104
- Serve the user's photo from his Microsoft profile
115
+ Serve the user's photo from their Microsoft profile
105
116
 
106
117
  - `get_user_info`
107
118
 
@@ -4,6 +4,7 @@ setup.cfg
4
4
  setup.py
5
5
  aiohttp_msal/__init__.py
6
6
  aiohttp_msal/msal_async.py
7
+ aiohttp_msal/redis_tools.py
7
8
  aiohttp_msal/routes.py
8
9
  aiohttp_msal/settings.py
9
10
  aiohttp_msal/settings_base.py
@@ -1,4 +1,4 @@
1
- msal>=1.21.0
1
+ msal>=1.24.1
2
2
  aiohttp_session>=2.12
3
3
  aiohttp>=3.8
4
4
 
@@ -6,7 +6,7 @@ aiohttp>=3.8
6
6
  aiohttp_session[aioredis]>=2.12
7
7
 
8
8
  [tests]
9
- black==23.3.0
9
+ black==23.9.1
10
10
  pylint
11
11
  flake8
12
12
  pytest-aiohttp
@@ -1,7 +1,7 @@
1
1
  [metadata]
2
2
  name = aiohttp_msal
3
3
  version = attr: aiohttp_msal.VERSION
4
- description = Helper Library to use MSAL with aiohttp
4
+ description = Helper Library to use the Microsoft Authentication Library (MSAL) with aiohttp
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
7
7
  url = https://github.com/kellerza/aiohttp_msal
@@ -10,7 +10,7 @@ author_email = kellerza@gmail.com
10
10
  license = MIT
11
11
  license_file = LICENSE
12
12
  classifiers =
13
- Development Status :: 2 - Pre-Alpha
13
+ Development Status :: 4 - Beta
14
14
  Intended Audience :: Developers
15
15
  Natural Language :: English
16
16
  Programming Language :: Python :: 3
@@ -18,6 +18,7 @@ classifiers =
18
18
  Programming Language :: Python :: 3.8
19
19
  Programming Language :: Python :: 3.9
20
20
  Programming Language :: Python :: 3.10
21
+ Programming Language :: Python :: 3.11
21
22
  keywords = msal, oauth, aiohttp, asyncio
22
23
 
23
24
  [options]
@@ -26,7 +27,7 @@ python_requires = >=3.9
26
27
  include_package_data = True
27
28
  tests_requires = file: requirements_test.txt
28
29
  install_requires =
29
- msal>=1.21.0
30
+ msal>=1.24.1
30
31
  aiohttp_session>=2.12
31
32
  aiohttp>=3.8
32
33
  zip_safe = true
@@ -35,7 +36,7 @@ zip_safe = true
35
36
  redis =
36
37
  aiohttp_session[aioredis]>=2.12
37
38
  tests =
38
- black==23.3.0
39
+ black==23.9.1
39
40
  pylint
40
41
  flake8
41
42
  pytest-aiohttp
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  """aiohttp_msal library setup."""
3
- import setuptools
3
+ from setuptools import setup
4
4
 
5
5
  if __name__ == "__main__":
6
- setuptools.setup()
6
+ setup()
@@ -1,2 +1,3 @@
1
1
  """Init."""
2
2
  import aiohttp_msal # noqa
3
+ import aiohttp_msal.routes # noqa
File without changes