Skip to content

Commit aec4e38

Browse files
authored
Merge pull request #87 from Colin-b/develop
Release version 0.22.0
2 parents 22ff327 + b9d19d6 commit aec4e38

11 files changed

Lines changed: 120 additions & 108 deletions

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.22.0] - 2024-03-02
10+
### Changed
11+
- Requires [`httpx`](https://www.python-httpx.org)==0.27.\*
12+
- `httpx_auth.JsonTokenFileCache` and `httpx_auth.TokenMemoryCache` `get_token` method does not handle kwargs anymore, the `on_missing_token` callable does not expect any arguments anymore.
13+
914
## [0.21.0] - 2024-02-19
1015
### Added
1116
- Publicly expose `httpx_auth.SupportMultiAuth`, allowing multiple authentication support for every `httpx` authentication class that exists.
@@ -245,7 +250,8 @@ Note that a few changes were made:
245250
### Added
246251
- Placeholder for port of requests_auth to httpx
247252

248-
[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.21.0...HEAD
253+
[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.22.0...HEAD
254+
[0.22.0]: https://github.com/Colin-b/httpx_auth/compare/v0.21.0...v0.22.0
249255
[0.21.0]: https://github.com/Colin-b/httpx_auth/compare/v0.20.0...v0.21.0
250256
[0.20.0]: https://github.com/Colin-b/httpx_auth/compare/v0.19.0...v0.20.0
251257
[0.19.0]: https://github.com/Colin-b/httpx_auth/compare/v0.18.0...v0.19.0

httpx_auth/_oauth2/authentication_responses_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def handle_timeout(self) -> None:
166166
raise TimeoutOccurred(self.timeout)
167167

168168

169-
def request_new_grant(grant_details: GrantDetails) -> (str, str):
169+
def request_new_grant(grant_details: GrantDetails) -> tuple[str, str]:
170170
"""
171171
Ask for a new OAuth2 grant.
172172
:return: A tuple (state, grant)

httpx_auth/_oauth2/authorization_code.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from hashlib import sha512
2-
from typing import Generator, Iterable, Union
2+
from typing import Iterable, Union
33

44
import httpx
55

@@ -8,14 +8,14 @@
88
from httpx_auth._oauth2.browser import BrowserAuth
99
from httpx_auth._oauth2.common import (
1010
request_new_grant_with_post,
11-
OAuth2,
11+
OAuth2BaseAuth,
1212
_add_parameters,
1313
_pop_parameter,
1414
_get_query_parameter,
1515
)
1616

1717

18-
class OAuth2AuthorizationCode(httpx.Auth, SupportMultiAuth, BrowserAuth):
18+
class OAuth2AuthorizationCode(OAuth2BaseAuth, SupportMultiAuth, BrowserAuth):
1919
"""
2020
Authorization Code Grant
2121
@@ -71,13 +71,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
7171

7272
BrowserAuth.__init__(self, kwargs)
7373

74-
self.header_name = kwargs.pop("header_name", None) or "Authorization"
75-
self.header_value = kwargs.pop("header_value", None) or "Bearer {token}"
76-
if "{token}" not in self.header_value:
77-
raise Exception("header_value parameter must contains {token}.")
74+
header_name = kwargs.pop("header_name", None) or "Authorization"
75+
header_value = kwargs.pop("header_value", None) or "Bearer {token}"
7876

7977
self.token_field_name = kwargs.pop("token_field_name", None) or "access_token"
80-
self.early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
78+
early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
8179

8280
username = kwargs.pop("username", None)
8381
password = kwargs.pop("password", None)
@@ -99,11 +97,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
9997
authorization_url_without_nonce, nonce = _pop_parameter(
10098
authorization_url_without_nonce, "nonce"
10199
)
102-
self.state = sha512(
100+
state = sha512(
103101
authorization_url_without_nonce.encode("unicode_escape")
104102
).hexdigest()
105103
custom_code_parameters = {
106-
"state": self.state,
104+
"state": state,
107105
"redirect_uri": self.redirect_uri,
108106
}
109107
if nonce:
@@ -129,17 +127,14 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
129127
self.refresh_data = {"grant_type": "refresh_token"}
130128
self.refresh_data.update(kwargs)
131129

132-
def auth_flow(
133-
self, request: httpx.Request
134-
) -> Generator[httpx.Request, httpx.Response, None]:
135-
token = OAuth2.token_cache.get_token(
136-
self.state,
137-
early_expiry=self.early_expiry,
138-
on_missing_token=self.request_new_token,
139-
on_expired_token=self.refresh_token,
130+
OAuth2BaseAuth.__init__(
131+
self,
132+
state,
133+
early_expiry,
134+
header_name,
135+
header_value,
136+
self.refresh_token,
140137
)
141-
request.headers[self.header_name] = self.header_value.format(token=token)
142-
yield request
143138

144139
def request_new_token(self) -> tuple:
145140
# Request code

httpx_auth/_oauth2/authorization_code_pkce.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import base64
22
import os
33
from hashlib import sha256, sha512
4-
from typing import Generator
54

65
import httpx
76

@@ -10,13 +9,13 @@
109
from httpx_auth._oauth2.browser import BrowserAuth
1110
from httpx_auth._oauth2.common import (
1211
request_new_grant_with_post,
13-
OAuth2,
12+
OAuth2BaseAuth,
1413
_add_parameters,
1514
_pop_parameter,
1615
)
1716

1817

19-
class OAuth2AuthorizationCodePKCE(httpx.Auth, SupportMultiAuth, BrowserAuth):
18+
class OAuth2AuthorizationCodePKCE(OAuth2BaseAuth, SupportMultiAuth, BrowserAuth):
2019
"""
2120
Proof Key for Code Exchange
2221
@@ -72,13 +71,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
7271

7372
self.client = kwargs.pop("client", None)
7473

75-
self.header_name = kwargs.pop("header_name", None) or "Authorization"
76-
self.header_value = kwargs.pop("header_value", None) or "Bearer {token}"
77-
if "{token}" not in self.header_value:
78-
raise Exception("header_value parameter must contains {token}.")
74+
header_name = kwargs.pop("header_name", None) or "Authorization"
75+
header_value = kwargs.pop("header_value", None) or "Bearer {token}"
7976

8077
self.token_field_name = kwargs.pop("token_field_name", None) or "access_token"
81-
self.early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
78+
early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
8279

8380
# As described in https://tools.ietf.org/html/rfc6749#section-4.1.2
8481
code_field_name = kwargs.pop("code_field_name", "code")
@@ -98,11 +95,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
9895
authorization_url_without_nonce, nonce = _pop_parameter(
9996
authorization_url_without_nonce, "nonce"
10097
)
101-
self.state = sha512(
98+
state = sha512(
10299
authorization_url_without_nonce.encode("unicode_escape")
103100
).hexdigest()
104101
custom_code_parameters = {
105-
"state": self.state,
102+
"state": state,
106103
"redirect_uri": self.redirect_uri,
107104
}
108105
if nonce:
@@ -139,17 +136,9 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs):
139136
self.refresh_data = {"grant_type": "refresh_token"}
140137
self.refresh_data.update(kwargs)
141138

142-
def auth_flow(
143-
self, request: httpx.Request
144-
) -> Generator[httpx.Request, httpx.Response, None]:
145-
token = OAuth2.token_cache.get_token(
146-
self.state,
147-
early_expiry=self.early_expiry,
148-
on_missing_token=self.request_new_token,
149-
on_expired_token=self.refresh_token,
139+
OAuth2BaseAuth.__init__(
140+
self, state, early_expiry, header_name, header_value, self.refresh_token
150141
)
151-
request.headers[self.header_name] = self.header_value.format(token=token)
152-
yield request
153142

154143
def request_new_token(self) -> tuple:
155144
# Request code

httpx_auth/_oauth2/client_credentials.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
from hashlib import sha512
2-
from typing import Generator, Union, Iterable
2+
from typing import Union, Iterable
33

44
import httpx
55
from httpx_auth._authentication import SupportMultiAuth
66
from httpx_auth._oauth2.common import (
7-
OAuth2,
7+
OAuth2BaseAuth,
88
request_new_grant_with_post,
99
_add_parameters,
1010
)
1111

1212

13-
class OAuth2ClientCredentials(httpx.Auth, SupportMultiAuth):
13+
class OAuth2ClientCredentials(OAuth2BaseAuth, SupportMultiAuth):
1414
"""
1515
Client Credentials Grant
1616
@@ -49,13 +49,11 @@ def __init__(self, token_url: str, client_id: str, client_secret: str, **kwargs)
4949
if not self.client_secret:
5050
raise Exception("client_secret is mandatory.")
5151

52-
self.header_name = kwargs.pop("header_name", None) or "Authorization"
53-
self.header_value = kwargs.pop("header_value", None) or "Bearer {token}"
54-
if "{token}" not in self.header_value:
55-
raise Exception("header_value parameter must contains {token}.")
52+
header_name = kwargs.pop("header_name", None) or "Authorization"
53+
header_value = kwargs.pop("header_value", None) or "Bearer {token}"
5654

5755
self.token_field_name = kwargs.pop("token_field_name", None) or "access_token"
58-
self.early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
56+
early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
5957

6058
# Time is expressed in seconds
6159
self.timeout = int(kwargs.pop("timeout", None) or 60)
@@ -70,18 +68,14 @@ def __init__(self, token_url: str, client_id: str, client_secret: str, **kwargs)
7068
self.data.update(kwargs)
7169

7270
all_parameters_in_url = _add_parameters(self.token_url, self.data)
73-
self.state = sha512(all_parameters_in_url.encode("unicode_escape")).hexdigest()
74-
75-
def auth_flow(
76-
self, request: httpx.Request
77-
) -> Generator[httpx.Request, httpx.Response, None]:
78-
token = OAuth2.token_cache.get_token(
79-
self.state,
80-
early_expiry=self.early_expiry,
81-
on_missing_token=self.request_new_token,
71+
state = sha512(all_parameters_in_url.encode("unicode_escape")).hexdigest()
72+
73+
super().__init__(
74+
state,
75+
early_expiry,
76+
header_name,
77+
header_value,
8278
)
83-
request.headers[self.header_name] = self.header_value.format(token=token)
84-
yield request
8579

8680
def request_new_token(self) -> tuple:
8781
client = self.client or httpx.Client()

httpx_auth/_oauth2/common.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Optional
1+
import abc
2+
from typing import Callable, Generator, Optional, Union
23
from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode
34

45
import httpx
@@ -86,3 +87,41 @@ def request_new_grant_with_post(
8687
class OAuth2:
8788
token_cache = TokenMemoryCache()
8889
display = DisplaySettings()
90+
91+
92+
class OAuth2BaseAuth(abc.ABC, httpx.Auth):
93+
def __init__(
94+
self,
95+
state: str,
96+
early_expiry: float,
97+
header_name: str,
98+
header_value: str,
99+
refresh_token: Optional[Callable] = None,
100+
) -> None:
101+
if "{token}" not in header_value:
102+
raise Exception("header_value parameter must contains {token}.")
103+
104+
self.state = state
105+
self.early_expiry = early_expiry
106+
self.header_name = header_name
107+
self.header_value = header_value
108+
self.refresh_token = refresh_token
109+
110+
def auth_flow(
111+
self, request: httpx.Request
112+
) -> Generator[httpx.Request, httpx.Response, None]:
113+
token = OAuth2.token_cache.get_token(
114+
self.state,
115+
early_expiry=self.early_expiry,
116+
on_missing_token=self.request_new_token,
117+
on_expired_token=self.refresh_token,
118+
)
119+
self._update_user_request(request, token)
120+
yield request
121+
122+
@abc.abstractmethod
123+
def request_new_token(self) -> Union[tuple[str, str], tuple[str, str, int]]:
124+
pass # pragma: no cover
125+
126+
def _update_user_request(self, request: httpx.Request, token: str) -> None:
127+
request.headers[self.header_name] = self.header_value.format(token=token)

httpx_auth/_oauth2/implicit.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import uuid
22
from hashlib import sha512
3-
from typing import Generator
43

54
import httpx
65

76
from httpx_auth._authentication import SupportMultiAuth
87
from httpx_auth._oauth2 import authentication_responses_server
98
from httpx_auth._oauth2.browser import BrowserAuth
109
from httpx_auth._oauth2.common import (
11-
OAuth2,
10+
OAuth2BaseAuth,
1211
_add_parameters,
1312
_pop_parameter,
1413
_get_query_parameter,
1514
)
1615

1716

18-
class OAuth2Implicit(httpx.Auth, SupportMultiAuth, BrowserAuth):
17+
class OAuth2Implicit(OAuth2BaseAuth, SupportMultiAuth, BrowserAuth):
1918
"""
2019
Implicit Grant
2120
@@ -62,10 +61,8 @@ def __init__(self, authorization_url: str, **kwargs):
6261

6362
BrowserAuth.__init__(self, kwargs)
6463

65-
self.header_name = kwargs.pop("header_name", None) or "Authorization"
66-
self.header_value = kwargs.pop("header_value", None) or "Bearer {token}"
67-
if "{token}" not in self.header_value:
68-
raise Exception("header_value parameter must contains {token}.")
64+
header_name = kwargs.pop("header_name", None) or "Authorization"
65+
header_value = kwargs.pop("header_value", None) or "Bearer {token}"
6966

7067
response_type = _get_query_parameter(self.authorization_url, "response_type")
7168
if response_type:
@@ -82,18 +79,18 @@ def __init__(self, authorization_url: str, **kwargs):
8279
"id_token" if "id_token" == response_type else "access_token"
8380
)
8481

85-
self.early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
82+
early_expiry = float(kwargs.pop("early_expiry", None) or 30.0)
8683

8784
authorization_url_without_nonce = _add_parameters(
8885
self.authorization_url, kwargs
8986
)
9087
authorization_url_without_nonce, nonce = _pop_parameter(
9188
authorization_url_without_nonce, "nonce"
9289
)
93-
self.state = sha512(
90+
state = sha512(
9491
authorization_url_without_nonce.encode("unicode_escape")
9592
).hexdigest()
96-
custom_parameters = {"state": self.state, "redirect_uri": self.redirect_uri}
93+
custom_parameters = {"state": state, "redirect_uri": self.redirect_uri}
9794
if nonce:
9895
custom_parameters["nonce"] = nonce
9996
grant_url = _add_parameters(authorization_url_without_nonce, custom_parameters)
@@ -104,17 +101,16 @@ def __init__(self, authorization_url: str, **kwargs):
104101
self.redirect_uri_port,
105102
)
106103

107-
def auth_flow(
108-
self, request: httpx.Request
109-
) -> Generator[httpx.Request, httpx.Response, None]:
110-
token = OAuth2.token_cache.get_token(
111-
self.state,
112-
early_expiry=self.early_expiry,
113-
on_missing_token=authentication_responses_server.request_new_grant,
114-
grant_details=self.grant_details,
104+
OAuth2BaseAuth.__init__(
105+
self,
106+
state,
107+
early_expiry,
108+
header_name,
109+
header_value,
115110
)
116-
request.headers[self.header_name] = self.header_value.format(token=token)
117-
yield request
111+
112+
def request_new_token(self) -> tuple[str, str]:
113+
return authentication_responses_server.request_new_grant(self.grant_details)
118114

119115

120116
class AzureActiveDirectoryImplicit(OAuth2Implicit):

0 commit comments

Comments
 (0)