Skip to content

Commit e1fa635

Browse files
authored
Merge pull request #60 from Colin-b/develop
Version 0.16.0
2 parents 531ef81 + 4419720 commit e1fa635

13 files changed

+1009
-76
lines changed

.github/workflows/release.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ['3.7', '3.8', '3.9', '3.10']
14+
python-version: ['3.8', '3.9', '3.10', '3.11']
1515

1616
steps:
17-
- uses: actions/checkout@v2
17+
- uses: actions/checkout@v3
1818
- name: Set up Python ${{ matrix.python-version }}
19-
uses: actions/setup-python@v2
19+
uses: actions/setup-python@v4
2020
with:
2121
python-version: ${{ matrix.python-version }}
2222
- name: Install dependencies
@@ -28,8 +28,8 @@ jobs:
2828
pytest --cov=httpx_auth --cov-fail-under=100 --cov-report=term-missing
2929
- name: Create packages
3030
run: |
31-
python -m pip install wheel
32-
python setup.py sdist bdist_wheel
31+
python -m pip install build
32+
python -m build .
3333
- name: Publish packages
3434
run: |
3535
python -m pip install twine

.github/workflows/test.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: ['3.7', '3.8', '3.9', '3.10']
11+
python-version: ['3.8', '3.9', '3.10', '3.11']
1212

1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v3
1515
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v2
16+
uses: actions/setup-python@v4
1717
with:
1818
python-version: ${{ matrix.python-version }}
1919
- name: Install dependencies

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
repos:
22
- repo: https://github.com/psf/black
3-
rev: 22.1.0
3+
rev: 23.3.0
44
hooks:
55
- id: black

