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 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 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/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/SECURITY.md b/SECURITY.md index 7d0f5250..f1a3fb80 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,4 +12,16 @@ The following versions are currently being supported with security updates. | < 3.1 | :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 + +## 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. 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 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 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()) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 4675a31f..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,14 +495,16 @@ 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 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/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, diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 2a7a8ff3..f9a4c9d1 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -262,3 +262,24 @@ 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"} + ) + self.assertIsNotNone(client) + client = WebApplicationClient( + client_id="dummy", + 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)),
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: