Skip to content

Commit

Permalink
Merge pull request #60 from Colin-b/develop
Browse files Browse the repository at this point in the history
Version 0.16.0
  • Loading branch information
Colin-b authored Apr 25, 2023
2 parents 531ef81 + 4419720 commit e1fa635
Show file tree
Hide file tree
Showing 13 changed files with 1,009 additions and 76 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10', '3.11']

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -28,8 +28,8 @@ jobs:
pytest --cov=httpx_auth --cov-fail-under=100 --cov-report=term-missing
- name: Create packages
run: |
python -m pip install wheel
python setup.py sdist bdist_wheel
python -m pip install build
python -m build .
- name: Publish packages
run: |
python -m pip install twine
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10', '3.11']

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/psf/black
rev: 22.1.0
rev: 23.3.0
hooks:
- id: black
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.16.0] - 2023-04-25
### Changed
- Requires [`httpx`](https://www.python-httpx.org)==0.24.\*

### Fixed
- Handle `text/html; charset=utf-8` content-type in token responses. Thanks to [`Marcelo Trylesinski`](https://github.com/Kludex).

### Added
- `httpx_auth.WakaTimeAuthorizationCode` handling access to the [WakaTime API](https://wakatime.com/developers).

### Removed
- Python 3.7 is no longer supported.

## [0.15.0] - 2022-06-01
### Changed
- Requires [`httpx`](https://www.python-httpx.org)==0.23.\*
Expand Down Expand Up @@ -157,7 +170,8 @@ Note that a few changes were made:
### Added
- Placeholder for port of requests_auth to httpx

[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.15.0...HEAD
[Unreleased]: https://github.com/Colin-b/httpx_auth/compare/v0.16.0...HEAD
[0.16.0]: https://github.com/Colin-b/httpx_auth/compare/v0.15.0...v0.16.0
[0.15.0]: https://github.com/Colin-b/httpx_auth/compare/v0.14.1...v0.15.0
[0.14.1]: https://github.com/Colin-b/httpx_auth/compare/v0.14.0...v0.14.1
[0.14.0]: https://github.com/Colin-b/httpx_auth/compare/v0.13.0...v0.14.0
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Colin Bounouar
Copyright (c) 2023 Colin Bounouar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<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>
<a href="https://github.com/Colin-b/httpx_auth/actions"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<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>
<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>
<a href="https://pypi.org/project/httpx-auth/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/httpx_auth"></a>
</p>

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

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

##### WakaTime (OAuth2 Authorization Code)

[WakaTime Authorization Code Grant](https://wakatime.com/developers#authentication) providing access tokens is supported.

Use `httpx_auth.WakaTimeAuthorizationCode` to configure this kind of authentication.

```python
import httpx
from httpx_auth import WakaTimeAuthorizationCode


waka_time = WakaTimeAuthorizationCode(client_id="aPJQV0op6Pu3b66MWDi9b1wB", client_secret="waka_sec_0c5MB", scope="email")
with httpx.Client() as client:
client.get('https://wakatime.com/api/v1/users/current', auth=waka_time)
```

###### Parameters

| Name | Description | Mandatory | Default value |
|:------------------------|:---------------------------|:----------|:---------------------------------------------|
| `client_id` | WakaTime Application Identifier (formatted as an Universal Unique Identifier). | Mandatory | |
| `client_secret` | WakaTime Application Secret (formatted as waka_sec_ followed by an Universal Unique Identifier). | Mandatory | |
| `scope` | Scope parameter sent in query. Can also be a list of scopes. | Mandatory | |
| `response_type` | Value of the response_type query parameter if not already provided in authorization URL. | Optional | token |
| `token_field_name` | Field name containing the token. | Optional | access_token |
| `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 |
| `nonce` | Refer to [OpenID ID Token specifications][3] for more details. | Optional | Newly generated Universal Unique Identifier. |
| `redirect_uri_endpoint` | Custom endpoint that will be used as redirect_uri the following way: http://localhost:<redirect_uri_port>/<redirect_uri_endpoint>. | Optional | '' |
| `redirect_uri_port` | The port on which the server listening for the OAuth 2 token will be started. | Optional | 5000 |
| `timeout` | Maximum amount of seconds to wait for a token to be received once requested. | Optional | 60 |
| `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 |
| `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 |
| `header_name` | Name of the header field used to send token. | Optional | Authorization |
| `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} |
| `client` | `httpx.Client` instance that will be used to request the token. Use it to provide a custom proxying rule for instance. | Optional | |

Any other parameter will be put as query parameter in the authorization URL.

### Authorization Code Flow with Proof Key for Code Exchange

Proof Key for Code Exchange is implemented following [rfc7636](https://tools.ietf.org/html/rfc7636).
Expand Down
2 changes: 2 additions & 0 deletions httpx_auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
OAuth2ClientCredentials,
OktaClientCredentials,
OAuth2ResourceOwnerPasswordCredentials,
WakaTimeAuthorizationCode,
)
from httpx_auth.oauth2_tokens import JsonTokenFileCache
from httpx_auth.aws import AWS4Auth
Expand Down Expand Up @@ -46,6 +47,7 @@
"OAuth2ClientCredentials",
"OktaClientCredentials",
"OAuth2ResourceOwnerPasswordCredentials",
"WakaTimeAuthorizationCode",
"JsonTokenFileCache",
"AWS4Auth",
"GrantNotProvided",
Expand Down
75 changes: 73 additions & 2 deletions httpx_auth/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import uuid
from hashlib import sha256, sha512
from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode
from typing import Optional, Generator
from typing import Optional, Generator, Union, Iterable

import httpx

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


def _content_from_response(response: httpx.Response) -> dict:
content_type = response.headers.get("content-type")
if content_type == "text/html; charset=utf-8":
return {
key_values[0]: key_values[1]
for key_value in response.text.split("&")
if (key_values := key_value.split("=")) and len(key_values) == 2
}
return response.json()


def request_new_grant_with_post(
url: str, data, grant_name: str, client: httpx.Client
) -> (str, int):
Expand All @@ -68,7 +79,7 @@ def request_new_grant_with_post(
# As described in https://tools.ietf.org/html/rfc6749#section-5.2
raise InvalidGrantRequest(response)

content = response.json()
content = _content_from_response(response)
token = content.get(grant_name)
if not token:
raise GrantNotProvided(grant_name, content)
Expand Down Expand Up @@ -1051,6 +1062,66 @@ def __init__(self, instance: str, client_id: str, **kwargs):
)


class WakaTimeAuthorizationCode(OAuth2AuthorizationCode):
"""
Describes a WakaTime (OAuth 2) "Access Token" authorization code flow requests authentication.
"""

def __init__(
self,
client_id: str,
client_secret: str,
scope: Union[str, Iterable[str]],
**kwargs,
):
"""
:param client_id: WakaTime Application Identifier (formatted as an Universal Unique Identifier)
:param client_secret: WakaTime Application Secret (formatted as waka_sec_ followed by an Universal Unique Identifier)
:param scope: Scope parameter sent in query. Can also be a list of scopes.
:param response_type: Value of the response_type query parameter.
token by default.
:param token_field_name: Name of the expected field containing the token.
access_token by default.
:param early_expiry: Number of seconds before actual token expiry where token will be considered as expired.
Default to 30 seconds 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.
:param nonce: Refer to http://openid.net/specs/openid-connect-core-1_0.html#IDToken for more details
(formatted as an Universal Unique Identifier - UUID). Use a newly generated UUID by default.
:param redirect_uri_endpoint: Custom endpoint that will be used as redirect_uri the following way:
http://localhost:<redirect_uri_port>/<redirect_uri_endpoint>. Default value is to redirect on / (root).
:param redirect_uri_port: The port on which the server listening for the OAuth 2 token will be started.
Listen on port 5000 by default.
:param timeout: Maximum amount of seconds to wait for a token to be received once requested.
Wait for 1 minute by default.
:param 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.
Display the page for 1 millisecond by default.
:param 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.
Display the page for 5 seconds by default.
:param header_name: Name of the header field used to send token.
Token will be sent in Authorization header field by default.
:param header_value: Format used to send the token value.
"{token}" must be present as it will be replaced by the actual token.
Token will be sent as "Bearer {token}" by default.
:param client: httpx.Client instance that will be used to request the token.
Use it to provide a custom proxying rule for instance.
:param kwargs: all additional authorization parameters that should be put as query parameter
in the authorization URL.
"""
if not scope:
raise Exception("Scope is mandatory.")
OAuth2AuthorizationCode.__init__(
self,
"https://wakatime.com/oauth/authorize",
"https://wakatime.com/oauth/token",
client_id=client_id,
client_secret=client_secret,
scope=",".join(scope) if isinstance(scope, list) else scope,
**kwargs,
)


class OktaAuthorizationCodePKCE(OAuth2AuthorizationCodePKCE):
"""
Describes an Okta (OAuth 2) "Access Token" Proof Key for Code Exchange (PKCE) flow requests authentication.
Expand Down
2 changes: 1 addition & 1 deletion httpx_auth/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0)
# Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0)
# Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
__version__ = "0.15.0"
__version__ = "0.16.0"
57 changes: 57 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[build-system]
requires = ["setuptools", "setuptools_scm"]
build-backend = "setuptools.build_meta"

[project]
name = "httpx_auth"
description = "Authentication for HTTPX"
readme = "README.md"
requires-python = ">=3.8"
license = {file = "LICENSE"}
authors = [
{name = "Colin Bounouar", email = "[email protected]" }
]
maintainers = [
{name = "Colin Bounouar", email = "[email protected]" }
]
keywords = ["authentication", "oauth2", "aws", "okta", "aad"]
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Typing :: Typed",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Build Tools",
]
dependencies = [
"httpx==0.24.*",
]
dynamic = ["version"]

[project.urls]
documentation = "https://colin-b.github.io/httpx_auth/"
repository = "https://github.com/Colin-b/httpx_auth"
changelog = "https://github.com/Colin-b/httpx_auth/blob/master/CHANGELOG.md"
issues = "https://github.com/Colin-b/httpx_auth/issues"

[project.optional-dependencies]
testing = [
# Used to generate test tokens
"pyjwt==2.*",
# Used to mock httpx
"pytest_httpx==0.22.*",
# Used to check coverage
"pytest-cov==4.*",
]

[tool.setuptools.packages.find]
exclude = ["tests*"]

[tool.setuptools.dynamic]
version = {attr = "httpx_auth.version.__version__"}
Loading

0 comments on commit e1fa635

Please sign in to comment.