CHANGELOG.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.16.0] - 2023-04-25
10+
### Changed
11+
- Requires [`httpx`](https://www.python-httpx.org)==0.24.\*
12+
13+
### Fixed
14+
- Handle `text/html; charset=utf-8` content-type in token responses. Thanks to [`Marcelo Trylesinski`](https://github.com/Kludex).
15+
16+
### Added
17+
- `httpx_auth.WakaTimeAuthorizationCode` handling access to the [WakaTime API](https://wakatime.com/developers).
18+
19+
### Removed
20+
- Python 3.7 is no longer supported.
21+
922
## [0.15.0] - 2022-06-01
1023
### Changed
1124
- Requires [`httpx`](https://www.python-httpx.org)==0.23.\*
@@ -157,7 +170,8 @@ Note that a few changes were made:
157170
### Added
158171
- Placeholder for port of requests_auth to httpx
159172

160-
[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.15.0...HEAD
173+
[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.16.0...HEAD
174+
[0.16.0]: https://github.com/Colin-b/httpx_auth/compare/v0.15.0...v0.16.0
161175
[0.15.0]: https://github.com/Colin-b/httpx_auth/compare/v0.14.1...v0.15.0
162176
[0.14.1]: https://github.com/Colin-b/httpx_auth/compare/v0.14.0...v0.14.1
163177
[0.14.0]: https://github.com/Colin-b/httpx_auth/compare/v0.13.0...v0.14.0

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 Colin Bounouar
3+
Copyright (c) 2023 Colin Bounouar
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+41-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
<a href="https://github.com/Colin-b/httpx_auth/actions"><img alt="Build status" src="https://github.com/Colin-b/httpx_auth/workflows/Release/badge.svg"></a>
66
<a href="https://github.com/Colin-b/httpx_auth/actions"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
77
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
8-
<a href="https://github.com/Colin-b/httpx_auth/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-275 passed-blue"></a>
8+
<a href="https://github.com/Colin-b/httpx_auth/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-307 passed-blue"></a>
99
<a href="https://pypi.org/project/httpx-auth/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/httpx_auth"></a>
1010
</p>
1111

1212
> Version 1.0.0 will be released once httpx is considered as stable (release of 1.0.0).
1313
>
14-
> However current state can be considered as stable.
14+
> However, current state can be considered as stable.
1515
1616
Provides authentication classes to be used with [`httpx`][1] [authentication parameter][2].
1717

@@ -27,6 +27,7 @@ Provides authentication classes to be used with [`httpx`][1] [authentication par
2727
- [OAuth2](#oauth-2)
2828
- [Authorization Code Flow](#authorization-code-flow)
2929
- [Okta](#okta-oauth2-authorization-code)
30+
- [WakaTime](#wakatime-oauth2-authorization-code)
3031
- [Authorization Code Flow with PKCE](#authorization-code-flow-with-proof-key-for-code-exchange)
3132
- [Okta](#okta-oauth2-proof-key-for-code-exchange)
3233
- [Resource Owner Password Credentials flow](#resource-owner-password-credentials-flow)
@@ -147,6 +148,44 @@ Usual extra parameters are:
147148
|:----------------|:---------------------------------------------------------------------|
148149
| `prompt` | none to avoid prompting the user if a session is already opened. |
149150

151+
##### WakaTime (OAuth2 Authorization Code)
152+
153+
[WakaTime Authorization Code Grant](https://wakatime.com/developers#authentication) providing access tokens is supported.
154+
155+
Use `httpx_auth.WakaTimeAuthorizationCode` to configure this kind of authentication.
156+
157+
```python
158+
import httpx
159+
from httpx_auth import WakaTimeAuthorizationCode
160+
161+
162+
waka_time = WakaTimeAuthorizationCode(client_id="aPJQV0op6Pu3b66MWDi9b1wB", client_secret="waka_sec_0c5MB", scope="email")
163+
with httpx.Client() as client:
164+
client.get('https://wakatime.com/api/v1/users/current', auth=waka_time)
165+
```
166+
167+
###### Parameters
168+
169+
| Name | Description | Mandatory | Default value |
170+
|:------------------------|:---------------------------|:----------|:---------------------------------------------|
171+
| `client_id` | WakaTime Application Identifier (formatted as an Universal Unique Identifier). | Mandatory | |
172+
| `client_secret` | WakaTime Application Secret (formatted as waka_sec_ followed by an Universal Unique Identifier). | Mandatory | |
173+
| `scope` | Scope parameter sent in query. Can also be a list of scopes. | Mandatory | |
174+
| `response_type` | Value of the response_type query parameter if not already provided in authorization URL. | Optional | token |
175+
| `token_field_name` | Field name containing the token. | Optional | access_token |
176+
| `early_expiry` | Number of seconds before actual token expiry where token will be considered as expired. Used to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. | Optional | 30.0 |
177+
| `nonce` | Refer to [OpenID ID Token specifications][3] for more details. | Optional | Newly generated Universal Unique Identifier. |
178+
| `redirect_uri_endpoint` | Custom endpoint that will be used as redirect_uri the following way: http://localhost:<redirect_uri_port>/<redirect_uri_endpoint>. | Optional | '' |
179+
| `redirect_uri_port` | The port on which the server listening for the OAuth 2 token will be started. | Optional | 5000 |
180+
| `timeout` | Maximum amount of seconds to wait for a token to be received once requested. | Optional | 60 |
181+
| `success_display_time` | In case a token is successfully received, this is the maximum amount of milliseconds the success page will be displayed in your browser. | Optional | 1 |
182+
| `failure_display_time` | In case received token is not valid, this is the maximum amount of milliseconds the failure page will be displayed in your browser. | Optional | 5000 |
183+
| `header_name` | Name of the header field used to send token. | Optional | Authorization |
184+
| `header_value` | Format used to send the token value. "{token}" must be present as it will be replaced by the actual token. | Optional | Bearer {token} |
185+
| `client` | `httpx.Client` instance that will be used to request the token. Use it to provide a custom proxying rule for instance. | Optional | |
186+
187+
Any other parameter will be put as query parameter in the authorization URL.
188+
150189
### Authorization Code Flow with Proof Key for Code Exchange
151190

152191
Proof Key for Code Exchange is implemented following [rfc7636](https://tools.ietf.org/html/rfc7636).

httpx_auth/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
OAuth2ClientCredentials,
1616
OktaClientCredentials,
1717
OAuth2ResourceOwnerPasswordCredentials,
18+
WakaTimeAuthorizationCode,
1819
)
1920
from httpx_auth.oauth2_tokens import JsonTokenFileCache
2021
from httpx_auth.aws import AWS4Auth
@@ -46,6 +47,7 @@
4647
"OAuth2ClientCredentials",
4748
"OktaClientCredentials",
4849
"OAuth2ResourceOwnerPasswordCredentials",
50+
"WakaTimeAuthorizationCode",
4951
"JsonTokenFileCache",
5052
"AWS4Auth",
5153
"GrantNotProvided",

httpx_auth/authentication.py

+73-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import uuid
44
from hashlib import sha256, sha512
55
from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode
6-
from typing import Optional, Generator
6+
from typing import Optional, Generator, Union, Iterable
77

88
import httpx
99

@@ -59,6 +59,17 @@ def _get_query_parameter(url: str, param_name: str) -> Optional[str]:
5959
return all_values[0] if all_values else None
6060

6161

62+
def _content_from_response(response: httpx.Response) -> dict:
63+
content_type = response.headers.get("content-type")
64+
if content_type == "text/html; charset=utf-8":
65+
return {
66+
key_values[0]: key_values[1]
67+
for key_value in response.text.split("&")
68+
if (key_values := key_value.split("=")) and len(key_values) == 2
69+
}
70+
return response.json()
71+
72+
6273
def request_new_grant_with_post(
6374
url: str, data, grant_name: str, client: httpx.Client
6475
) -> (str, int):
@@ -68,7 +79,7 @@ def request_new_grant_with_post(
6879
# As described in https://tools.ietf.org/html/rfc6749#section-5.2
6980
raise InvalidGrantRequest(response)
7081

71-
content = response.json()
82+
content = _content_from_response(response)
7283
token = content.get(grant_name)
7384
if not token:
7485
raise GrantNotProvided(grant_name, content)
@@ -1051,6 +1062,66 @@ def __init__(self, instance: str, client_id: str, **kwargs):
10511062
)
10521063

10531064

1065+
class WakaTimeAuthorizationCode(OAuth2AuthorizationCode):
1066+
"""
1067+
Describes a WakaTime (OAuth 2) "Access Token" authorization code flow requests authentication.
1068+
"""
1069+
1070+
def __init__(
1071+
self,
1072+
client_id: str,
1073+
client_secret: str,
1074+
scope: Union[str, Iterable[str]],
1075+
**kwargs,
1076+
):
1077+
"""
1078+
:param client_id: WakaTime Application Identifier (formatted as an Universal Unique Identifier)
1079+
:param client_secret: WakaTime Application Secret (formatted as waka_sec_ followed by an Universal Unique Identifier)
1080+
:param scope: Scope parameter sent in query. Can also be a list of scopes.
1081+
:param response_type: Value of the response_type query parameter.
1082+
token by default.
1083+
:param token_field_name: Name of the expected field containing the token.
1084+
access_token by default.
1085+
:param early_expiry: Number of seconds before actual token expiry where token will be considered as expired.
1086+
Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request
1087+
reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry.
1088+
:param nonce: Refer to http://openid.net/specs/openid-connect-core-1_0.html#IDToken for more details
1089+
(formatted as an Universal Unique Identifier - UUID). Use a newly generated UUID by default.
1090+
:param redirect_uri_endpoint: Custom endpoint that will be used as redirect_uri the following way:
1091+
http://localhost:<redirect_uri_port>/<redirect_uri_endpoint>. Default value is to redirect on / (root).
1092+
:param redirect_uri_port: The port on which the server listening for the OAuth 2 token will be started.
1093+
Listen on port 5000 by default.
1094+
:param timeout: Maximum amount of seconds to wait for a token to be received once requested.
1095+
Wait for 1 minute by default.
1096+
:param success_display_time: In case a token is successfully received,
1097+
this is the maximum amount of milliseconds the success page will be displayed in your browser.
1098+
Display the page for 1 millisecond by default.
1099+
:param failure_display_time: In case received token is not valid,
1100+
this is the maximum amount of milliseconds the failure page will be displayed in your browser.
1101+
Display the page for 5 seconds by default.
1102+
:param header_name: Name of the header field used to send token.
1103+
Token will be sent in Authorization header field by default.
1104+
:param header_value: Format used to send the token value.
1105+
"{token}" must be present as it will be replaced by the actual token.
1106+
Token will be sent as "Bearer {token}" by default.
1107+
:param client: httpx.Client instance that will be used to request the token.
1108+
Use it to provide a custom proxying rule for instance.
1109+
:param kwargs: all additional authorization parameters that should be put as query parameter
1110+
in the authorization URL.
1111+
"""
1112+
if not scope:
1113+
raise Exception("Scope is mandatory.")
1114+
OAuth2AuthorizationCode.__init__(
1115+
self,
1116+
"https://wakatime.com/oauth/authorize",
1117+
"https://wakatime.com/oauth/token",
1118+
client_id=client_id,
1119+
client_secret=client_secret,
1120+
scope=",".join(scope) if isinstance(scope, list) else scope,
1121+
**kwargs,
1122+
)
1123+
1124+
10541125
class OktaAuthorizationCodePKCE(OAuth2AuthorizationCodePKCE):
10551126
"""
10561127
Describes an Okta (OAuth 2) "Access Token" Proof Key for Code Exchange (PKCE) flow requests authentication.

httpx_auth/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0)
44
# Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0)
55
# Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
6-
__version__ = "0.15.0"
6+
__version__ = "0.16.0"

pyproject.toml

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
[build-system]
2+
requires = ["setuptools", "setuptools_scm"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "httpx_auth"
7+
description = "Authentication for HTTPX"
8+
readme = "README.md"
9+
requires-python = ">=3.8"
10+
license = {file = "LICENSE"}
11+
authors = [
12+
{name = "Colin Bounouar", email = "[email protected]" }
13+
]
14+
maintainers = [
15+
{name = "Colin Bounouar", email = "[email protected]" }
16+
]
17+
keywords = ["authentication", "oauth2", "aws", "okta", "aad"]
18+
classifiers=[
19+
"Development Status :: 5 - Production/Stable",
20+
"Intended Audience :: Developers",
21+
"License :: OSI Approved :: MIT License",
22+
"Natural Language :: English",
23+
"Typing :: Typed",
24+
"Programming Language :: Python",
25+
"Programming Language :: Python :: 3",
26+
"Programming Language :: Python :: 3.8",
27+
"Programming Language :: Python :: 3.9",
28+
"Programming Language :: Python :: 3.10",
29+
"Programming Language :: Python :: 3.11",
30+
"Topic :: Software Development :: Build Tools",
31+
]
32+
dependencies = [
33+
"httpx==0.24.*",
34+
]
35+
dynamic = ["version"]
36+
37+
[project.urls]
38+
documentation = "https://colin-b.github.io/httpx_auth/"
39+
repository = "https://github.com/Colin-b/httpx_auth"
40+
changelog = "https://github.com/Colin-b/httpx_auth/blob/master/CHANGELOG.md"
41+
issues = "https://github.com/Colin-b/httpx_auth/issues"
42+
43+
[project.optional-dependencies]
44+
testing = [
45+
# Used to generate test tokens
46+
"pyjwt==2.*",
47+
# Used to mock httpx
48+
"pytest_httpx==0.22.*",
49+
# Used to check coverage
50+
"pytest-cov==4.*",
51+
]
52+
53+
[tool.setuptools.packages.find]
54+
exclude = ["tests*"]
55+
56+
[tool.setuptools.dynamic]
57+
version = {attr = "httpx_auth.version.__version__"}

0 commit comments

Comments
 (0)