From c12d0ebe5122aac2ea96c403fb3a084f09c1a9c3 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 11 Jun 2025 07:46:09 +0200 Subject: [PATCH 01/33] Update SECURITY.md --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index ddb8632d..e5b7db85 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,4 +11,4 @@ following versions are currently being supported with security updates. | < 3.2.0 | :x: | ## Reporting a Vulnerability -Contact auvipy@gmail.com for reporting any vulnerability. +Please raise a draft advisory to start discussing about the vulnerability in a private channel with OAuthlib Admin: https://github.com/oauthlib/oauthlib/security/advisories/new From 4412df731bf3a3b6be417e03b053029666d86cb3 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 18 Jun 2025 08:50:17 +0200 Subject: [PATCH 02/33] Add Incident Response Plan --- SECURITY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index e5b7db85..3ddddebc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,4 +11,16 @@ following versions are currently being supported with security updates. | < 3.2.0 | :x: | ## Reporting a Vulnerability + Please raise a draft advisory to start discussing about the vulnerability in a private channel with OAuthlib Admin: https://github.com/oauthlib/oauthlib/security/advisories/new + +## Incident Response Plan + +The Incident Response Plan for oauthlib is composed of four steps: + +- Triage: discussion about the validity of the vulnerability with the reporter + in the private channel. +- Mitigate: work on a fix and release a newer version. +- Disclose: let downstream applications some time to update to the latest + release, then make the CVE public. +- Learn: discuss about any potential actions that could have prevented the vulnerability. From 694134d439a071a3dafb131ee8873fb473393ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 18 Jun 2025 07:35:40 +0200 Subject: [PATCH 03/33] Stop installing `examples` into `site-packages` Fix `setup.py` not to install `examples` as a top-level package into `site-packages`. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 70162daa..a35de99b 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def fread(fn): url='https://github.com/oauthlib/oauthlib', platforms='any', license='BSD-3-Clause', - packages=find_packages(exclude=('docs', 'tests', 'tests.*')), + packages=find_packages(exclude=('docs', 'examples', 'tests', 'tests.*')), python_requires='>=3.8', extras_require={ 'rsa': rsa_require, From 28aab3a42ad830433821b30e155865f885301718 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 08:11:05 +0200 Subject: [PATCH 04/33] Fix latest ruff findings Replaced deprecated ruff config --- oauthlib/oauth2/rfc6749/endpoints/resource.py | 2 +- ruff.toml | 10 +++++----- tests/oauth2/rfc6749/grant_types/test_refresh_token.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/resource.py b/oauthlib/oauth2/rfc6749/endpoints/resource.py index f7562255..d1ff5049 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/resource.py +++ b/oauthlib/oauth2/rfc6749/endpoints/resource.py @@ -81,4 +81,4 @@ def find_token_type(self, request): """ estimates = sorted(((t.estimate_type(request), n) for n, t in self.tokens.items()), reverse=True) - return estimates[0][1] if len(estimates) else None + return estimates[0][1] if estimates else None diff --git a/ruff.toml b/ruff.toml index 0546d652..ec45dd4b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -7,7 +7,7 @@ # start with [tool.ruff # [tool.ruff] -select = [ +lint.select = [ "A", # flake8-builtins "AIR", # Airflow "ASYNC", # flake8-async @@ -65,7 +65,7 @@ select = [ # "TRY", # tryceratops # "UP", # pyupgrade ] -ignore = [ +lint.ignore = [ "F401", "F403", "F405", @@ -88,11 +88,11 @@ line-length = 255 target-version = "py37" # [tool.ruff.mccabe] -[mccabe] +[lint.mccabe] max-complexity = 24 # default is 10 # [tool.ruff.per-file-ignores] -[per-file-ignores] +[lint.per-file-ignores] "docs/conf.py" = ["A001", "INP001"] "oauthlib/oauth2/rfc6749/clients/base.py" = ["E722"] "oauthlib/oauth2/rfc6749/endpoints/base.py" = ["BLE001"] @@ -106,7 +106,7 @@ max-complexity = 24 # default is 10 "oauthlib/openid/connect/core/tokens.py" = ["RUF023"] # [tool.ruff.pylint] -[pylint] +[lint.pylint] allow-magic-value-types = ["int", "str"] max-args = 16 # default is 5 max-branches = 24 # default is 12 diff --git a/tests/oauth2/rfc6749/grant_types/test_refresh_token.py b/tests/oauth2/rfc6749/grant_types/test_refresh_token.py index 0a4ddd9a..f963444a 100644 --- a/tests/oauth2/rfc6749/grant_types/test_refresh_token.py +++ b/tests/oauth2/rfc6749/grant_types/test_refresh_token.py @@ -184,7 +184,7 @@ def test_valid_token_request(self): # all ok but without request.scope del self.request.scope self.auth.validate_token_request(self.request) - self.assertEqual(self.request.scopes, 'foo bar baz'.split()) + self.assertEqual(self.request.scopes, ['foo', 'bar', 'baz']) # CORS From 034c8eacc9971be5ce3d98b292cc34f858c8fb50 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 08:21:45 +0200 Subject: [PATCH 05/33] Removed year from license Removing date from copyrights to ease maintenance is the practice nowadays, see https://aboutcode.org/2023/update-copyright-each-new-year/ --- LICENSE | 2 +- docs/conf.py | 2 +- oauthlib/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index d5a9e9ac..e75118ad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 The OAuthlib Community +Copyright (c) The OAuthlib Community All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index 05e93ee0..e609c541 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,7 @@ # General information about the project. project = 'OAuthLib' -copyright = '2019, The OAuthlib Community' +copyright = 'The OAuthlib Community' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 4f56ef14..9a5e0786 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -5,7 +5,7 @@ A generic, spec-compliant, thorough implementation of the OAuth request-signing logic. - :copyright: (c) 2019 by The OAuthlib Community + :copyright: (c) The OAuthlib Community :license: BSD, see LICENSE for details. """ import logging From c3550a0072dca48304634fafd7affb1cf1a0157d Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 08:32:05 +0200 Subject: [PATCH 06/33] Removed unused bandit, since it has been replaced with ruff --- bandit.json | 1222 --------------------------------------------------- 1 file changed, 1222 deletions(-) delete mode 100644 bandit.json diff --git a/bandit.json b/bandit.json deleted file mode 100644 index 7161f005..00000000 --- a/bandit.json +++ /dev/null @@ -1,1222 +0,0 @@ -{ - "errors": [], - "generated_at": "2019-05-13T12:51:49Z", - "metrics": { - "_totals": { - "CONFIDENCE.HIGH": 3.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 10.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 12.0, - "SEVERITY.MEDIUM": 1.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 8338, - "nosec": 0 - }, - "oauthlib/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 25, - "nosec": 0 - }, - "oauthlib/common.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 337, - "nosec": 0 - }, - "oauthlib/oauth1/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 16, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/__init__.py": { - "CONFIDENCE.HIGH": 1.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 1.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 230, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 8, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/access_token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 152, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/authorization.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 135, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 142, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/pre_configured.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 10, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/request_token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 141, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/resource.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 97, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/endpoints/signature_only.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 53, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/errors.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 58, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/parameters.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 75, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/request_validator.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 630, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/signature.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 379, - "nosec": 0 - }, - "oauthlib/oauth1/rfc5849/utils.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 61, - "nosec": 0 - }, - "oauthlib/oauth2/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 33, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 14, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 13, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/backend_application.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 56, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 3.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 3.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 384, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/legacy_application.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 67, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/mobile_application.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 140, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/service_application.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 144, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/clients/web_application.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 165, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 18, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/authorization.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 85, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 71, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/introspect.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 98, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/metadata.py": { - "CONFIDENCE.HIGH": 2.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 2.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 182, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 5.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 5.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 189, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/resource.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 65, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/revocation.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 96, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/endpoints/token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 76, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/errors.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 311, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 10, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/authorization_code.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 389, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 199, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/client_credentials.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 96, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/implicit.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 259, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/refresh_token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 102, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 156, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/parameters.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 1.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 1.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 335, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/request_validator.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 504, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/tokens.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 277, - "nosec": 0 - }, - "oauthlib/oauth2/rfc6749/utils.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 70, - "nosec": 0 - }, - "oauthlib/openid/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 8, - "nosec": 0 - }, - "oauthlib/openid/connect/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 0, - "nosec": 0 - }, - "oauthlib/openid/connect/core/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 0, - "nosec": 0 - }, - "oauthlib/openid/connect/core/endpoints/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 9, - "nosec": 0 - }, - "oauthlib/openid/connect/core/endpoints/pre_configured.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 1.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 1.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 93, - "nosec": 0 - }, - "oauthlib/openid/connect/core/endpoints/userinfo.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 83, - "nosec": 0 - }, - "oauthlib/openid/connect/core/exceptions.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 117, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 15, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/authorization_code.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 32, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 234, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/dispatchers.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 66, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/exceptions.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 26, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/hybrid.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 38, - "nosec": 0 - }, - "oauthlib/openid/connect/core/grant_types/implicit.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 35, - "nosec": 0 - }, - "oauthlib/openid/connect/core/request_validator.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 235, - "nosec": 0 - }, - "oauthlib/openid/connect/core/tokens.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 42, - "nosec": 0 - }, - "oauthlib/signals.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 32, - "nosec": 0 - }, - "oauthlib/tokens/__init__.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 0, - "nosec": 0 - }, - "oauthlib/tokens/access_token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 13, - "nosec": 0 - }, - "oauthlib/tokens/base.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 8, - "nosec": 0 - }, - "oauthlib/tokens/id_token.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 6, - "nosec": 0 - }, - "oauthlib/uri_validate.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 93, - "nosec": 0 - } - }, - "results": [ - { - "code": "183 if request.body is not None and content_type_eligible:\n184 params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))\n185 \n", - "filename": "oauthlib/oauth1/rfc5849/__init__.py", - "issue_confidence": "HIGH", - "issue_severity": "MEDIUM", - "issue_text": "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", - "line_number": 184, - "line_range": [ - 184 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b303-md5", - "test_id": "B303", - "test_name": "blacklist" - }, - { - "code": "49 \"\"\"\n50 refresh_token_key = 'refresh_token'\n51 \n52 def __init__(self, client_id,\n", - "filename": "oauthlib/oauth2/rfc6749/clients/base.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'refresh_token'", - "line_number": 50, - "line_range": [ - 50, - 51 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html", - "test_id": "B105", - "test_name": "hardcoded_password_string" - }, - { - "code": "51 \n52 def __init__(self, client_id,\n53 default_token_placement=AUTH_HEADER,\n54 token_type='Bearer',\n55 access_token=None,\n56 refresh_token=None,\n57 mac_key=None,\n58 mac_algorithm=None,\n59 token=None,\n60 scope=None,\n61 state=None,\n62 redirect_url=None,\n63 state_generator=generate_token,\n64 **kwargs):\n65 \"\"\"Initialize a client with commonly used attributes.\n66 \n67 :param client_id: Client identifier given by the OAuth provider upon\n68 registration.\n69 \n70 :param default_token_placement: Tokens can be supplied in the Authorization\n71 header (default), the URL query component (``query``) or the request\n72 body (``body``).\n73 \n74 :param token_type: OAuth 2 token type. Defaults to Bearer. Change this\n75 if you specify the ``access_token`` parameter and know it is of a\n76 different token type, such as a MAC, JWT or SAML token. Can\n77 also be supplied as ``token_type`` inside the ``token`` dict parameter.\n78 \n79 :param access_token: An access token (string) used to authenticate\n80 requests to protected resources. Can also be supplied inside the\n81 ``token`` dict parameter.\n82 \n83 :param refresh_token: A refresh token (string) used to refresh expired\n84 tokens. Can also be supplied inside the ``token`` dict parameter.\n85 \n86 :param mac_key: Encryption key used with MAC tokens.\n87 \n88 :param mac_algorithm: Hashing algorithm for MAC tokens.\n89 \n90 :param token: A dict of token attributes such as ``access_token``,\n91 ``token_type`` and ``expires_at``.\n92 \n93 :param scope: A list of default scopes to request authorization for.\n94 \n95 :param state: A CSRF protection string used during authorization.\n96 \n97 :param redirect_url: The redirection endpoint on the client side to which\n98 the user returns after authorization.\n99 \n100 :param state_generator: A no argument state generation callable. Defaults\n101 to :py:meth:`oauthlib.common.generate_token`.\n102 \"\"\"\n103 \n104 self.client_id = client_id\n105 self.default_token_placement = default_token_placement\n106 self.token_type = token_type\n107 self.access_token = access_token\n108 self.refresh_token = refresh_token\n109 self.mac_key = mac_key\n110 self.mac_algorithm = mac_algorithm\n111 self.token = token or {}\n112 self.scope = scope\n113 self.state_generator = state_generator\n114 self.state = state\n115 self.redirect_url = redirect_url\n116 self.code = None\n117 self.expires_in = None\n118 self._expires_at = None\n119 self.populate_token_attributes(self.token)\n120 \n121 @property\n", - "filename": "oauthlib/oauth2/rfc6749/clients/base.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 52, - "line_range": [ - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", - "test_id": "B107", - "test_name": "hardcoded_password_default" - }, - { - "code": "313 \n314 def prepare_token_revocation_request(self, revocation_url, token,\n315 token_type_hint=\"access_token\", body='', callback=None, **kwargs):\n316 \"\"\"Prepare a token revocation request.\n317 \n318 :param revocation_url: Provider token revocation endpoint URL.\n319 \n320 :param token: The access or refresh token to be revoked (string).\n321 \n322 :param token_type_hint: ``\"access_token\"`` (default) or\n323 ``\"refresh_token\"``. This is optional and if you wish to not pass it you\n324 must provide ``token_type_hint=None``.\n325 \n326 :param body:\n327 \n328 :param callback: A jsonp callback such as ``package.callback`` to be invoked\n329 upon receiving the response. Not that it should not include a () suffix.\n330 \n331 :param kwargs: Additional parameters to included in the request.\n332 \n333 :returns: The prepared request tuple with (url, headers, body).\n334 \n335 Note that JSONP request may use GET requests as the parameters will\n336 be added to the request URL query as opposed to the request body.\n337 \n338 An example of a revocation request\n339 \n340 .. code-block: http\n341 \n342 POST /revoke HTTP/1.1\n343 Host: server.example.com\n344 Content-Type: application/x-www-form-urlencoded\n345 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n346 \n347 token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token\n348 \n349 An example of a jsonp revocation request\n350 \n351 .. code-block: http\n352 \n353 GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1\n354 Host: server.example.com\n355 Content-Type: application/x-www-form-urlencoded\n356 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n357 \n358 and an error response\n359 \n360 .. code-block: http\n361 \n362 package.myCallback({\"error\":\"unsupported_token_type\"});\n363 \n364 Note that these requests usually require client credentials, client_id in\n365 the case for public clients and provider specific authentication\n366 credentials for confidential clients.\n367 \"\"\"\n368 if not is_secure_transport(revocation_url):\n369 raise InsecureTransportError()\n370 \n371 return prepare_token_revocation_request(revocation_url, token,\n372 token_type_hint=token_type_hint, body=body, callback=callback,\n373 **kwargs)\n374 \n375 def parse_request_body_response(self, body, scope=None, **kwargs):\n", - "filename": "oauthlib/oauth2/rfc6749/clients/base.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'access_token'", - "line_number": 314, - "line_range": [ - 314, - 315, - 316, - 317, - 318, - 319, - 320, - 321, - 322, - 323, - 324, - 325, - 326, - 327, - 328, - 329, - 330, - 331, - 332, - 333, - 334, - 335, - 336, - 337, - 338, - 339, - 340, - 341, - 342, - 343, - 344, - 345, - 346, - 347, - 348, - 349, - 350, - 351, - 352, - 353, - 354, - 355, - 356, - 357, - 358, - 359, - 360, - 361, - 362, - 363, - 364, - 365, - 366, - 367, - 368, - 369, - 370, - 371, - 372, - 373, - 374 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", - "test_id": "B107", - "test_name": "hardcoded_password_default" - }, - { - "code": "45 def __init__(self, endpoints, claims={}, raise_errors=True):\n46 assert isinstance(claims, dict)\n47 for endpoint in endpoints:\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/metadata.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.", - "line_number": 46, - "line_range": [ - 46 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html", - "test_id": "B101", - "test_name": "assert_used" - }, - { - "code": "47 for endpoint in endpoints:\n48 assert isinstance(endpoint, BaseEndpoint)\n49 \n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/metadata.py", - "issue_confidence": "HIGH", - "issue_severity": "LOW", - "issue_text": "Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.", - "line_number": 48, - "line_range": [ - 48 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html", - "test_id": "B101", - "test_name": "assert_used" - }, - { - "code": "70 default_token_type=bearer)\n71 ResourceEndpoint.__init__(self, default_token='Bearer',\n72 token_types={'Bearer': bearer})\n73 RevocationEndpoint.__init__(self, request_validator)\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 71, - "line_range": [ - 71, - 72 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - }, - { - "code": "109 default_token_type=bearer)\n110 ResourceEndpoint.__init__(self, default_token='Bearer',\n111 token_types={'Bearer': bearer})\n112 RevocationEndpoint.__init__(self, request_validator)\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 110, - "line_range": [ - 110, - 111 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - }, - { - "code": "142 default_token_type=bearer)\n143 ResourceEndpoint.__init__(self, default_token='Bearer',\n144 token_types={'Bearer': bearer})\n145 RevocationEndpoint.__init__(self, request_validator,\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 143, - "line_range": [ - 143, - 144 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - }, - { - "code": "181 default_token_type=bearer)\n182 ResourceEndpoint.__init__(self, default_token='Bearer',\n183 token_types={'Bearer': bearer})\n184 RevocationEndpoint.__init__(self, request_validator)\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 182, - "line_range": [ - 182, - 183 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - }, - { - "code": "214 default_token_type=bearer)\n215 ResourceEndpoint.__init__(self, default_token='Bearer',\n216 token_types={'Bearer': bearer})\n217 RevocationEndpoint.__init__(self, request_validator,\n", - "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 215, - "line_range": [ - 215, - 216 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - }, - { - "code": "164 \n165 def prepare_token_revocation_request(url, token, token_type_hint=\"access_token\",\n166 callback=None, body='', **kwargs):\n167 \"\"\"Prepare a token revocation request.\n168 \n169 The client constructs the request by including the following parameters\n170 using the \"application/x-www-form-urlencoded\" format in the HTTP request\n171 entity-body:\n172 \n173 :param token: REQUIRED. The token that the client wants to get revoked.\n174 \n175 :param token_type_hint: OPTIONAL. A hint about the type of the token\n176 submitted for revocation. Clients MAY pass this\n177 parameter in order to help the authorization server\n178 to optimize the token lookup. If the server is\n179 unable to locate the token using the given hint, it\n180 MUST extend its search across all of its supported\n181 token types. An authorization server MAY ignore\n182 this parameter, particularly if it is able to detect\n183 the token type automatically.\n184 \n185 This specification defines two values for `token_type_hint`:\n186 \n187 * access_token: An access token as defined in [RFC6749],\n188 `Section 1.4`_\n189 \n190 * refresh_token: A refresh token as defined in [RFC6749],\n191 `Section 1.5`_\n192 \n193 Specific implementations, profiles, and extensions of this\n194 specification MAY define other values for this parameter using the\n195 registry defined in `Section 4.1.2`_.\n196 \n197 .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4\n198 .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5\n199 .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2\n200 \n201 \"\"\"\n202 if not is_secure_transport(url):\n203 raise InsecureTransportError()\n204 \n205 params = [('token', token)]\n206 \n207 if token_type_hint:\n208 params.append(('token_type_hint', token_type_hint))\n209 \n210 for k in kwargs:\n211 if kwargs[k]:\n212 params.append((str(k), kwargs[k]))\n213 \n214 headers = {'Content-Type': 'application/x-www-form-urlencoded'}\n215 \n216 if callback:\n217 params.append(('callback', callback))\n218 return add_params_to_uri(url, params), headers, body\n219 else:\n220 return url, headers, add_params_to_qs(body, params)\n221 \n222 \n223 def parse_authorization_code_response(uri, state=None):\n", - "filename": "oauthlib/oauth2/rfc6749/parameters.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'access_token'", - "line_number": 165, - "line_range": [ - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 215, - 216, - 217, - 218, - 219, - 220, - 221, - 222 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", - "test_id": "B107", - "test_name": "hardcoded_password_default" - }, - { - "code": "104 default_token_type=bearer)\n105 ResourceEndpoint.__init__(self, default_token='Bearer',\n106 token_types={'Bearer': bearer, 'JWT': jwt})\n107 RevocationEndpoint.__init__(self, request_validator)\n", - "filename": "oauthlib/openid/connect/core/endpoints/pre_configured.py", - "issue_confidence": "MEDIUM", - "issue_severity": "LOW", - "issue_text": "Possible hardcoded password: 'Bearer'", - "line_number": 105, - "line_range": [ - 105, - 106 - ], - "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", - "test_id": "B106", - "test_name": "hardcoded_password_funcarg" - } - ] -} \ No newline at end of file From 7760637843935b6e73d63f3c99b3d85ca5f4e031 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 09:22:11 +0200 Subject: [PATCH 07/33] Removed inactive users while keeping them in AUTHORS file. --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1a100bac..3b75880a 100755 --- a/setup.py +++ b/setup.py @@ -28,9 +28,8 @@ def fread(fn): long_description=fread('README.rst'), long_description_content_type='text/x-rst', author='The OAuthlib Community', - author_email='idan@gazit.me', - maintainer='Ib Lundgren', - maintainer_email='ib.lundgren@gmail.com', + maintainer='Jonathan Huot', + maintainer_email='jonathan.huot@gmail.com', url='https://github.com/oauthlib/oauthlib', platforms='any', license='BSD-3-Clause', From e9a575721046329e9dd54a3127f0e8d85d9837f5 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 09:29:10 +0200 Subject: [PATCH 08/33] Refresh wording of license to not confuse tools between BSD-3 & BSD Based on latest https://opensource.org/license/BSD-3-Clause, suggested removal of ambiguity from https://peps.python.org/pep-0639/appendix-mapping-classifiers/ and https://peps.python.org/pep-0639/#deprecate-license-classifiers Fixed #896 --- LICENSE | 8 ++++---- README.rst | 2 +- oauthlib/__init__.py | 2 +- setup.py | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index e75118ad..ffab1267 100644 --- a/LICENSE +++ b/LICENSE @@ -11,14 +11,14 @@ modification, are permitted provided that the following conditions are met: notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of this project nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER diff --git a/README.rst b/README.rst index ef0b5ad7..c4e38b66 100644 --- a/README.rst +++ b/README.rst @@ -114,7 +114,7 @@ have the pleasure to run into each other, please send a docs pull request =) License ------- -OAuthLib is yours to use and abuse according to the terms of the BSD license. +OAuthLib is yours to use and abuse according to the terms of the BSD-3-Clause license. Check the LICENSE file for full details. Credits diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 9a5e0786..98b123e8 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -6,7 +6,7 @@ request-signing logic. :copyright: (c) The OAuthlib Community - :license: BSD, see LICENSE for details. + :license: BSD-3-Clause, see LICENSE for details. """ import logging from logging import NullHandler diff --git a/setup.py b/setup.py index 3b75880a..70162daa 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,6 @@ def fread(fn): 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', 'Operating System :: MacOS', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', From e57283b4209a2a4ca86adbbe6898b59fded0426b Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sun, 11 May 2025 09:49:50 +0200 Subject: [PATCH 09/33] Add 3.3.0 changelog and bump version --- CHANGELOG.rst | 23 +++++++++++++++++++++++ oauthlib/__init__.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 82dbd75a..4ec62bdf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,29 @@ Changelog ========= +3.3.0 (2025-05-11): +------------------ +OAuth2.0 Provider: +* OIDC: #879 Changed in how ui_locales is parsed +* RFC8628: Added OAuth2.0 Device Authorization Grant support +* PKCE: #876, #893 Fixed `create_code_verifier` length +* OIDC: Pre-configured OIDC server to use Refresh Token by default + +OAuth2.0 Common: +* OAuth2Error: Allow 0 to be a valid state + +OAuth2.0 Client: +* #745: expires_at is forced to be an int + +General: +* Removed Python 3.5, 3.6, 3.7 support +* #859, #883: Added Python 3.12, 3.13 Support +* Added dependency-review GitHub Action +* Updated various references of license (SPDX identifier..) +* Added GitHub Action for lint, replaced bandy with ruff, removed isort... +* Migrated to GitHub Actions from Travis +* Added Security Policy + 3.2.2 (2022-10-17) ------------------ OAuth2.0 Provider: diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 98b123e8..2920cf44 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,7 +12,7 @@ from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.2.2' +__version__ = '3.3.0' logging.getLogger('oauthlib').addHandler(NullHandler()) From c7a3c8c577affe23b27a1b4a2d6300fa07ec8f4c Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 13 May 2025 10:45:32 +0200 Subject: [PATCH 10/33] Fix multiple if statements --- .github/workflows/python-publish.yml | 9 ++++----- Makefile | 15 ++++----------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 43417ee5..110e5f60 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,4 +1,4 @@ -name: Production deploy +name: Publish release on: workflow_run: workflows: ["Python Tests"] @@ -6,10 +6,9 @@ on: - completed jobs: pypi-publish: - if: | - github.repository_owner == 'oauthlib' && - ${{ github.event.workflow_run.conclusion == 'success' }} && - ${{ github.ref_type == 'tag' }} + if: ${{ github.repository_owner == 'oauthlib' && + github.event.workflow_run.conclusion == 'success' && + github.ref_type == 'tag' }} name: Upload release to PyPI runs-on: ubuntu-latest environment: diff --git a/Makefile b/Makefile index 550525c6..9622f70d 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ # # Please specify your library as well as primary contacts. # Since these contacts will be addressed with Github mentions they -# need to be Github users (for now)(sorry Bitbucket). +# need to be Github users. # clean: clean-eggs clean-build @find . -iname '*.pyc' -delete @@ -45,25 +45,18 @@ test: bottle: #--------------------------- - # Library thomsonreuters/bottle-oauthlib + # Library refinitiv/bottle-oauthlib # Contacts: Jonathan.Huot - cd bottle-oauthlib 2>/dev/null || git clone https://github.com/thomsonreuters/bottle-oauthlib.git + cd bottle-oauthlib 2>/dev/null || git clone https://github.com/refinitiv/bottle-oauthlib.git cd bottle-oauthlib && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && sed -i.old '/oauthlib/d' requirements.txt && tox -flask: - #--------------------------- - # Library: lepture/flask-oauthLib - # Contacts: lepture,widnyana - cd flask-oauthlib 2>/dev/null || git clone https://github.com/lepture/flask-oauthlib.git - cd flask-oauthlib && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && sed -i.old '/oauthlib/d' requirements.txt && tox - django: #--------------------------- # Library: evonove/django-oauth-toolkit # Contacts: evonove,masci # (note: has tox.ini already) cd django-oauth-toolkit 2>/dev/null || git clone https://github.com/evonove/django-oauth-toolkit.git - cd django-oauth-toolkit && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && tox -e py27,py35,py36 + cd django-oauth-toolkit && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && tox requests: #--------------------------- From aac6c64895bf2bf307d71a10eb997c08fa4d1126 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 14 May 2025 21:58:38 +0200 Subject: [PATCH 11/33] Handle expires_at with best effort basis --- oauthlib/oauth2/rfc6749/clients/base.py | 28 ++++++++-- oauthlib/oauth2/rfc6749/parameters.py | 4 +- requirements-test.txt | 1 + tests/oauth2/rfc6749/clients/test_base.py | 65 +++++++++-------------- 4 files changed, 52 insertions(+), 46 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index a94e73c4..66ebc3e8 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -586,10 +586,30 @@ def populate_token_attributes(self, response): self._expires_at = round(time.time()) + int(self.expires_in) if 'expires_at' in response: - try: - self._expires_at = round(float(response.get('expires_at'))) - except: - self._expires_at = None + # expires_at is not in specification + # so it does its best to : + # convert into a float, or + # convert into an integer, or + # reuse the type as-is + + if isinstance(response.get('expires_at'), str): + try: + self.expires_at = int(response.get('expires_at')) + except ValueError: + self.expires_at = response.get('expires_at') + try: + self.expires_at = float(response.get('expires_at')) + except ValueError: + self.expires_at = response.get('expires_at') + else: + self.expires_at = response.get('expires_at') + + # we preserve internal capability to raise TokenExpiredError + # for valid types only + if isinstance(self.expires_at, float): + self._expires_at = round(self.expires_at) + elif isinstance(self.expires_at, int): + self._expires_at = self.expires_at if 'mac_key' in response: self.mac_key = response.get('mac_key') diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 6c55000c..f6e55a16 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -434,10 +434,8 @@ def parse_token_response(body, scope=None): if params['expires_in'] is None: params.pop('expires_in') else: - params['expires_at'] = time.time() + int(params['expires_in']) + params['expires_at'] = round(time.time()) + int(params['expires_in']) - if isinstance(params.get('expires_at'), float): - params['expires_at'] = round(params['expires_at']) params = OAuth2Token(params, old_scope=scope) validate_token_parameters(params) diff --git a/requirements-test.txt b/requirements-test.txt index 6d8d6e9d..2761eca0 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,4 @@ -r requirements.txt pytest>=4.0 pytest-cov>=2.6 +pytest-subtests==0.14.1 diff --git a/tests/oauth2/rfc6749/clients/test_base.py b/tests/oauth2/rfc6749/clients/test_base.py index b0b6372b..cddd1a14 100644 --- a/tests/oauth2/rfc6749/clients/test_base.py +++ b/tests/oauth2/rfc6749/clients/test_base.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import datetime +import json from unittest.mock import patch from oauthlib import common @@ -303,31 +304,6 @@ def test_prepare_refresh_token_request(self): self.assertEqual(h, {'Content-Type': 'application/x-www-form-urlencoded'}) self.assertFormBodyEqual(b, 'grant_type=refresh_token&scope={}&refresh_token={}'.format(scope, token)) - def test_parse_token_response_invalid_expires_at(self): - token_json = ('{ "access_token":"2YotnFZFEjr1zCsicMWpAA",' - ' "token_type":"example",' - ' "expires_at":"2006-01-02T15:04:05Z",' - ' "scope":"/profile",' - ' "example_parameter":"example_value"}') - token = { - "access_token": "2YotnFZFEjr1zCsicMWpAA", - "token_type": "example", - "expires_at": "2006-01-02T15:04:05Z", - "scope": ["/profile"], - "example_parameter": "example_value" - } - - client = Client(self.client_id) - - # Parse code and state - response = client.parse_request_body_response(token_json, scope=["/profile"]) - self.assertEqual(response, token) - self.assertEqual(None, client._expires_at) - self.assertEqual(client.access_token, response.get("access_token")) - self.assertEqual(client.refresh_token, response.get("refresh_token")) - self.assertEqual(client.token_type, response.get("token_type")) - - def test_create_code_verifier_min_length(self): client = Client(self.client_id) length = 43 @@ -361,20 +337,31 @@ def test_create_code_challenge_s256(self): code_challenge_s256 = client.create_code_challenge(code_verifier=code_verifier, code_challenge_method='S256') self.assertEqual(code_challenge_s256, client.code_challenge) - def test_parse_token_response_expires_at_is_int(self): - expected_expires_at = 1661185149 - token_json = ('{ "access_token":"2YotnFZFEjr1zCsicMWpAA",' - ' "token_type":"example",' - ' "expires_at":1661185148.6437678,' - ' "scope":"/profile",' - ' "example_parameter":"example_value"}') - - client = Client(self.client_id) - - response = client.parse_request_body_response(token_json, scope=["/profile"]) - - self.assertEqual(response['expires_at'], expected_expires_at) - self.assertEqual(client._expires_at, expected_expires_at) + def test_parse_token_response_expires_at_types(self): + for data in [ # title, expected, expected_valid, fieldjson + ('int', 1661185148, 1661185148, 1661185148), + ('float', 1661185148.6437678, 1661185149, 1661185148.6437678), + ('str', "2006-01-02T15:04:05Z", None, "\"2006-01-02T15:04:05Z\""), + ('str-as-int', 1661185148, 1661185148, "\"1661185148\""), + ('str-as-float', 1661185148.42, 1661185148, "\"1661185148.42\""), + ]: + with self.subTest(msg=data[0]): + expected_expires_at = data[1] + expected_valid_expires_at = data[2] + token_json = ('{{ "access_token":"2YotnFZFEjr1zCsicMWpAA",' + ' "token_type":"example",' + ' "expires_at":{expires_at},' + ' "scope":"/profile",' + ' "example_parameter":"example_value"}}'.format(expires_at=data[3])) + + client = Client(self.client_id) + + response = client.parse_request_body_response(token_json, scope=["/profile"]) + + self.assertEqual(response['expires_at'], json.loads('{{"foo":{}}}'.format(data[3]))["foo"], "response attribute wrong") + self.assertEqual(client.expires_at, expected_expires_at, "client attribute wrong") + if expected_valid_expires_at: + self.assertEqual(client._expires_at, expected_valid_expires_at, "internal expiration wrong") @patch('time.time') def test_parse_token_response_generated_expires_at_is_int(self, t): From e1672e498905b505830ebb69155bec5e99afb847 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 14 May 2025 22:02:27 +0200 Subject: [PATCH 12/33] Removed version ping to work with all py versions --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 2761eca0..521036a4 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -r requirements.txt pytest>=4.0 pytest-cov>=2.6 -pytest-subtests==0.14.1 +pytest-subtests From a8566919393418926cc1aef00cf31575c5235285 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 15 May 2025 23:26:01 +0200 Subject: [PATCH 13/33] Simplified str convertion of expires_at Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- oauthlib/oauth2/rfc6749/clients/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index 66ebc3e8..ad273a84 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -594,13 +594,13 @@ def populate_token_attributes(self, response): if isinstance(response.get('expires_at'), str): try: + # Attempt to convert to int first, then float if int fails self.expires_at = int(response.get('expires_at')) except ValueError: - self.expires_at = response.get('expires_at') - try: - self.expires_at = float(response.get('expires_at')) - except ValueError: - self.expires_at = response.get('expires_at') + try: + self.expires_at = float(response.get('expires_at')) + except ValueError: + self.expires_at = response.get('expires_at') else: self.expires_at = response.get('expires_at') From 35480caefa84019d9b5ab504bda7d3ed987a7f81 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 28 May 2025 21:40:36 +0200 Subject: [PATCH 14/33] Does not round if float is provided --- oauthlib/oauth2/rfc6749/clients/base.py | 2 +- tests/oauth2/rfc6749/clients/test_base.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index ad273a84..e104cb3a 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -607,7 +607,7 @@ def populate_token_attributes(self, response): # we preserve internal capability to raise TokenExpiredError # for valid types only if isinstance(self.expires_at, float): - self._expires_at = round(self.expires_at) + self._expires_at = self.expires_at elif isinstance(self.expires_at, int): self._expires_at = self.expires_at diff --git a/tests/oauth2/rfc6749/clients/test_base.py b/tests/oauth2/rfc6749/clients/test_base.py index cddd1a14..f059db9c 100644 --- a/tests/oauth2/rfc6749/clients/test_base.py +++ b/tests/oauth2/rfc6749/clients/test_base.py @@ -340,10 +340,10 @@ def test_create_code_challenge_s256(self): def test_parse_token_response_expires_at_types(self): for data in [ # title, expected, expected_valid, fieldjson ('int', 1661185148, 1661185148, 1661185148), - ('float', 1661185148.6437678, 1661185149, 1661185148.6437678), + ('float', 1661185148.6437678, 1661185148.6437678, 1661185148.6437678), ('str', "2006-01-02T15:04:05Z", None, "\"2006-01-02T15:04:05Z\""), ('str-as-int', 1661185148, 1661185148, "\"1661185148\""), - ('str-as-float', 1661185148.42, 1661185148, "\"1661185148.42\""), + ('str-as-float', 1661185148.42, 1661185148.42, "\"1661185148.42\""), ]: with self.subTest(msg=data[0]): expected_expires_at = data[1] From 6a30bcf6386d79521eadb342768d936fe76b1e98 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 12 Jun 2025 20:04:24 +0200 Subject: [PATCH 15/33] Factorized parsing of expires_at Added similar behaviors to all interfaces where expires_at is parsed, this will facilitate the implementations. Note this is breaking change for those which are expecting the "default" `expires_at` (as in, not provided) to be a float. This will now default to a int. --- oauthlib/oauth2/rfc6749/clients/base.py | 37 ++------ .../rfc6749/clients/service_application.py | 2 +- oauthlib/oauth2/rfc6749/parameters.py | 87 +++++++++++++++---- tests/oauth2/rfc6749/clients/test_base.py | 23 +++-- tests/oauth2/rfc6749/test_parameters.py | 22 +++++ 5 files changed, 113 insertions(+), 58 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index e104cb3a..17f833d2 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -17,6 +17,7 @@ InsecureTransportError, TokenExpiredError, ) from oauthlib.oauth2.rfc6749.parameters import ( + parse_expires, parse_token_response, prepare_token_request, prepare_token_revocation_request, ) @@ -581,35 +582,13 @@ def populate_token_attributes(self, response): if 'token_type' in response: self.token_type = response.get('token_type') - if 'expires_in' in response: - self.expires_in = response.get('expires_in') - self._expires_at = round(time.time()) + int(self.expires_in) - - if 'expires_at' in response: - # expires_at is not in specification - # so it does its best to : - # convert into a float, or - # convert into an integer, or - # reuse the type as-is - - if isinstance(response.get('expires_at'), str): - try: - # Attempt to convert to int first, then float if int fails - self.expires_at = int(response.get('expires_at')) - except ValueError: - try: - self.expires_at = float(response.get('expires_at')) - except ValueError: - self.expires_at = response.get('expires_at') - else: - self.expires_at = response.get('expires_at') - - # we preserve internal capability to raise TokenExpiredError - # for valid types only - if isinstance(self.expires_at, float): - self._expires_at = self.expires_at - elif isinstance(self.expires_at, int): - self._expires_at = self.expires_at + vin, vat, v_at = parse_expires(response) + if vin: + self.expires_in = vin + if vat: + self.expires_at = vat + if v_at: + self._expires_at = v_at if 'mac_key' in response: self.mac_key = response.get('mac_key') diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 8fb17377..9c223840 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -91,7 +91,7 @@ def prepare_request_body(self, ``https://provider.com/oauth2/token``. :param expires_at: A unix expiration timestamp for the JWT. Defaults - to an hour from now, i.e. ``time.time() + 3600``. + to an hour from now, i.e. ``round(time.time()) + 3600``. :param issued_at: A unix timestamp of when the JWT was created. Defaults to now, i.e. ``time.time()``. diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index f6e55a16..7a0b2d3e 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -336,15 +336,18 @@ def parse_implicit_response(uri, state=None, scope=None): fragment = urlparse.urlparse(uri).fragment params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True)) - for key in ('expires_in',): - if key in params: # cast things to int - params[key] = int(params[key]) - if 'scope' in params: params['scope'] = scope_to_list(params['scope']) - if 'expires_in' in params: - params['expires_at'] = round(time.time()) + int(params['expires_in']) + vin, vat, v_at = parse_expires(params) + if vin: + params['expires_in'] = vin + elif 'expires_in' in params: + params.pop('expires_in') + if vat: + params['expires_at'] = vat + elif 'expires_at' in params: + params.pop('expires_at') if state and params.get('state') != state: raise ValueError("Mismatching or missing state in params.") @@ -423,19 +426,19 @@ def parse_token_response(body, scope=None): # https://github.com/oauthlib/oauthlib/issues/267 params = dict(urlparse.parse_qsl(body)) - for key in ('expires_in',): - if key in params: # cast things to int - params[key] = int(params[key]) if 'scope' in params: params['scope'] = scope_to_list(params['scope']) - if 'expires_in' in params: - if params['expires_in'] is None: - params.pop('expires_in') - else: - params['expires_at'] = round(time.time()) + int(params['expires_in']) - + vin, vat, v_at = parse_expires(params) + if vin: + params['expires_in'] = vin + elif 'expires_in' in params: + params.pop('expires_in') + if vat: + params['expires_at'] = vat + elif 'expires_at' in params: + params.pop('expires_at') params = OAuth2Token(params, old_scope=scope) validate_token_parameters(params) @@ -468,3 +471,57 @@ def validate_token_parameters(params): w.old_scope = params.old_scopes w.new_scope = params.scopes raise w + +def parse_expires(params): + """Parse `expires_in`, `expires_at` fields from params + + Parse following these rules: + - `expires_in` must be either blank or a valid integer. + - `expires_at` is not in specification so it does its best to: + - convert into a int, else + - convert into a float, else + - reuse the same type as-is (usually string) + - `_expires_at` is a special internal value returned to be always an `int`, based + either on the presence of `expires_at`, or reuse the current time plus + `expires_in`. This is typically used to validate token expiry. + + :param params: Dict with expires_in and expires_at optionally set + :return: Tuple of `expires_in`, `expires_at`, and `_expires_at`. None if not set. + """ + expires_in = None + expires_at = None + _expires_at = None + + if 'expires_in' in params: + if isinstance(params.get('expires_in'), int): + expires_in = params.get('expires_in') + elif isinstance(params.get('expires_in'), str): + try: + # Attempt to convert to int + expires_in = int(params.get('expires_in')) + except ValueError: + raise ValueError("expires_int must be an int") + elif params.get('expires_in') is not None: + raise ValueError("expires_int must be an int") + + if 'expires_at' in params: + if isinstance(params.get('expires_at'), float) or \ + isinstance(params.get('expires_at'), int): + expires_at = params.get('expires_at') + _expires_at = expires_at + elif isinstance(params.get('expires_at'), str): + try: + # Attempt to convert to int first, then float if int fails + expires_at = int(params.get('expires_at')) + _expires_at = expires_at + except ValueError: + try: + expires_at = float(params.get('expires_at')) + _expires_at = expires_at + except ValueError: + # no change from str + expires_at = params.get('expires_at') + if _expires_at is None and expires_in: + expires_at = round(time.time()) + expires_in + _expires_at = expires_at + return expires_in, expires_at, _expires_at diff --git a/tests/oauth2/rfc6749/clients/test_base.py b/tests/oauth2/rfc6749/clients/test_base.py index f059db9c..0e68d3bb 100644 --- a/tests/oauth2/rfc6749/clients/test_base.py +++ b/tests/oauth2/rfc6749/clients/test_base.py @@ -338,30 +338,27 @@ def test_create_code_challenge_s256(self): self.assertEqual(code_challenge_s256, client.code_challenge) def test_parse_token_response_expires_at_types(self): - for data in [ # title, expected, expected_valid, fieldjson + for title, fieldjson, expected, generated in [ # title, fieldjson, expected, expected_valid ('int', 1661185148, 1661185148, 1661185148), ('float', 1661185148.6437678, 1661185148.6437678, 1661185148.6437678), - ('str', "2006-01-02T15:04:05Z", None, "\"2006-01-02T15:04:05Z\""), - ('str-as-int', 1661185148, 1661185148, "\"1661185148\""), - ('str-as-float', 1661185148.42, 1661185148.42, "\"1661185148.42\""), + ('str', "\"2006-01-02T15:04:05Z\"", "2006-01-02T15:04:05Z", None), + ('str-as-int', "\"1661185148\"", 1661185148, 1661185148), + ('str-as-float', "\"1661185148.42\"", 1661185148.42, 1661185148.42), ]: - with self.subTest(msg=data[0]): - expected_expires_at = data[1] - expected_valid_expires_at = data[2] + with self.subTest(msg=title): token_json = ('{{ "access_token":"2YotnFZFEjr1zCsicMWpAA",' ' "token_type":"example",' ' "expires_at":{expires_at},' ' "scope":"/profile",' - ' "example_parameter":"example_value"}}'.format(expires_at=data[3])) + ' "example_parameter":"example_value"}}'.format(expires_at=fieldjson)) client = Client(self.client_id) - response = client.parse_request_body_response(token_json, scope=["/profile"]) - self.assertEqual(response['expires_at'], json.loads('{{"foo":{}}}'.format(data[3]))["foo"], "response attribute wrong") - self.assertEqual(client.expires_at, expected_expires_at, "client attribute wrong") - if expected_valid_expires_at: - self.assertEqual(client._expires_at, expected_valid_expires_at, "internal expiration wrong") + self.assertEqual(response['expires_at'], expected, "response attribute wrong") + self.assertEqual(client.expires_at, expected, "client attribute wrong") + if generated: + self.assertEqual(client._expires_at, generated, "internal expiration wrong") @patch('time.time') def test_parse_token_response_generated_expires_at_is_int(self, t): diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index cd8c9e95..cdc7164b 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -302,3 +302,25 @@ def record_scope_change(sender, message, old, new): finally: signals.scope_changed.disconnect(record_scope_change) del os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] + + + def test_parse_expires(self): + for title, arg, expected in [ # title, arg_tuple, returned_expected_tuple, + ('none', (None, None), (None, None, None)), + ('expires_in only', (3600, None), (3600, 4600, 4600)), + ('expires_in and expires_at', (3600, 200), (3600, 200, 200)), + ('expires_in and expires_at float', (3600, 200.42), (3600, 200.42, 200.42)), + ('expires_in and expires_at str-int', (3600, "200"), (3600, 200, 200)), + ('expires_in and expires_at str-float', (3600, "200.42"), (3600, 200.42, 200.42)), + ('expires_in str only', ("3600", None), (3600, 4600, 4600)), + ('expires_in str and expires_at', ("3600", 200), (3600, 200, 200)), + ('expires_in str and expires_at float', ("3600", 200.42), (3600, 200.42, 200.42)), + ('expires_in str and expires_at str-int', ("3600", "200"), (3600, 200, 200)), + ('expires_in str and expires_at str-float', ("3600", "200.42"), (3600, 200.42, 200.42)), + ]: + with self.subTest(msg=title): + params = { + "expires_in": arg[0], + "expires_at": arg[1] + } + self.assertEqual(expected, parse_expires(params)) From 51259c22d856d6c8c8612cd6d1af03632db6bd13 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 12 Jun 2025 20:15:35 +0200 Subject: [PATCH 16/33] Added expires_at changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4ec62bdf..c0f02515 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ OAuth2.0 Common: OAuth2.0 Client: * #745: expires_at is forced to be an int +* #899: expires_at clarification General: * Removed Python 3.5, 3.6, 3.7 support From ebbe68297d7115b80fa5bbd4688a406222a06fe4 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 12 Jun 2025 20:15:52 +0200 Subject: [PATCH 17/33] Removed unnecessary comments --- tests/oauth2/rfc6749/clients/test_base.py | 2 +- tests/oauth2/rfc6749/test_parameters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/oauth2/rfc6749/clients/test_base.py b/tests/oauth2/rfc6749/clients/test_base.py index 0e68d3bb..b0970f2d 100644 --- a/tests/oauth2/rfc6749/clients/test_base.py +++ b/tests/oauth2/rfc6749/clients/test_base.py @@ -338,7 +338,7 @@ def test_create_code_challenge_s256(self): self.assertEqual(code_challenge_s256, client.code_challenge) def test_parse_token_response_expires_at_types(self): - for title, fieldjson, expected, generated in [ # title, fieldjson, expected, expected_valid + for title, fieldjson, expected, generated in [ ('int', 1661185148, 1661185148, 1661185148), ('float', 1661185148.6437678, 1661185148.6437678, 1661185148.6437678), ('str', "\"2006-01-02T15:04:05Z\"", "2006-01-02T15:04:05Z", None), diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index cdc7164b..63b74c37 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -305,7 +305,7 @@ def record_scope_change(sender, message, old, new): def test_parse_expires(self): - for title, arg, expected in [ # title, arg_tuple, returned_expected_tuple, + for title, arg, expected in [ ('none', (None, None), (None, None, None)), ('expires_in only', (3600, None), (3600, 4600, 4600)), ('expires_in and expires_at', (3600, 200), (3600, 200, 200)), From 7293d0cbae2179a06d338d7d717fa18df3d76293 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 12 Jun 2025 20:16:23 +0200 Subject: [PATCH 18/33] Simplify isinstance calls to one/Ruff/SIM101 --- oauthlib/oauth2/rfc6749/parameters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 7a0b2d3e..4675a31f 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -505,8 +505,7 @@ def parse_expires(params): raise ValueError("expires_int must be an int") if 'expires_at' in params: - if isinstance(params.get('expires_at'), float) or \ - isinstance(params.get('expires_at'), int): + if isinstance(params.get('expires_at'), (float, int)): expires_at = params.get('expires_at') _expires_at = expires_at elif isinstance(params.get('expires_at'), str): From 78e2c43aae8f655deb15dd1357ae5cd93130937a Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 19:22:20 +0200 Subject: [PATCH 19/33] Updated release date --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c0f02515..8064cd0d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -3.3.0 (2025-05-11): +3.3.0 (2025-06-17): ------------------ OAuth2.0 Provider: * OIDC: #879 Changed in how ui_locales is parsed From e4d4a0d0eab5c305712ff08244ab81d533fc118f Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 19:22:26 +0200 Subject: [PATCH 20/33] Bumped release --- SECURITY.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 3ddddebc..f1a3fb80 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,13 +2,14 @@ ## Supported Versions -following versions are currently being supported with security updates. +The following versions are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | +| 3.3.x | :white_check_mark: | | 3.2.x | :white_check_mark: | | 3.1.x | :x: | -| < 3.2.0 | :x: | +| < 3.1 | :x: | ## Reporting a Vulnerability From be152364ed71c7b9fc228e328f7ff503fda4ea26 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 23:09:57 +0200 Subject: [PATCH 21/33] Updated GH actions --- .github/workflows/python-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 110e5f60..dd4630bd 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -18,8 +18,8 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - name: Check out repository code - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install prereq From e5b3617504f5c6d4f492ce4251a2376ec969e904 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 23:10:14 +0200 Subject: [PATCH 22/33] Added maintainer instructions --- docs/release_process.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/release_process.rst b/docs/release_process.rst index 2796f29c..e915f938 100644 --- a/docs/release_process.rst +++ b/docs/release_process.rst @@ -51,3 +51,26 @@ Minor point (1.1.0) releases will introduce non API breaking new features and changes. Bug releases (1.0.1) will include minor fixes that needs to be released quickly (e.g. after a bigger release unintentionally introduced a bug). + +For maintainer - Publishing a newer version +-------------------------------------------- + +List of tasks to do a release from a maintainer point of view: + + - Create a Branch ``xyz-release`` + - Update ``oauthlib/__init__.py`` version + - Update ``CHANGELOG.rst`` accordingly + - Review Github Issues and PR, and associate the milestone of the version + - Run ``make`` to cover the release readiness + - Create a PR to let downstreams developers test their apps and comments + - Create a tag and push tag, it will automatically publish the release to pypi + - Create a release with GitHub Releases + - Merge PR, close Github milestone + + +Initial setup: + - Because we currently use "trusted publisher", it does not require to setup + token. However, OIDC Authorization flow has to be configured in `pypi publishing`. + - During setup, refer to the environment and name of the workflow directly in the code. + +.. _`pypi publishing`: https://pypi.org/manage/project/oauthlib/settings/publishing/ From 76585febf8d029e7f30d0571737f164691a6d483 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 23:10:33 +0200 Subject: [PATCH 23/33] Fixed ruff findings about function import --- oauthlib/common.py | 4 ++-- oauthlib/oauth1/rfc5849/signature.py | 2 +- oauthlib/oauth2/rfc6749/clients/service_application.py | 2 +- oauthlib/oauth2/rfc6749/errors.py | 4 ++-- oauthlib/openid/connect/core/exceptions.py | 5 +++-- tests/oauth2/rfc6749/endpoints/test_metadata.py | 4 ++-- tests/test_uri_validate.py | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/oauthlib/common.py b/oauthlib/common.py index fd9cad09..dfa85179 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -198,7 +198,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET): def generate_signed_token(private_pem, request): - import jwt + import jwt # noqa: PLC0415 now = datetime.datetime.utcnow() @@ -216,7 +216,7 @@ def generate_signed_token(private_pem, request): def verify_signed_token(public_pem, token): - import jwt + import jwt # noqa: PLC0415 return jwt.decode(token, public_pem, algorithms=['RS256']) diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 8916782b..a27cb2e7 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -568,7 +568,7 @@ def _get_jwt_rsa_algorithm(hash_algorithm_name: str): # Not in cache: instantiate a new RSAAlgorithm # PyJWT has some nice pycrypto/cryptography abstractions - import jwt.algorithms as jwt_algorithms + import jwt.algorithms as jwt_algorithms # noqa: PLC0415 m = { 'SHA-1': jwt_algorithms.hashes.SHA1, 'SHA-256': jwt_algorithms.hashes.SHA256, diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 9c223840..abf22d2d 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -149,7 +149,7 @@ def prepare_request_body(self, .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ - import jwt + import jwt # noqa: PLC0415 key = private_key or self.private_key if not key: diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 3b415748..be8e7a1e 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -6,6 +6,8 @@ defined error responses for all four core grant types. """ import json +import inspect +import sys from oauthlib.common import add_params_to_uri, urlencode @@ -386,8 +388,6 @@ def __init__(self, error, *args, **kwargs): def raise_from_error(error, params=None): - import inspect - import sys kwargs = { 'description': params.get('error_description'), 'uri': params.get('error_uri'), diff --git a/oauthlib/openid/connect/core/exceptions.py b/oauthlib/openid/connect/core/exceptions.py index 8a3e79f4..291cf137 100644 --- a/oauthlib/openid/connect/core/exceptions.py +++ b/oauthlib/openid/connect/core/exceptions.py @@ -5,6 +5,9 @@ Error used both by OAuth 2 clients and providers to represent the spec defined error responses for all four core grant types. """ +import inspect +import sys + from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error @@ -137,8 +140,6 @@ class InsufficientScopeError(OAuth2Error): def raise_from_error(error, params=None): - import inspect - import sys kwargs = { 'description': params.get('error_description'), 'uri': params.get('error_uri'), diff --git a/tests/oauth2/rfc6749/endpoints/test_metadata.py b/tests/oauth2/rfc6749/endpoints/test_metadata.py index facf69d0..c36d94bf 100644 --- a/tests/oauth2/rfc6749/endpoints/test_metadata.py +++ b/tests/oauth2/rfc6749/endpoints/test_metadata.py @@ -20,8 +20,8 @@ def test_openid_oauth2_preconfigured(self): "introspection_endpoint": "https://foo.bar/introspect", "token_endpoint": "https://foo.bar/token" } - from oauthlib.oauth2 import Server as OAuth2Server - from oauthlib.openid import Server as OpenIDServer + from oauthlib.oauth2 import Server as OAuth2Server # noqa: PLC0415 + from oauthlib.openid import Server as OpenIDServer # noqa: PLC0415 endpoint = OAuth2Server(None) metadata = MetadataEndpoint([endpoint], default_claims) diff --git a/tests/test_uri_validate.py b/tests/test_uri_validate.py index 04138d60..f1ac404e 100644 --- a/tests/test_uri_validate.py +++ b/tests/test_uri_validate.py @@ -1,3 +1,4 @@ +from datetime import datetime import unittest from oauthlib.uri_validate import is_absolute_uri @@ -77,7 +78,6 @@ def test_failures(self): self.assertIsNone(is_absolute_uri('http://[abcd:efgh::1]/')) def test_recursive_regex(self): - from datetime import datetime t0 = datetime.now() is_absolute_uri('http://[::::::::::::::::::::::::::]/path') t1 = datetime.now() From 854670b3fc14e612ef6ea746483a90aa80167c32 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 23:24:22 +0200 Subject: [PATCH 24/33] Fix linter after rebase --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index c4e38b66..840c33d5 100644 --- a/README.rst +++ b/README.rst @@ -76,8 +76,8 @@ Which web frameworks are supported? The following packages provide OAuth support using OAuthLib. - For Django there is: - - `django-oauth-toolkit`_, which includes `Django REST framework`_ support. - - `django-allauth`_, which includes `Django REST framework`_ as well as `Django Ninja`_ support. + - `django-oauth-toolkit`_, which includes `Django REST framework`_ support. + - `django-allauth`_, which includes `Django REST framework`_ as well as `Django Ninja`_ support. - For Flask there is `flask-oauthlib`_ and `Flask-Dance`_. - For Pyramid there is `pyramid-oauthlib`_. - For Bottle there is `bottle-oauthlib`_. From 68b2dfc52ffe0697b677d5856dbbb1d9eaa6520b Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 17 Jun 2025 23:34:34 +0200 Subject: [PATCH 25/33] Updated recommended filter for publish --- .github/workflows/python-publish.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index dd4630bd..fc9181d4 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -6,9 +6,7 @@ on: - completed jobs: pypi-publish: - if: ${{ github.repository_owner == 'oauthlib' && - github.event.workflow_run.conclusion == 'success' && - github.ref_type == 'tag' }} + if: github.repository_owner == 'oauthlib' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') name: Upload release to PyPI runs-on: ubuntu-latest environment: From 967bc5024680a587db909cbc1143a94552c76c90 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 18 Jun 2025 00:12:41 +0200 Subject: [PATCH 26/33] Execute publish workflow of the tagged source. Not of master branch. --- .github/workflows/python-publish.yml | 2 ++ docs/release_process.rst | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index fc9181d4..b36e018a 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -17,6 +17,8 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v4 + with: # by default, this event will trigger a workflow on the default branch. + ref: ${{ github.event.workflow_run.head_ref }} # set source branch - uses: actions/setup-python@v5 with: python-version: '3.10' diff --git a/docs/release_process.rst b/docs/release_process.rst index e915f938..70cc1a5f 100644 --- a/docs/release_process.rst +++ b/docs/release_process.rst @@ -71,6 +71,9 @@ List of tasks to do a release from a maintainer point of view: Initial setup: - Because we currently use "trusted publisher", it does not require to setup token. However, OIDC Authorization flow has to be configured in `pypi publishing`. - - During setup, refer to the environment and name of the workflow directly in the code. + + - During setup, refer to the environment and name of the workflow directly in the code. + - GitHub Restrictions: tag protection must be enabled + .. _`pypi publishing`: https://pypi.org/manage/project/oauthlib/settings/publishing/ From 892953211edcaa2c106144364c926fbaaa436918 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 18 Jun 2025 22:20:56 +0200 Subject: [PATCH 27/33] Add unit test sample based on 3.3.0 regression of expires_in --- oauthlib/oauth2/rfc6749/parameters.py | 4 ++-- .../rfc6749/clients/test_web_application.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 4675a31f..ea5821fd 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -500,9 +500,9 @@ def parse_expires(params): # Attempt to convert to int expires_in = int(params.get('expires_in')) except ValueError: - raise ValueError("expires_int must be an int") + raise ValueError("expires_in must be an int") elif params.get('expires_in') is not None: - raise ValueError("expires_int must be an int") + raise ValueError("expires_in must be an int") if 'expires_at' in params: if isinstance(params.get('expires_at'), (float, int)): diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 2a7a8ff3..1f7c4138 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -262,3 +262,19 @@ def test_prepare_request_body(self): with self.assertWarns(DeprecationWarning), self.assertRaises(ValueError): client.prepare_request_body(client_id='different_client_id') # testing the exact exception message in Python2&Python3 is a pain + + def test_expires_in_as_str(self): + """ + see regression issue #906 + """ + + client = WebApplicationClient( + client_id="dummy", + token={"access_token": "xyz", "expires_in": "3600"} # ← str on purpose + ) + self.assertIsNotNone(client) + client = WebApplicationClient( + client_id="dummy", + token={"access_token": "xyz", "expires_in": 3600} # ← str on purpose + ) + self.assertIsNotNone(client) From 136b3e72586286d04f8a53ae230539af4d99b69e Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 18 Jun 2025 22:44:22 +0200 Subject: [PATCH 28/33] Add mandatory RTD configuration --- .readthedocs.yaml | 16 ++++++++++++++++ docs/requirements.txt | 3 +++ 2 files changed, 19 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..d76e42c7 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.12" +sphinx: + builder: html + configuration: docs/conf.py + fail_on_warning: true +# the requirements.txt override some RTD defaults. +# ideally it has to be updated from time to time +# with latest libraries versions. +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..812eac27 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx +sphinx_rtd_theme +readthedocs-sphinx-ext From 34c90128402e24460ec4bce54ef4d9de8a0cd24c Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 19 Jun 2025 11:47:10 +0200 Subject: [PATCH 29/33] Handle expires_in as float to be backward compatible with 3.2.* --- oauthlib/oauth2/rfc6749/parameters.py | 4 +++- tests/oauth2/rfc6749/clients/test_web_application.py | 9 +++++++-- tests/oauth2/rfc6749/test_parameters.py | 5 +++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index ea5821fd..8268ef92 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -476,7 +476,7 @@ def parse_expires(params): """Parse `expires_in`, `expires_at` fields from params Parse following these rules: - - `expires_in` must be either blank or a valid integer. + - `expires_in` must be either integer, float or None. If a float, it is converted into an integer. - `expires_at` is not in specification so it does its best to: - convert into a int, else - convert into a float, else @@ -495,6 +495,8 @@ def parse_expires(params): if 'expires_in' in params: if isinstance(params.get('expires_in'), int): expires_in = params.get('expires_in') + elif isinstance(params.get('expires_in'), float): + expires_in = int(params.get('expires_in')) elif isinstance(params.get('expires_in'), str): try: # Attempt to convert to int diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 1f7c4138..f9a4c9d1 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -270,11 +270,16 @@ def test_expires_in_as_str(self): client = WebApplicationClient( client_id="dummy", - token={"access_token": "xyz", "expires_in": "3600"} # ← str on purpose + token={"access_token": "xyz", "expires_in": "3600"} ) self.assertIsNotNone(client) client = WebApplicationClient( client_id="dummy", - token={"access_token": "xyz", "expires_in": 3600} # ← str on purpose + token={"access_token": "xyz", "expires_in": 3600} + ) + self.assertIsNotNone(client) + client = WebApplicationClient( + client_id="dummy", + token={"access_token": "xyz", "expires_in": 3600.12} ) self.assertIsNotNone(client) diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index 63b74c37..bd8a8b61 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -312,6 +312,11 @@ def test_parse_expires(self): ('expires_in and expires_at float', (3600, 200.42), (3600, 200.42, 200.42)), ('expires_in and expires_at str-int', (3600, "200"), (3600, 200, 200)), ('expires_in and expires_at str-float', (3600, "200.42"), (3600, 200.42, 200.42)), + ('expires_in float only', (3600.12, None), (3600, 4600, 4600)), + ('expires_in float and expires_at', (3600.12, 200), (3600, 200, 200)), + ('expires_in float and expires_at float', (3600.12, 200.42), (3600, 200.42, 200.42)), + ('expires_in float and expires_at str-int', (3600.12, "200"), (3600, 200, 200)), + ('expires_in float and expires_at str-float', (3600.12, "200.42"), (3600, 200.42, 200.42)), ('expires_in str only', ("3600", None), (3600, 4600, 4600)), ('expires_in str and expires_at', ("3600", 200), (3600, 200, 200)), ('expires_in str and expires_at float', ("3600", 200.42), (3600, 200.42, 200.42)), From 93fdf9144060751d555915960054431cadb6679a Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 19 Jun 2025 17:39:49 +0200 Subject: [PATCH 30/33] Add twine manual instructions --- docs/release_process.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release_process.rst b/docs/release_process.rst index 70cc1a5f..8588ce28 100644 --- a/docs/release_process.rst +++ b/docs/release_process.rst @@ -67,6 +67,12 @@ List of tasks to do a release from a maintainer point of view: - Create a release with GitHub Releases - Merge PR, close Github milestone +In case of issues with CICD and a manual publish is required, follow these steps: + + - Install dependencies `pip install build twine` + - Run `python -m build` + - Run `twine check dist/*` + - Run `twine upload dist/*` Initial setup: - Because we currently use "trusted publisher", it does not require to setup From 9b65baf2432e33f51c4e9a834dd6973277385ad1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 19 Jun 2025 17:39:55 +0200 Subject: [PATCH 31/33] Bump version --- CHANGELOG.rst | 6 ++++++ oauthlib/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8064cd0d..2b2b6d56 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +3.3.1 (2025-06-19): +------------------ +OAuth2.0 Client: +* #906: fix regression of expires_in parsing when float in string. + + 3.3.0 (2025-06-17): ------------------ OAuth2.0 Provider: diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 2920cf44..462612f8 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,7 +12,7 @@ from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.3.0' +__version__ = '3.3.1' logging.getLogger('oauthlib').addHandler(NullHandler()) From 38c2a8e859a0d0890b76fbb1a42e4d72ce84ae04 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 20 Jun 2025 00:46:38 +0200 Subject: [PATCH 32/33] Merge publish into build workflow Having two workflows are usually required when different events are used. Calling from one workflow another workflow of the same repository is not efficient and clear. --- .github/workflows/lint_python.yml | 5 +++-- .github/workflows/python-build.yml | 33 ++++++++++++++++++++++++++++ .github/workflows/python-publish.yml | 32 --------------------------- 3 files changed, 36 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 683a3283..25e7f88b 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -3,6 +3,8 @@ on: [pull_request, push] jobs: lint_python: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -10,9 +12,8 @@ jobs: python-version: 3.x check-latest: true - run: pip install --upgrade pip setuptools wheel - - run: pip install black codespell mypy pytest ruff safety + - run: pip install codespell mypy pytest ruff safety - run: ruff check --output-format=github . - - run: black --check . || true - run: codespell --ignore-words-list="implementor,mimiced,provicers,re-use,THIRDPARTY,assertIn" # --skip="*.css,*.js,*.lock" - run: pip install -r requirements-test.txt - run: pip install --editable . diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 8108b6dc..51623fad 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -9,6 +9,8 @@ jobs: matrix: python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 @@ -32,6 +34,8 @@ jobs: needs: tests runs-on: ubuntu-latest container: python:3-slim + permissions: + contents: read steps: - name: Finished run: | @@ -44,6 +48,8 @@ jobs: matrix: toxenv: ["docs", "readme"] runs-on: ubuntu-latest + permissions: + contents: read steps: - run: sudo apt install -y graphviz - name: Set up Python @@ -56,3 +62,30 @@ jobs: run: pip install tox - name: Run python tests run: tox -e ${{ matrix.toxenv }} + pypi-publish: + needs: + - tests + - docs + - coveralls + if: ${{ success() }} && github.repository == 'oauthlib/oauthlib' && startsWith(github.ref, 'refs/tags') + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/oauthlib + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install prereq + run: pip install build twine + - name: Build python package + run: python -m build + - name: Check python package + run: twine check dist/* + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index b36e018a..00000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Publish release -on: - workflow_run: - workflows: ["Python Tests"] - types: - - completed -jobs: - pypi-publish: - if: github.repository_owner == 'oauthlib' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - name: Upload release to PyPI - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/oauthlib - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - - name: Check out repository code - uses: actions/checkout@v4 - with: # by default, this event will trigger a workflow on the default branch. - ref: ${{ github.event.workflow_run.head_ref }} # set source branch - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - name: Install prereq - run: pip install wheel - - name: Build python package - run: python setup.py build - - name: Package python package - run: python setup.py sdist bdist_wheel - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 From a2779c65b3e961f1b49c05a2417c0ae819f3f8c4 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 20 Jun 2025 00:49:42 +0200 Subject: [PATCH 33/33] Bump dependency-review --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 4e751977..0d4a0136 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,4 +17,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy