From c5daaebde3899c376f5defeb385c0d892ad3707b Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Mon, 20 May 2024 22:20:12 -0400 Subject: [PATCH 001/115] whitelist -> allowlist (#1422) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 61b983b5b..ba97bd113 100644 --- a/tox.ini +++ b/tox.ini @@ -127,7 +127,7 @@ commands = deps = setuptools>=39.0 wheel -whitelist_externals = rm +allowlist_externals = rm commands = rm -rf dist python setup.py sdist bdist_wheel From fd2bcec428d7f730973886211519e7c91d90e875 Mon Sep 17 00:00:00 2001 From: Giovanni <63993401+giovanni1106@users.noreply.github.com> Date: Wed, 22 May 2024 14:42:29 -0300 Subject: [PATCH 002/115] 1421 missing import in documentation (#1424) * docs: add missing import * add name in authors --- AUTHORS | 1 + docs/tutorial/tutorial_05.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 52a3693af..15eec14f9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,6 +56,7 @@ Florian Demmer Frederico Vieira Gaël Utard Glauco Junior +Giovanni Giampauli Hasan Ramezani Hiroki Kiyohara Hossein Shakiba diff --git a/docs/tutorial/tutorial_05.rst b/docs/tutorial/tutorial_05.rst index e75f3e23e..74feec4d2 100644 --- a/docs/tutorial/tutorial_05.rst +++ b/docs/tutorial/tutorial_05.rst @@ -65,6 +65,7 @@ Now add a new file to your app to add Celery: :file:`tutorial/celery.py`: import os from celery import Celery + from django.conf import settings # Set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tutorial.settings') From 30afee8e82c2654c7de77d0182330b632ccc9f04 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 13:02:49 -0400 Subject: [PATCH 003/115] [pre-commit.ci] pre-commit autoupdate (#1429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.2.6 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.2.6...v2.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eea3dd1af..ea110f065 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: sphinx-lint # Configuration for codespell is in pyproject.toml - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell exclude: (package-lock.json|/locale/) From 5185d20840dc2d0e9156cced3d7ddb6be4d4c65c Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Wed, 12 Jun 2024 09:44:53 -0400 Subject: [PATCH 004/115] Remove stuff that was deprecated for 2.5.0 (#1425) * Remove stuff that was deprecated for 2.5.0 * add PR# * temporarily remove codespell which is incorrectly causing commit failures until we can better tune it. --- .pre-commit-config.yaml | 14 ++--- CHANGELOG.md | 2 + oauth2_provider/validators.py | 34 ----------- oauth2_provider/views/oidc.py | 71 ----------------------- pyproject.toml | 8 +-- tests/test_oidc_views.py | 105 +--------------------------------- tests/test_validators.py | 96 +------------------------------ 7 files changed, 15 insertions(+), 315 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea110f065..c1628c521 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,10 +30,10 @@ repos: hooks: - id: sphinx-lint # Configuration for codespell is in pyproject.toml - - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 - hooks: - - id: codespell - exclude: (package-lock.json|/locale/) - additional_dependencies: - - tomli + # - repo: https://github.com/codespell-project/codespell + # rev: v2.3.0 + # hooks: + # - id: codespell + # exclude: (package-lock.json|/locale/) + # additional_dependencies: + # - tomli diff --git a/CHANGELOG.md b/CHANGELOG.md index c965bc21b..8bf0ff2ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Deprecated ### Removed +* #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 + ### Fixed ### Security diff --git a/oauth2_provider/validators.py b/oauth2_provider/validators.py index 1654dccd7..b238b12d6 100644 --- a/oauth2_provider/validators.py +++ b/oauth2_provider/validators.py @@ -1,5 +1,4 @@ import re -import warnings from urllib.parse import urlsplit from django.core.exceptions import ValidationError @@ -19,20 +18,6 @@ class URIValidator(URLValidator): regex = re.compile(scheme_re + host_re + port_re + path_re, re.IGNORECASE) -class RedirectURIValidator(URIValidator): - def __init__(self, allowed_schemes, allow_fragments=False): - warnings.warn("This class is deprecated and will be removed in version 2.5.0.", DeprecationWarning) - super().__init__(schemes=allowed_schemes) - self.allow_fragments = allow_fragments - - def __call__(self, value): - super().__call__(value) - value = force_str(value) - scheme, netloc, path, query, fragment = urlsplit(value) - if fragment and not self.allow_fragments: - raise ValidationError("Redirect URIs must not contain fragments") - - class AllowedURIValidator(URIValidator): # TODO: find a way to get these associated with their form fields in place of passing name # TODO: submit PR to get `cause` included in the parent class ValidationError params` @@ -90,22 +75,3 @@ def __call__(self, value): "%(name)s URI validation error. %(cause)s: %(value)s", params={"name": self.name, "value": value, "cause": e}, ) - - -## -# WildcardSet is a special set that contains everything. -# This is required in order to move validation of the scheme from -# URLValidator (the base class of URIValidator), to OAuth2Application.clean(). - - -class WildcardSet(set): - """ - A set that always returns True on `in`. - """ - - def __init__(self, *args, **kwargs): - warnings.warn("This class is deprecated and will be removed in version 2.5.0.", DeprecationWarning) - super().__init__(*args, **kwargs) - - def __contains__(self, item): - return True diff --git a/oauth2_provider/views/oidc.py b/oauth2_provider/views/oidc.py index 584b0c895..c9d10c25e 100644 --- a/oauth2_provider/views/oidc.py +++ b/oauth2_provider/views/oidc.py @@ -1,5 +1,4 @@ import json -import warnings from urllib.parse import urlparse from django.contrib.auth import logout @@ -212,76 +211,6 @@ def _validate_claims(request, claims): return True -def validate_logout_request(request, id_token_hint, client_id, post_logout_redirect_uri): - """ - Validate an OIDC RP-Initiated Logout Request. - `(prompt_logout, (post_logout_redirect_uri, application), token_user)` is returned. - - `prompt_logout` indicates whether the logout has to be confirmed by the user. This happens if the - specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`. - `post_logout_redirect_uri` is the validated URI where the User should be redirected to after the - logout. Can be None. None will redirect to "/" of this app. If it is set `application` will also - be set to the Application that is requesting the logout. `token_user` is the id_token user, which will - used to revoke the tokens if found. - - The `id_token_hint` will be validated if given. If both `client_id` and `id_token_hint` are given they - will be validated against each other. - """ - - warnings.warn("This method is deprecated and will be removed in version 2.5.0.", DeprecationWarning) - - id_token = None - must_prompt_logout = True - token_user = None - if id_token_hint: - # Only basic validation has been done on the IDToken at this point. - id_token, claims = _load_id_token(id_token_hint) - - if not id_token or not _validate_claims(request, claims): - raise InvalidIDTokenError() - - token_user = id_token.user - - if id_token.user == request.user: - # A logout without user interaction (i.e. no prompt) is only allowed - # if an ID Token is provided that matches the current user. - must_prompt_logout = False - - # If both id_token_hint and client_id are given it must be verified that they match. - if client_id: - if id_token.application.client_id != client_id: - raise ClientIdMissmatch() - - # The standard states that a prompt should always be shown. - # This behaviour can be configured with OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT. - prompt_logout = must_prompt_logout or oauth2_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT - - application = None - # Determine the application that is requesting the logout. - if client_id: - application = get_application_model().objects.get(client_id=client_id) - elif id_token: - application = id_token.application - - # Validate `post_logout_redirect_uri` - if post_logout_redirect_uri: - if not application: - raise InvalidOIDCClientError() - scheme = urlparse(post_logout_redirect_uri)[0] - if not scheme: - raise InvalidOIDCRedirectURIError("A Scheme is required for the redirect URI.") - if oauth2_settings.OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS and ( - scheme == "http" and application.client_type != "confidential" - ): - raise InvalidOIDCRedirectURIError("http is only allowed with confidential clients.") - if scheme not in application.get_allowed_schemes(): - raise InvalidOIDCRedirectURIError(f'Redirect to scheme "{scheme}" is not permitted.') - if not application.post_logout_redirect_uri_allowed(post_logout_redirect_uri): - raise InvalidOIDCRedirectURIError("This client does not have this redirect uri registered.") - - return prompt_logout, (post_logout_redirect_uri, application), token_user - - class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView): template_name = "oauth2_provider/logout_confirm.html" form_class = ConfirmLogoutForm diff --git a/pyproject.toml b/pyproject.toml index 900f4d3dd..4d10990b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ exclude = ''' ''' # Ref: https://github.com/codespell-project/codespell#using-a-config-file -[tool.codespell] -skip = '.git,package-lock.json,locale' -check-hidden = true -ignore-regex = '.*pragma: codespell-ignore.*' +# [tool.codespell] +# skip = '.git,package-lock.json,locale' +# check-hidden = true +# ignore-regex = '.*pragma: codespell-ignore.*' # ignore-words-list = '' diff --git a/tests/test_oidc_views.py b/tests/test_oidc_views.py index 4bcf839ef..f44a808e7 100644 --- a/tests/test_oidc_views.py +++ b/tests/test_oidc_views.py @@ -15,12 +15,7 @@ from oauth2_provider.models import get_access_token_model, get_id_token_model, get_refresh_token_model from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.settings import oauth2_settings -from oauth2_provider.views.oidc import ( - RPInitiatedLogoutView, - _load_id_token, - _validate_claims, - validate_logout_request, -) +from oauth2_provider.views.oidc import RPInitiatedLogoutView, _load_id_token, _validate_claims from . import presets @@ -225,104 +220,6 @@ def mock_request_for(user): return request -@pytest.mark.django_db -@pytest.mark.parametrize("ALWAYS_PROMPT", [True, False]) -def test_deprecated_validate_logout_request( - oidc_tokens, public_application, other_user, rp_settings, ALWAYS_PROMPT -): - rp_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT = ALWAYS_PROMPT - oidc_tokens = oidc_tokens - application = oidc_tokens.application - client_id = application.client_id - id_token = oidc_tokens.id_token - assert validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=None, - post_logout_redirect_uri=None, - ) == (True, (None, None), None) - assert validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=client_id, - post_logout_redirect_uri=None, - ) == (True, (None, application), None) - assert validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=client_id, - post_logout_redirect_uri="http://example.org", - ) == (True, ("http://example.org", application), None) - assert validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=id_token, - client_id=None, - post_logout_redirect_uri="http://example.org", - ) == (ALWAYS_PROMPT, ("http://example.org", application), oidc_tokens.user) - assert validate_logout_request( - request=mock_request_for(other_user), - id_token_hint=id_token, - client_id=None, - post_logout_redirect_uri="http://example.org", - ) == (True, ("http://example.org", application), oidc_tokens.user) - assert validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=id_token, - client_id=client_id, - post_logout_redirect_uri="http://example.org", - ) == (ALWAYS_PROMPT, ("http://example.org", application), oidc_tokens.user) - with pytest.raises(InvalidIDTokenError): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint="111", - client_id=public_application.client_id, - post_logout_redirect_uri="http://other.org", - ) - with pytest.raises(ClientIdMissmatch): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=id_token, - client_id=public_application.client_id, - post_logout_redirect_uri="http://other.org", - ) - with pytest.raises(InvalidOIDCClientError): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=None, - post_logout_redirect_uri="http://example.org", - ) - with pytest.raises(InvalidOIDCRedirectURIError): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=client_id, - post_logout_redirect_uri="example.org", - ) - with pytest.raises(InvalidOIDCRedirectURIError): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=client_id, - post_logout_redirect_uri="imap://example.org", - ) - with pytest.raises(InvalidOIDCRedirectURIError): - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=client_id, - post_logout_redirect_uri="http://other.org", - ) - with pytest.raises(InvalidOIDCRedirectURIError): - rp_settings.OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS = True - validate_logout_request( - request=mock_request_for(oidc_tokens.user), - id_token_hint=None, - client_id=public_application.client_id, - post_logout_redirect_uri="http://other.org", - ) - - @pytest.mark.django_db def test_validate_logout_request(oidc_tokens, public_application, rp_settings): oidc_tokens = oidc_tokens diff --git a/tests/test_validators.py b/tests/test_validators.py index b2bbb2970..a28e54a4d 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -2,101 +2,7 @@ from django.core.validators import ValidationError from django.test import TestCase -from oauth2_provider.validators import AllowedURIValidator, RedirectURIValidator, WildcardSet - - -@pytest.mark.usefixtures("oauth2_settings") -class TestValidators(TestCase): - def test_validate_good_uris(self): - validator = RedirectURIValidator(allowed_schemes=["https"]) - good_uris = [ - "https://example.com/", - "https://example.org/?key=val", - "https://example", - "https://localhost", - "https://1.1.1.1", - "https://127.0.0.1", - "https://255.255.255.255", - ] - for uri in good_uris: - # Check ValidationError not thrown - validator(uri) - - def test_validate_custom_uri_scheme(self): - validator = RedirectURIValidator(allowed_schemes=["my-scheme", "https", "git+ssh"]) - good_uris = [ - "my-scheme://example.com", - "my-scheme://example", - "my-scheme://localhost", - "https://example.com", - "HTTPS://example.com", - "git+ssh://example.com", - ] - for uri in good_uris: - # Check ValidationError not thrown - validator(uri) - - def test_validate_bad_uris(self): - validator = RedirectURIValidator(allowed_schemes=["https"]) - self.oauth2_settings.ALLOWED_REDIRECT_URI_SCHEMES = ["https", "good"] - bad_uris = [ - "http:/example.com", - "HTTP://localhost", - "HTTP://example.com", - "HTTP://example.com.", - "http://example.com/#fragment", - "123://example.com", - "http://fe80::1", - "git+ssh://example.com", - "my-scheme://example.com", - "uri-without-a-scheme", - "https://example.com/#fragment", - "good://example.com/#fragment", - " ", - "", - # Bad IPv6 URL, urlparse behaves differently for these - 'https://[">', - ] - - for uri in bad_uris: - with self.assertRaises(ValidationError): - validator(uri) - - def test_validate_wildcard_scheme__bad_uris(self): - validator = RedirectURIValidator(allowed_schemes=WildcardSet()) - bad_uris = [ - "http:/example.com#fragment", - "HTTP://localhost#fragment", - "http://example.com/#fragment", - "good://example.com/#fragment", - " ", - "", - # Bad IPv6 URL, urlparse behaves differently for these - 'https://[">', - ] - - for uri in bad_uris: - with self.assertRaises(ValidationError, msg=uri): - validator(uri) - - def test_validate_wildcard_scheme_good_uris(self): - validator = RedirectURIValidator(allowed_schemes=WildcardSet()) - good_uris = [ - "my-scheme://example.com", - "my-scheme://example", - "my-scheme://localhost", - "https://example.com", - "HTTPS://example.com", - "HTTPS://example.com.", - "git+ssh://example.com", - "ANY://localhost", - "scheme://example.com", - "at://example.com", - "all://example.com", - ] - for uri in good_uris: - # Check ValidationError not thrown - validator(uri) +from oauth2_provider.validators import AllowedURIValidator @pytest.mark.usefixtures("oauth2_settings") From 12236cd11d3696e17e43f4470cd6334bfb4672fe Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Wed, 12 Jun 2024 17:09:48 -0400 Subject: [PATCH 005/115] fix: test/app/rp npm install failing (#1430) --- .github/workflows/test.yml | 38 +- tests/app/rp/package-lock.json | 761 ++++++++++++++++++++------- tests/app/rp/package.json | 10 +- tests/app/rp/src/app.html | 2 +- tests/app/rp/src/routes/+page.svelte | 75 ++- tests/app/rp/svelte.config.js | 2 +- 6 files changed, 662 insertions(+), 226 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86a21bc27..627aacf97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,8 @@ name: Test on: [push, pull_request] jobs: - build: - name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) + test-package: + name: Test Package (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -84,8 +84,40 @@ jobs: with: name: Python ${{ matrix.python-version }} + test-demo-rp: + name: Test Demo Relying Party + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: + - "18.x" + - "20.x" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up NodeJS + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm install + working-directory: tests/app/rp + + - name: Run Lint + run: npm run lint + working-directory: tests/app/rp + + - name: Run build + run: npm run build + working-directory: tests/app/rp + success: - needs: build + needs: + - test-package + - test-demo-rp runs-on: ubuntu-latest name: Test successful steps: diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 80b168437..9188cf955 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -13,13 +13,26 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/kit": "^2.5.0", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.8.1", - "svelte": "^3.54.0", - "svelte-check": "^3.0.1", + "prettier": "^3.3.2", + "prettier-plugin-svelte": "^3.2.4", + "svelte": "^4.0.0", + "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^4.5.3" + "vite": "^5.0.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@dopry/svelte-oidc": { @@ -30,10 +43,26 @@ "oidc-client": "1.11.5" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -47,9 +76,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -63,9 +92,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -79,9 +108,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -95,9 +124,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -111,9 +140,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -127,9 +156,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -143,9 +172,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -159,9 +188,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -175,9 +204,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -191,9 +220,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -207,9 +236,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -223,9 +252,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -239,9 +268,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -255,9 +284,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -271,9 +300,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -287,9 +316,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -303,9 +332,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -319,9 +348,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -335,9 +364,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -351,9 +380,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -367,9 +396,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -382,6 +411,20 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", @@ -391,6 +434,15 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -398,21 +450,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -454,6 +500,214 @@ "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", "dev": true }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sveltejs/adapter-auto": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.1.tgz", @@ -545,10 +799,16 @@ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "dev": true }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/pug": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", - "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", "dev": true }, "node_modules/acorn": { @@ -575,6 +835,24 @@ "node": ">= 8" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -632,12 +910,12 @@ } }, "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, "engines": { - "node": "*" + "node": ">=8.0.0" } }, "node_modules/callsites": { @@ -676,6 +954,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/code-red/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -706,6 +1009,19 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -734,6 +1050,15 @@ "node": ">=0.10.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -756,9 +1081,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -768,28 +1093,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/esm-env": { @@ -798,6 +1124,15 @@ "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", "dev": true }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -842,9 +1177,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -859,6 +1194,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -935,6 +1271,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -989,6 +1326,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -998,6 +1344,12 @@ "node": ">=6" } }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true + }, "node_modules/magic-string": { "version": "0.30.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", @@ -1010,6 +1362,12 @@ "node": ">=12" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1100,9 +1458,9 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -1168,6 +1526,17 @@ "node": ">=0.10.0" } }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1187,9 +1556,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -1206,37 +1575,37 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prettier-plugin-svelte": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.10.0.tgz", - "integrity": "sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.4.tgz", + "integrity": "sha512-tZv+ADfeOWFNQkXkRh6zUXE16w3Vla8x2Ug0B/EnSmjR4EnwdwZbGgL/liSwR1kcEALU5mAAyua98HBxheCxgg==", "dev": true, "peerDependencies": { - "prettier": "^1.16.4 || ^2.0.0", - "svelte": "^3.2.0" + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "node_modules/queue-microtask": { @@ -1302,6 +1671,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -1311,18 +1681,37 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -1421,13 +1810,13 @@ } }, "node_modules/sorcery": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", - "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz", + "integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", - "buffer-crc32": "^0.2.5", + "buffer-crc32": "^1.0.0", "minimist": "^1.2.0", "sander": "^0.5.0" }, @@ -1436,9 +1825,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1457,18 +1846,34 @@ } }, "node_modules/svelte": { - "version": "3.58.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz", - "integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", + "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, "engines": { - "node": ">= 8" + "node": ">=16" } }, "node_modules/svelte-check": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.2.0.tgz", - "integrity": "sha512-6ZnscN8dHEN5Eq5LgIzjj07W9nc9myyBH+diXsUAuiY/3rt0l65/LCIQYlIuoFEjp2F1NhXqZiJwV9omPj9tMw==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.0.tgz", + "integrity": "sha512-7Nxn+3X97oIvMzYJ7t27w00qUf1Y52irE2RU2dQAd5PyvfGp4E7NLhFKVhb6PV2fx7dCRMpNKDIuazmGthjpSQ==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -1477,14 +1882,14 @@ "import-fresh": "^3.2.1", "picocolors": "^1.0.0", "sade": "^1.7.4", - "svelte-preprocess": "^5.0.3", + "svelte-preprocess": "^5.1.3", "typescript": "^5.0.3" }, "bin": { "svelte-check": "bin/svelte-check" }, "peerDependencies": { - "svelte": "^3.55.0" + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" } }, "node_modules/svelte-hmr": { @@ -1501,32 +1906,32 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.3.tgz", - "integrity": "sha512-GrHF1rusdJVbOZOwgPWtpqmaexkydznKzy5qIC2FabgpFyKN57bjMUUUqPRfbBXK5igiEWn1uO/DXsa2vJ5VHA==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", + "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^3.23.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -1562,16 +1967,16 @@ } } }, - "node_modules/svelte-preprocess/node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "node_modules/svelte/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=12" + "node": ">=0.4.0" } }, "node_modules/tiny-glob": { @@ -1625,29 +2030,29 @@ } }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", + "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", "dev": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 4a3851d97..dd087397e 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -14,13 +14,13 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/kit": "^2.5.0", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.8.1", - "svelte": "^3.54.0", - "svelte-check": "^3.0.1", + "prettier": "^3.3.2", + "prettier-plugin-svelte": "^3.2.4", + "svelte": "^4.0.0", + "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^4.5.3" + "vite": "^5.0.3" }, "type": "module", "dependencies": { diff --git a/tests/app/rp/src/app.html b/tests/app/rp/src/app.html index effe0d0d2..77ec85d79 100644 --- a/tests/app/rp/src/app.html +++ b/tests/app/rp/src/app.html @@ -1,4 +1,4 @@ - + diff --git a/tests/app/rp/src/routes/+page.svelte b/tests/app/rp/src/routes/+page.svelte index 5853d61f1..1df1a226b 100644 --- a/tests/app/rp/src/routes/+page.svelte +++ b/tests/app/rp/src/routes/+page.svelte @@ -1,44 +1,43 @@ {#if browser} - - - Login - Logout - RefreshToken
-
isLoading: {$isLoading}
-
isAuthenticated: {$isAuthenticated}
-
authToken: {$accessToken}
-
idToken: {$idToken}
-
userInfo: {JSON.stringify($userInfo, null, 2)}
-
authError: {$authError}
-
+ + Login + Logout + RefreshToken
+
isLoading: {$isLoading}
+
isAuthenticated: {$isAuthenticated}
+
authToken: {$accessToken}
+
idToken: {$idToken}
+
userInfo: {JSON.stringify($userInfo, null, 2)}
+
authError: {$authError}
+
{/if} diff --git a/tests/app/rp/svelte.config.js b/tests/app/rp/svelte.config.js index 1cf26a00d..2b35fe1be 100644 --- a/tests/app/rp/svelte.config.js +++ b/tests/app/rp/svelte.config.js @@ -1,5 +1,5 @@ import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/kit/vite'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { From c09ca765d505a1d2e5ed24efed8c53b289c32f71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:20:20 -0400 Subject: [PATCH 006/115] Bump braces from 3.0.2 to 3.0.3 in /tests/app/rp (#1432) --- tests/app/rp/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 9188cf955..a20b5654d 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -898,12 +898,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1159,9 +1159,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" From 9a862fc749ce675628a460cc8a6c081444793937 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Thu, 13 Jun 2024 17:31:11 -0400 Subject: [PATCH 007/115] chore: fix code spell errors (#1431) --- .pre-commit-config.yaml | 16 ++++++++-------- CHANGELOG.md | 2 +- pyproject.toml | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1628c521..235de3f1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,11 +29,11 @@ repos: rev: v0.9.1 hooks: - id: sphinx-lint - # Configuration for codespell is in pyproject.toml - # - repo: https://github.com/codespell-project/codespell - # rev: v2.3.0 - # hooks: - # - id: codespell - # exclude: (package-lock.json|/locale/) - # additional_dependencies: - # - tomli +# Configuration for codespell is in pyproject.toml + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + exclude: (package-lock.json|/locale/) + additional_dependencies: + - tomli diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0ff2ee..362fd74b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -206,7 +206,7 @@ This is a major release with **BREAKING** changes. Please make sure to review th ## [1.6.0] 2021-12-19 ### Added -* #949 Provide django.contrib.auth.authenticate() with a `request` for compatibiity with more backends (like django-axes). +* #949 Provide django.contrib.auth.authenticate() with a `request` for compatibility with more backends (like django-axes). * #968, #1039 Add support for Django 3.2 and 4.0. * #953 Allow loopback redirect URIs using random ports as described in [RFC8252 section 7.3](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3). * #972 Add Farsi/fa language support. diff --git a/pyproject.toml b/pyproject.toml index 4d10990b9..884f7aec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ exclude = ''' ''' # Ref: https://github.com/codespell-project/codespell#using-a-config-file -# [tool.codespell] -# skip = '.git,package-lock.json,locale' -# check-hidden = true -# ignore-regex = '.*pragma: codespell-ignore.*' -# ignore-words-list = '' +[tool.codespell] +skip = '.git,package-lock.json,locale,AUTHORS,tox.ini' +check-hidden = true +ignore-regex = '.*pragma: codespell-ignore.*' +ignore-words-list = 'assertIn' From 133ba8513b077729985ad5dfa7fafceb0ccbfc30 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Mon, 17 Jun 2024 09:57:26 -0400 Subject: [PATCH 008/115] feat: containerized apps (#1366) --- .dockerignore | 73 ++++++++ .gitignore | 2 + Dockerfile | 67 +++++++ docker-compose.yml | 40 +++++ tests/app/idp/idp/settings.py | 151 +++++++++------- tests/app/idp/requirements.txt | 1 + tests/app/rp/Dockerfile | 16 ++ tests/app/rp/package-lock.json | 308 +++++++++++++++++++++++++++++++-- tests/app/rp/package.json | 3 +- tests/app/rp/svelte.config.js | 6 +- 10 files changed, 582 insertions(+), 85 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 tests/app/rp/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..1c1551eb3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,73 @@ +venv +__pycache__ +.tox +.github +.vscode +.django_oauth_toolkit.egg-info +.coverage +coverage.xml + +# every time we change this we need to do the COPY . /code and +# RUN pip install -r requirements.txt again +# so don't include the Dockerfile in the context. +Dockerfile +docker-compose.yml + + +# from .gitignore +*.py[cod] + +*.swp + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.cache +.pytest_cache +.coverage +.tox +.pytest_cache/ +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# PyCharm stuff +.idea + +# Sphinx build dir +_build + +# Sqlite database files +*.sqlite + +/venv/ +/coverage.xml + +db.sqlite3 +venv/ diff --git a/.gitignore b/.gitignore index c4436f57d..70d81b559 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ _build db.sqlite3 venv/ + +/tests/app/idp/static diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..e501e84d2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +# syntax=docker/dockerfile:1.6.0 +# this Dockerfile is located at the root so the build context +# includes oauth2_provider which is a requirement of the +# tests/app/idp. This way we build images with the source +# code from the repos for validation before publishing packages. + +FROM python:3.11.6-slim as builder + +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +ENV DEBUG=False +ENV ALLOWED_HOSTS="*" +ENV TEMPLATES_DIRS="/data/templates" +ENV STATIC_ROOT="/data/static" +ENV DATABASE_URL="sqlite:////data/db.sqlite3" + +RUN apt-get update +# Build Deps +RUN apt-get install -y --no-install-recommends gcc libc-dev python3-dev git openssh-client libpq-dev file libev-dev +# bundle code in a virtual env to make copying to the final image without all the upstream stuff easier. +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +# need to update pip and setuptools for pep517 support required by gevent. +RUN pip install --upgrade pip +RUN pip install --upgrade setuptools +COPY . /code +WORKDIR /code/tests/app/idp +RUN pip install -r requirements.txt +RUN pip install gunicorn +RUN python manage.py collectstatic --noinput + + + +FROM python:3.11.6-slim + +# allow embed sha1 at build time as release. +ARG GIT_SHA1 + +LABEL org.opencontainers.image.authors="https://jazzband.co/projects/django-oauth-toolkit" +LABEL org.opencontainers.image.source="https://github.com/jazzband/django-oauth-toolkit" +LABEL org.opencontainers.image.revision=${GIT_SHA1} + + +ENV SENTRY_RELEASE=${GIT_SHA1} + +# disable debug mode, but allow all hosts by default when running in docker +ENV DEBUG=False +ENV ALLOWED_HOSTS="*" +ENV TEMPLATES_DIRS="/data/templates" +ENV STATIC_ROOT="/data/static" +ENV DATABASE_URL="sqlite:////data/db.sqlite3" + + + + +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +COPY --from=builder /code /code +RUN mkdir -p /code/tests/app/idp/static /code/tests/app/idp/templates +WORKDIR /code/tests/app/idp +RUN apt-get update && apt-get install -y \ + libpq5 \ + && rm -rf /var/lib/apt/lists/* +EXPOSE 80 +VOLUME ["/data" ] +CMD ["gunicorn", "idp.wsgi:application", "-w 4 -b 0.0.0.0:80 --chdir=/code --worker-tmp-dir /dev/shm --timeout 120 --error-logfile '-' --log-level debug --access-logfile '-'"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..3a3459fde --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +volumes: + idp-data: + + +x-idp: &idp + image: django-oauth-toolkit/idp + volumes: + - idp-data:/data + +services: + idp-migrate: + <<: *idp + build: . + command: python manage.py migrate + + idp-loaddata: + <<: *idp + command: python manage.py loaddata fixtures/seed.json + depends_on: + idp-migrate: + condition: service_completed_successfully + + idp: + <<: *idp + command: gunicorn idp.wsgi:application -w 4 -b 0.0.0.0:80 --chdir=/code --timeout 120 --error-logfile '-' --log-level debug --access-logfile '-' + ports: + # map to dev port. + - "8000:80" + depends_on: + idp-loaddata: + condition: service_completed_successfully + + rp: + image: django-oauth-toolkit/rp + build: ./tests/app/rp + ports: + # map to dev port. + - "5173:3000" + depends_on: + - idp \ No newline at end of file diff --git a/tests/app/idp/idp/settings.py b/tests/app/idp/idp/settings.py index 375cdcc9b..eee20982e 100644 --- a/tests/app/idp/idp/settings.py +++ b/tests/app/idp/idp/settings.py @@ -13,21 +13,93 @@ import os from pathlib import Path +import environ + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +env = environ.FileAwareEnv( + DEBUG=(bool, True), + ALLOWED_HOSTS=(list, []), + DATABASE_URL=(str, "sqlite:///db.sqlite3"), + SECRET_KEY=(str, "django-insecure-vri27@j_q62e2it4$xiy9ca!7@qgjkhhan(*zs&lz0k@yukbb3"), + OAUTH2_PROVIDER_OIDC_ENABLED=(bool, True), + OAUTH2_PROVIDER_OIDC_RP_INITIATED_LOGOUT_ENABLED=(bool, True), + OAUTH2_PROVIDER_OIDC_RSA_PRIVATE_KEY=( + str, + """ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAtd8X/v8pddKt+opMJZrhV4FH86gBTMPjTGXeAfKkQVf7KDUZ +Ty90n+JMe2rvCUn+Nws9yy5vmtbkomQbj8Xs1kHJOVdCnH1L2HTkvM7BjTBmJ5vc +bA94IBmSf9jJIzfIJkepshRLcGllMvHPOYQiR+lJsj58FFDLZN4/182S21C8Ri0w ++63rT64SxiQkqt6h+E1w7V+tHQJKDZq3du1QctZVXiIr6Zs5BgTjTyRURoiqUVH0 +WJ4dT2t4+Rg9mp3PBlVwTOqzw9xTcO8ke+ZdrIWP4euZuPIr/Dya5R7S2Ki8Nwag +ANGV+LghJilucuWzJlOBO8TlIVUwgUaGOqaDxMHx9P/nRLQ6vTKP81FUJ7gNv6oj +W+6No6nMhsESQ+thizvBYOgintZZoeBwpB8lebKvGJUeqRo6qhc5BeUEjAjsAgtP +sJrRNQ4t8PT8mP+2dw4sU7J5PBAtx+ZdZ9bcH/sNuohBj77+6WhyvjmeYIKgCgjO +TdZH9O+kUIMaX9mlB+WvoVsk32qensZG/CgXXa3rWyXPvOdA9aOE4V0GCv1JfWKK +OXA8aY5aUGy0VvOWXHWpft5begr8onCjNs9UR6fCdCvcrSuiHTvNpM37E6Xh4kV4 +uMzjGaj5ZLBOAY3cYzFI6LNrK4/YJvzLi9jxI1sJG1ZMz8kCywuJISEq4LcCAwEA +AQKCAgBcnbV8l7gnVhhfA9pvNAYZJ67ad+3hh8fSefWqjEP1Orad7RxsZMBBQ16r +YvNDibi5kzHurEENWu2nfM9EUgifu3SbjMJRKsVa/3wUYj3ShpkfBpIjPWVxA1TF +YkJbeuakB8507zzTi/iLDvT2V0GV2Uk8SfGp7tMFFODyJq/om56lJhJRuGmidAT/ +fhxmH2XgKp+dYiGoKihH8UgIeiWDtX5Xp5MxLWjGleqjvN5l5ObG7rM+BZbrgNFk +GGIWwNJSaWP853CQBz0+v6mWpuOBHar945quwjSACOTgVOgOiS7/3pHQmOqEdE/9 +PRAP1sV6eP/Qzh3Y8ab3zlBAwddLmZi+8sVV/sJadEMciU6AR8ZInf2zWtmxh6Ft +TNXUrSmDjKId84wyYT+pDg8Vv04X8xMNLWAIYeBawOPasEiBiFVUqDGHciPMBbhb +XxZK7Noi8akzCLWouPkrW4pjpsd5xrllakGFAFPktLvc8ZRyz2InaQKqhaaU+is5 +ykAeHpJHVxg1xFY0hX06i8pkjXQROhc7+GUuifxKvVcouCwlUiSxcHGQLqzGKnYE +fpCs9uGI8+XolEq637LyYaZ7zpWd8Ehiw4AEfE3oOVIQd4xAQ8YDJxUG1fUYQfF8 +iD5VO2+WO7a9QfScFZK+UebHEEXQGq4+JNUlP0KSnSsp3J0XkQKCAQEA3Y0sE9sE +l8VTTW3oxKChmq18UKJchyXU3BMLFnvDAPweUTdtS0QUIsDQD2pCU7wQonWOpqUj +vMwlTZjyNo+9N0l2fqleha1phzgYFCfTsgJ6gcl82y/JUvsGqMglKOUKoCFW5UtM +kUO+P5S25GqiDc0qsO6FGKSOvJ5aJLYEpEK5ez2q9uyzSYbp5aUuKwLb11rX0HW9 +JjkB7hL4OtHpJ9E9uAsOj4VIWpysmX3d8UIv1Uez8f+bilhCMShKk4U9xz8ZY2K4 +YXdfFr83b1kQybIDzeXeOQ5NQ6myS5HiqBSYx9Iy7Y54605KVM0CzLCPS5fAAcbW +5wq1H32OtxRS4wKCAQEA0iZ24W30BIYIx65YseVbBNs4cJr9ppqCAqUGqAhW8xfe +q7Atd6KG+lXWVDj2tZzuoYeb0PLjQRsmOs8CVFUZT0ntH6YAUOpPW8l8tkrWTugp +7fCx2pR4r8aFAVb7Jkc41ojSvaYMbUClKf+JVtFPsY1ug7gNxizGjVnpAq66XX+X +76BVIpMEUivZcXos6/BrVM3seFYQg1pMZkjjO3q8lETnlT3LIYpPtRjaFSvcMaMy +1Cb4dGUz+xj8BM73bLDEJtHZEsyF6nEnurlE9rSbMui9XhckcC267e1qvIbAnKB9 +JK5oJAM4L+xOylmvk71gdrul9Q9aT+QJGUXkPxwfHQKCAQBkMIQ/UmtISyb5u/to +eA+8yDmQqWvYfiY9g6se9sbfuiPnrH4TbG0Crlkor2/hOAn5vdnNyJ5ZsaQo7EKU +o/n4d5NLgkJJh3tSd+6DpuMX/AD0km6RHJIZoYWIbEJJtRJSCeGm/Z9Zjd4KGLGA +qCwyu5ZTvvmXhEs8RwwSz/FXawlAD0oyMiZ92LILdOBk+Pz77YvtLGFmWJ9jz1ZM +G0MqC3iysuVZx/dJatKu8vmcMcc51xwsEuB+9pywaD0Za0bdxM4xYKJrCTWKLtzd +0NRDseoAgbQ17x7Hu4Tyob1zLyVML+VyAlzyZEw+/xsF/849bBmbdBUZFIGGBRy1 +9E3rAoIBAQCDs3dtb+stqpJ2Ed2kH4kbUgfdCkVM1CgGYEX7qL5VOvBhyNe10jWl +TYY04j47M06aDNKp8I5bjxg2YuWi1HI4Lqxc2Tv5ed6iN3PhCqWkbftZEy9jPQkl +n9RbMpfTNW95g+YO1LGVBp5745m+vw6ix3ArPH3lZMpKa76L39UMI5qkoma4dEqQ +9MohQ+BDPTkGvMcl40oWB9E5iRRfglwMz+IStddH/dZWOGz0N7iXox+HtaSfzYz2 +IIJQwSRvCZjkez7/eQ20D5ZGfzWpJybckN+cyAQeCYrM8a2i2RB9GFdVVbgOWbYs +0nvOdMaEYHrD7nXjTuvahZ7uJ88TfhxBAoIBAG3ClX40pxUXs6kEOGZYUXHFaYDz +Upuvj8X2h6SaepTAAokkJxGOdeg5t3ohsaXDeV2WcNb8KRFmDuVtcGSo0mUWtrtT +RXgJT9SBEMl1rEPbEh0i9uXOaI8DWdBO62Ei0efeL0Wac7kxwBbObKDn8mQCmlWK +4nvzevqUB8frm9abjRGTOZX8QlNZcPs065vHubNJ8SAqr+uoe1GTb0qL7YkWT6vb +dBCCnF8FP1yPW8UgGVGSeozmIMaJwSpl2srZUMkN1KlqHwzehrOn9Tn2grA9ue/i +ipUMvb4Se0LDJnmFuv8v6gM6V4vyXkP855mNOiRHUOHOSKdQ3SeKrLlnR6I= +-----END RSA PRIVATE KEY----- +""", + ), + OAUTH2_PROVIDER_SCOPES=(dict, {"openid": "OpenID Connect scope"}), + OAUTH2_PROVIDER_ALLOWED_SCHEMES=(list, ["https", "http"]), + OAUTHLIB_INSECURE_TRANSPORT=(bool, "1"), + STATIC_ROOT=(str, BASE_DIR / "static"), + STATIC_URL=(str, "static/"), + TEMPLATES_DIRS=(list, [BASE_DIR / "templates"]), +) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-vri27@j_q62e2it4$xiy9ca!7@qgjkhhan(*zs&lz0k@yukbb3" +SECRET_KEY = env("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = env("DEBUG") -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = env("ALLOWED_HOSTS") # Application definition @@ -60,7 +132,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [BASE_DIR / "templates"], + "DIRS": env("TEMPLATES_DIRS"), "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -80,10 +152,7 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } + "default": env.db(), } @@ -120,8 +189,8 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ - -STATIC_URL = "static/" +STATIC_ROOT = env("STATIC_ROOT") +STATIC_URL = env("STATIC_URL") # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field @@ -130,69 +199,17 @@ OAUTH2_PROVIDER = { "OAUTH2_VALIDATOR_CLASS": "idp.oauth.CustomOAuth2Validator", - "OIDC_ENABLED": True, - "OIDC_RP_INITIATED_LOGOUT_ENABLED": True, + "OIDC_ENABLED": env("OAUTH2_PROVIDER_OIDC_ENABLED"), + "OIDC_RP_INITIATED_LOGOUT_ENABLED": env("OAUTH2_PROVIDER_OIDC_RP_INITIATED_LOGOUT_ENABLED"), # this key is just for out test app, you should never store a key like this in a production environment. - "OIDC_RSA_PRIVATE_KEY": """ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAtd8X/v8pddKt+opMJZrhV4FH86gBTMPjTGXeAfKkQVf7KDUZ -Ty90n+JMe2rvCUn+Nws9yy5vmtbkomQbj8Xs1kHJOVdCnH1L2HTkvM7BjTBmJ5vc -bA94IBmSf9jJIzfIJkepshRLcGllMvHPOYQiR+lJsj58FFDLZN4/182S21C8Ri0w -+63rT64SxiQkqt6h+E1w7V+tHQJKDZq3du1QctZVXiIr6Zs5BgTjTyRURoiqUVH0 -WJ4dT2t4+Rg9mp3PBlVwTOqzw9xTcO8ke+ZdrIWP4euZuPIr/Dya5R7S2Ki8Nwag -ANGV+LghJilucuWzJlOBO8TlIVUwgUaGOqaDxMHx9P/nRLQ6vTKP81FUJ7gNv6oj -W+6No6nMhsESQ+thizvBYOgintZZoeBwpB8lebKvGJUeqRo6qhc5BeUEjAjsAgtP -sJrRNQ4t8PT8mP+2dw4sU7J5PBAtx+ZdZ9bcH/sNuohBj77+6WhyvjmeYIKgCgjO -TdZH9O+kUIMaX9mlB+WvoVsk32qensZG/CgXXa3rWyXPvOdA9aOE4V0GCv1JfWKK -OXA8aY5aUGy0VvOWXHWpft5begr8onCjNs9UR6fCdCvcrSuiHTvNpM37E6Xh4kV4 -uMzjGaj5ZLBOAY3cYzFI6LNrK4/YJvzLi9jxI1sJG1ZMz8kCywuJISEq4LcCAwEA -AQKCAgBcnbV8l7gnVhhfA9pvNAYZJ67ad+3hh8fSefWqjEP1Orad7RxsZMBBQ16r -YvNDibi5kzHurEENWu2nfM9EUgifu3SbjMJRKsVa/3wUYj3ShpkfBpIjPWVxA1TF -YkJbeuakB8507zzTi/iLDvT2V0GV2Uk8SfGp7tMFFODyJq/om56lJhJRuGmidAT/ -fhxmH2XgKp+dYiGoKihH8UgIeiWDtX5Xp5MxLWjGleqjvN5l5ObG7rM+BZbrgNFk -GGIWwNJSaWP853CQBz0+v6mWpuOBHar945quwjSACOTgVOgOiS7/3pHQmOqEdE/9 -PRAP1sV6eP/Qzh3Y8ab3zlBAwddLmZi+8sVV/sJadEMciU6AR8ZInf2zWtmxh6Ft -TNXUrSmDjKId84wyYT+pDg8Vv04X8xMNLWAIYeBawOPasEiBiFVUqDGHciPMBbhb -XxZK7Noi8akzCLWouPkrW4pjpsd5xrllakGFAFPktLvc8ZRyz2InaQKqhaaU+is5 -ykAeHpJHVxg1xFY0hX06i8pkjXQROhc7+GUuifxKvVcouCwlUiSxcHGQLqzGKnYE -fpCs9uGI8+XolEq637LyYaZ7zpWd8Ehiw4AEfE3oOVIQd4xAQ8YDJxUG1fUYQfF8 -iD5VO2+WO7a9QfScFZK+UebHEEXQGq4+JNUlP0KSnSsp3J0XkQKCAQEA3Y0sE9sE -l8VTTW3oxKChmq18UKJchyXU3BMLFnvDAPweUTdtS0QUIsDQD2pCU7wQonWOpqUj -vMwlTZjyNo+9N0l2fqleha1phzgYFCfTsgJ6gcl82y/JUvsGqMglKOUKoCFW5UtM -kUO+P5S25GqiDc0qsO6FGKSOvJ5aJLYEpEK5ez2q9uyzSYbp5aUuKwLb11rX0HW9 -JjkB7hL4OtHpJ9E9uAsOj4VIWpysmX3d8UIv1Uez8f+bilhCMShKk4U9xz8ZY2K4 -YXdfFr83b1kQybIDzeXeOQ5NQ6myS5HiqBSYx9Iy7Y54605KVM0CzLCPS5fAAcbW -5wq1H32OtxRS4wKCAQEA0iZ24W30BIYIx65YseVbBNs4cJr9ppqCAqUGqAhW8xfe -q7Atd6KG+lXWVDj2tZzuoYeb0PLjQRsmOs8CVFUZT0ntH6YAUOpPW8l8tkrWTugp -7fCx2pR4r8aFAVb7Jkc41ojSvaYMbUClKf+JVtFPsY1ug7gNxizGjVnpAq66XX+X -76BVIpMEUivZcXos6/BrVM3seFYQg1pMZkjjO3q8lETnlT3LIYpPtRjaFSvcMaMy -1Cb4dGUz+xj8BM73bLDEJtHZEsyF6nEnurlE9rSbMui9XhckcC267e1qvIbAnKB9 -JK5oJAM4L+xOylmvk71gdrul9Q9aT+QJGUXkPxwfHQKCAQBkMIQ/UmtISyb5u/to -eA+8yDmQqWvYfiY9g6se9sbfuiPnrH4TbG0Crlkor2/hOAn5vdnNyJ5ZsaQo7EKU -o/n4d5NLgkJJh3tSd+6DpuMX/AD0km6RHJIZoYWIbEJJtRJSCeGm/Z9Zjd4KGLGA -qCwyu5ZTvvmXhEs8RwwSz/FXawlAD0oyMiZ92LILdOBk+Pz77YvtLGFmWJ9jz1ZM -G0MqC3iysuVZx/dJatKu8vmcMcc51xwsEuB+9pywaD0Za0bdxM4xYKJrCTWKLtzd -0NRDseoAgbQ17x7Hu4Tyob1zLyVML+VyAlzyZEw+/xsF/849bBmbdBUZFIGGBRy1 -9E3rAoIBAQCDs3dtb+stqpJ2Ed2kH4kbUgfdCkVM1CgGYEX7qL5VOvBhyNe10jWl -TYY04j47M06aDNKp8I5bjxg2YuWi1HI4Lqxc2Tv5ed6iN3PhCqWkbftZEy9jPQkl -n9RbMpfTNW95g+YO1LGVBp5745m+vw6ix3ArPH3lZMpKa76L39UMI5qkoma4dEqQ -9MohQ+BDPTkGvMcl40oWB9E5iRRfglwMz+IStddH/dZWOGz0N7iXox+HtaSfzYz2 -IIJQwSRvCZjkez7/eQ20D5ZGfzWpJybckN+cyAQeCYrM8a2i2RB9GFdVVbgOWbYs -0nvOdMaEYHrD7nXjTuvahZ7uJ88TfhxBAoIBAG3ClX40pxUXs6kEOGZYUXHFaYDz -Upuvj8X2h6SaepTAAokkJxGOdeg5t3ohsaXDeV2WcNb8KRFmDuVtcGSo0mUWtrtT -RXgJT9SBEMl1rEPbEh0i9uXOaI8DWdBO62Ei0efeL0Wac7kxwBbObKDn8mQCmlWK -4nvzevqUB8frm9abjRGTOZX8QlNZcPs065vHubNJ8SAqr+uoe1GTb0qL7YkWT6vb -dBCCnF8FP1yPW8UgGVGSeozmIMaJwSpl2srZUMkN1KlqHwzehrOn9Tn2grA9ue/i -ipUMvb4Se0LDJnmFuv8v6gM6V4vyXkP855mNOiRHUOHOSKdQ3SeKrLlnR6I= ------END RSA PRIVATE KEY----- -""", + "OIDC_RSA_PRIVATE_KEY": env("OAUTH2_PROVIDER_OIDC_RSA_PRIVATE_KEY"), "SCOPES": { "openid": "OpenID Connect scope", }, - "ALLOWED_SCHEMES": ["https", "http"], + "ALLOWED_SCHEMES": env("OAUTH2_PROVIDER_ALLOWED_SCHEMES"), } # needs to be set to allow cors requests from the test app, along with ALLOWED_SCHEMES=["http"] -os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" +os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = env("OAUTHLIB_INSECURE_TRANSPORT") LOGGING = { "version": 1, diff --git a/tests/app/idp/requirements.txt b/tests/app/idp/requirements.txt index d17f9bd45..ba8e75052 100644 --- a/tests/app/idp/requirements.txt +++ b/tests/app/idp/requirements.txt @@ -1,4 +1,5 @@ Django>=3.2,<4.2 django-cors-headers==3.14.0 +django-environ==0.11.2 -e ../../../ \ No newline at end of file diff --git a/tests/app/rp/Dockerfile b/tests/app/rp/Dockerfile new file mode 100644 index 000000000..a719a1eb4 --- /dev/null +++ b/tests/app/rp/Dockerfile @@ -0,0 +1,16 @@ +FROM node:18-alpine AS builder +WORKDIR /app +COPY package*.json . +RUN npm ci +COPY . . +RUN npm run build +RUN npm prune --production + +FROM node:18-alpine +WORKDIR /app +COPY --from=builder /app/build build/ +COPY --from=builder /app/node_modules node_modules/ +COPY package.json . +EXPOSE 3000 +ENV NODE_ENV=production +CMD [ "node", "build" ] \ No newline at end of file diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index a20b5654d..80d8b1372 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -12,7 +12,8 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", - "@sveltejs/kit": "^2.5.0", + "@sveltejs/adapter-node": "^5.0.1", + "@sveltejs/kit": "^2.5.10", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.0.0", @@ -500,6 +501,160 @@ "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", "dev": true }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", + "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -720,18 +875,33 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.0.1.tgz", + "integrity": "sha512-eYdmxdUWMW+dad1JfMsWBPY2vjXz9eE+52A2AQnXPScPJlIxIVk5mmbaEEzrZivLfO2wEcLTZ5vdC03W69x+iA==", + "dev": true, + "dependencies": { + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, "node_modules/@sveltejs/kit": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.0.tgz", - "integrity": "sha512-1uyXvzC2Lu1FZa30T4y5jUAC21R309ZMRG0TPt+PPPbNUoDpy8zSmSNVWYaBWxYDqLGQ5oPNWvjvvF2IjJ1jmA==", + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.10.tgz", + "integrity": "sha512-OqoyTmFG2cYmCFAdBfW+Qxbg8m23H4dv6KqwEt7ofr/ROcfcIl3Z/VT56L22H9f0uNZyr+9Bs1eh2gedOCK9kA==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", - "devalue": "^4.3.2", + "devalue": "^5.0.0", "esm-env": "^1.0.0", - "import-meta-resolve": "^4.0.0", + "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", @@ -811,6 +981,12 @@ "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", "dev": true }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -918,6 +1094,18 @@ "node": ">=8.0.0" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -979,6 +1167,12 @@ "node": ">=0.4.0" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1045,7 +1239,6 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1069,9 +1262,9 @@ } }, "node_modules/devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", + "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", "dev": true }, "node_modules/es6-promise": { @@ -1190,6 +1383,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1241,6 +1443,18 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -1258,9 +1472,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true, "funding": { "type": "github", @@ -1296,6 +1510,33 @@ "node": ">=8" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1317,6 +1558,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1526,6 +1773,12 @@ "node": ">=0.10.0" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -1648,6 +1901,23 @@ "node": ">=8.10.0" } }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1845,6 +2115,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/svelte": { "version": "4.2.18", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index dd087397e..d36c7b769 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -13,7 +13,8 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", - "@sveltejs/kit": "^2.5.0", + "@sveltejs/adapter-node": "^5.0.1", + "@sveltejs/kit": "^2.5.10", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.0.0", diff --git a/tests/app/rp/svelte.config.js b/tests/app/rp/svelte.config.js index 2b35fe1be..1023568ae 100644 --- a/tests/app/rp/svelte.config.js +++ b/tests/app/rp/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ @@ -8,9 +8,7 @@ const config = { preprocess: vitePreprocess(), kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. + // build to run in containerized node.js environment adapter: adapter() } }; From 9146e2bc73943ec4f5f596c22d5f159bad3b78e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:17:19 -0400 Subject: [PATCH 009/115] [pre-commit.ci] pre-commit autoupdate (#1433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 7.0.0 → 7.1.0](https://github.com/PyCQA/flake8/compare/7.0.0...7.1.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 235de3f1a..0d6e67899 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: isort exclude: ^(oauth2_provider/migrations/|tests/migrations/) - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 exclude: ^(oauth2_provider/migrations/|tests/migrations/) From 4212987d315a69fc19a6d7550191f078218ed13f Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 20 Jun 2024 21:03:14 +0200 Subject: [PATCH 010/115] Correct rst syntax in installation section (#1434) --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index cbeedf1b4..39a2613f3 100644 --- a/README.rst +++ b/README.rst @@ -54,7 +54,7 @@ Install with pip:: pip install django-oauth-toolkit -Add `oauth2_provider` to your `INSTALLED_APPS` +Add ``oauth2_provider`` to your ``INSTALLED_APPS`` .. code-block:: python @@ -64,8 +64,8 @@ Add `oauth2_provider` to your `INSTALLED_APPS` ) -If you need an OAuth2 provider you'll want to add the following to your urls.py. -Notice that `oauth2_provider` namespace is mandatory. +If you need an OAuth2 provider you'll want to add the following to your ``urls.py``. +Notice that ``oauth2_provider`` namespace is mandatory. .. code-block:: python From 924310b8cdd1192bf86cae20baf5225b0cea1c6f Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 20 Jun 2024 23:45:18 +0200 Subject: [PATCH 011/115] Drop re_path from installation guide (#1435) --- docs/install.rst | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 7186a94c0..ffddc151e 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -26,17 +26,6 @@ If you need an OAuth2 provider you'll want to add the following to your :file:`u path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), ] -Or using ``re_path()`` - -.. code-block:: python - - from django.urls import include, re_path - - urlpatterns = [ - ... - re_path(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), - ] - Sync your database ------------------ @@ -45,4 +34,3 @@ Sync your database python manage.py migrate oauth2_provider Next step is :doc:`getting started ` or :doc:`first tutorial `. - From 9cb93ee37db6f0d020f29e14d6e6a42c47d00e76 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Fri, 21 Jun 2024 18:57:27 +0200 Subject: [PATCH 012/115] Simplify how urlpatterns are loaded (#1436) --- AUTHORS | 1 + README.rst | 5 +++-- docs/getting_started.rst | 3 ++- docs/install.rst | 3 ++- docs/rest-framework/getting_started.rst | 3 ++- docs/tutorial/tutorial_01.rst | 3 ++- tests/urls.py | 4 +++- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 15eec14f9..357abc2fa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -62,6 +62,7 @@ Hiroki Kiyohara Hossein Shakiba Islam Kamel Ivan Lukyanets +Jaap Roes Jadiel Teófilo Jens Timmerman Jerome Leclanche diff --git a/README.rst b/README.rst index 39a2613f3..1935c49b9 100644 --- a/README.rst +++ b/README.rst @@ -65,13 +65,14 @@ Add ``oauth2_provider`` to your ``INSTALLED_APPS`` If you need an OAuth2 provider you'll want to add the following to your ``urls.py``. -Notice that ``oauth2_provider`` namespace is mandatory. .. code-block:: python + from oauth2_provider import urls as oauth2_urls + urlpatterns = [ ... - path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), + path('o/', include(oauth2_urls)), ] Changelog diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 80ff9ed71..e95618723 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -191,10 +191,11 @@ Include ``oauth2_provider.urls`` to :file:`iam/urls.py` as follows: from django.contrib import admin from django.urls import include, path + from oauth2_provider import urls as oauth2_urls urlpatterns = [ path('admin/', admin.site.urls), - path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), + path('o/', include(oauth2_urls)), ] This will make available endpoints to authorize, generate token and create OAuth applications. diff --git a/docs/install.rst b/docs/install.rst index ffddc151e..cfa219ecd 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -20,10 +20,11 @@ If you need an OAuth2 provider you'll want to add the following to your :file:`u .. code-block:: python from django.urls import include, path + from oauth2_provider import urls as oauth2_urls urlpatterns = [ ... - path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), + path('o/', include(oauth2_urls), ] Sync your database diff --git a/docs/rest-framework/getting_started.rst b/docs/rest-framework/getting_started.rst index 4e6b037b0..8e019c44e 100644 --- a/docs/rest-framework/getting_started.rst +++ b/docs/rest-framework/getting_started.rst @@ -51,6 +51,7 @@ Here's our project's root :file:`urls.py` module: from rest_framework import generics, permissions, serializers + from oauth2_provider import urls as oauth2_urls from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope # first we define the serializers @@ -84,7 +85,7 @@ Here's our project's root :file:`urls.py` module: # Setup the URLs and include login URLs for the browsable API. urlpatterns = [ path('admin/', admin.site.urls), - path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), + path('o/', include(oauth2_urls)), path('users/', UserList.as_view()), path('users//', UserDetails.as_view()), path('groups/', GroupList.as_view()), diff --git a/docs/tutorial/tutorial_01.rst b/docs/tutorial/tutorial_01.rst index efd1265f7..0d0e6b45c 100644 --- a/docs/tutorial/tutorial_01.rst +++ b/docs/tutorial/tutorial_01.rst @@ -34,10 +34,11 @@ Include the Django OAuth Toolkit urls in your `urls.py`, choosing the urlspace y .. code-block:: python from django.urls import path, include + from oauth2_provider import urls as oauth2_urls urlpatterns = [ path("admin", admin.site.urls), - path("o/", include('oauth2_provider.urls', namespace='oauth2_provider')), + path("o/", include(oauth2_urls)), # ... ] diff --git a/tests/urls.py b/tests/urls.py index 0661a9336..6f8f56832 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,11 +1,13 @@ from django.contrib import admin from django.urls import include, path +from oauth2_provider import urls as oauth2_urls + admin.autodiscover() urlpatterns = [ - path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")), + path("o/", include(oauth2_urls)), path("admin/", admin.site.urls), ] From 102c85141ec44549e17080c676292e79e5eb46cc Mon Sep 17 00:00:00 2001 From: Joni Bekenstein Date: Mon, 8 Jul 2024 12:22:24 -0300 Subject: [PATCH 013/115] Add missing closing ) (#1437) --- docs/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.rst b/docs/install.rst index cfa219ecd..3d46c507d 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -24,7 +24,7 @@ If you need an OAuth2 provider you'll want to add the following to your :file:`u urlpatterns = [ ... - path('o/', include(oauth2_urls), + path('o/', include(oauth2_urls)), ] Sync your database From ba752975ec092dd55eed3d4dd7d6c10a3cc85f4a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:06:02 -0400 Subject: [PATCH 014/115] [pre-commit.ci] pre-commit autoupdate (#1448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0) - [github.com/PyCQA/flake8: 7.1.0 → 7.1.1](https://github.com/PyCQA/flake8/compare/7.1.0...7.1.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d6e67899..8a2e65601 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black exclude: ^(oauth2_provider/migrations/|tests/migrations/) @@ -21,7 +21,7 @@ repos: - id: isort exclude: ^(oauth2_provider/migrations/|tests/migrations/) - repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 exclude: ^(oauth2_provider/migrations/|tests/migrations/) From 9c18de21f979bd0ddd0b5a429b79e49340d494d8 Mon Sep 17 00:00:00 2001 From: Madison Swain-Bowden Date: Tue, 13 Aug 2024 06:16:33 -0700 Subject: [PATCH 015/115] Handle invalid hex values in query strings in DRF extension (#1444) * Handle invalid hex values in query strings in DRF extension --------- Co-authored-by: Alan Crosswell --- AUTHORS | 1 + CHANGELOG.md | 1 + .../contrib/rest_framework/authentication.py | 15 ++++++++++++--- tests/test_rest_framework.py | 6 ++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 357abc2fa..17447b108 100644 --- a/AUTHORS +++ b/AUTHORS @@ -83,6 +83,7 @@ Kristian Rune Larsen Lazaros Toumanidis Ludwig Hähne Łukasz Skarżyński +Madison Swain-Bowden Marcus Sonestedt Matias Seniquiel Michael Howitz diff --git a/CHANGELOG.md b/CHANGELOG.md index 362fd74b3..826ae43bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 ### Fixed +* #1443 Query strings with invalid hex values now raise a SuspiciousOperation exception (in DRF extension) instead of raising a 500 ValueError: Invalid hex encoding in query string. ### Security ## [2.4.0] - 2024-05-13 diff --git a/oauth2_provider/contrib/rest_framework/authentication.py b/oauth2_provider/contrib/rest_framework/authentication.py index 53087f756..afa75d845 100644 --- a/oauth2_provider/contrib/rest_framework/authentication.py +++ b/oauth2_provider/contrib/rest_framework/authentication.py @@ -1,5 +1,6 @@ from collections import OrderedDict +from django.core.exceptions import SuspiciousOperation from rest_framework.authentication import BaseAuthentication from ...oauth2_backends import get_oauthlib_core @@ -23,10 +24,18 @@ def authenticate(self, request): Returns two-tuple of (user, token) if authentication succeeds, or None otherwise. """ + if request is None: + return None oauthlib_core = get_oauthlib_core() - valid, r = oauthlib_core.verify_request(request, scopes=[]) - if valid: - return r.user, r.access_token + try: + valid, r = oauthlib_core.verify_request(request, scopes=[]) + except ValueError as error: + if str(error) == "Invalid hex encoding in query string.": + raise SuspiciousOperation(error) + raise + else: + if valid: + return r.user, r.access_token request.oauth2_error = getattr(r, "oauth2_error", {}) return None diff --git a/tests/test_rest_framework.py b/tests/test_rest_framework.py index 0061f8d3a..632c62e26 100644 --- a/tests/test_rest_framework.py +++ b/tests/test_rest_framework.py @@ -415,3 +415,9 @@ def test_authentication_none(self): auth = self._create_authorization_header(self.access_token.token) response = self.client.get("/oauth2-authentication-none/", HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) + + def test_invalid_hex_string_in_query(self): + auth = self._create_authorization_header(self.access_token.token) + response = self.client.get("/oauth2-test/?q=73%%20of%20Arkansans", HTTP_AUTHORIZATION=auth) + # Should respond with a 400 rather than raise a ValueError + self.assertEqual(response.status_code, 400) From 51d9798a8baf03609a5cdc868e48f07a87f259da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wegener?= Date: Tue, 13 Aug 2024 16:02:06 +0200 Subject: [PATCH 016/115] Refresh Token Reuse Protection (#1452) * Implement REFRESH_TOKEN_REUSE_PROTECTION (#1404) According to https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#name-recommendations, the authorization server needs a way to determine which refresh tokens belong to the same session, so it is able to figure out which tokens to revoke. Therefore, this commit introduces a "token_family" field to the RefreshToken table. Whenever a revoked refresh token is reused, the auth server uses the token family to revoke all related tokens. --- AUTHORS | 1 + CHANGELOG.md | 1 + docs/settings.rst | 12 ++ .../0011_refreshtoken_token_family.py | 19 ++++ oauth2_provider/models.py | 1 + oauth2_provider/oauth2_validators.py | 36 ++++-- oauth2_provider/settings.py | 1 + .../0006_basetestapplication_token_family.py | 20 ++++ tests/test_authorization_code.py | 105 ++++++++++++++++++ 9 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 oauth2_provider/migrations/0011_refreshtoken_token_family.py create mode 100644 tests/migrations/0006_basetestapplication_token_family.py diff --git a/AUTHORS b/AUTHORS index 17447b108..ce5ec2ec8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -105,6 +105,7 @@ Shaheed Haque Shaun Stanworth Silvano Cerza Sora Yanai +Sören Wegener Spencer Carroll Stéphane Raimbault Tom Evans diff --git a/CHANGELOG.md b/CHANGELOG.md index 826ae43bc..ed1ec2e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added +* #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION` ### Changed ### Deprecated ### Removed diff --git a/docs/settings.rst b/docs/settings.rst index 901fe8575..4ebe6cc47 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -185,6 +185,18 @@ The import string of the class (model) representing your refresh tokens. Overwri this value if you wrote your own implementation (subclass of ``oauth2_provider.models.RefreshToken``). +REFRESH_TOKEN_REUSE_PROTECTION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When this is set to ``True`` (default ``False``), and ``ROTATE_REFRESH_TOKEN`` is used, the server will check +if a previously, already revoked refresh token is used a second time. If it detects a reuse, it will automatically +revoke all related refresh tokens. +A reused refresh token indicates a breach. Since the server can't determine which request came from the legitimate +user and which from an attacker, it will end the session for both. The user is required to perform a new login. + +Can be used in combination with ``REFRESH_TOKEN_GRACE_PERIOD_SECONDS`` + +More details at https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#name-recommendations + ROTATE_REFRESH_TOKEN ~~~~~~~~~~~~~~~~~~~~ When is set to ``True`` (default) a new refresh token is issued to the client when the client refreshes an access token. diff --git a/oauth2_provider/migrations/0011_refreshtoken_token_family.py b/oauth2_provider/migrations/0011_refreshtoken_token_family.py new file mode 100644 index 000000000..94fb4e171 --- /dev/null +++ b/oauth2_provider/migrations/0011_refreshtoken_token_family.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2 on 2024-08-09 16:40 + +from django.db import migrations, models +from oauth2_provider.settings import oauth2_settings + +class Migration(migrations.Migration): + + dependencies = [ + ('oauth2_provider', '0010_application_allowed_origins'), + migrations.swappable_dependency(oauth2_settings.REFRESH_TOKEN_MODEL) + ] + + operations = [ + migrations.AddField( + model_name='refreshtoken', + name='token_family', + field=models.UUIDField(blank=True, editable=False, null=True), + ), + ] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 661bd7dfc..9895528de 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -490,6 +490,7 @@ class AbstractRefreshToken(models.Model): null=True, related_name="refresh_token", ) + token_family = models.UUIDField(null=True, blank=True, editable=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 47d65e851..d1cb8b9b6 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -15,7 +15,6 @@ from django.contrib.auth.hashers import check_password, identify_hasher from django.core.exceptions import ObjectDoesNotExist from django.db import transaction -from django.db.models import Q from django.http import HttpRequest from django.utils import dateformat, timezone from django.utils.crypto import constant_time_compare @@ -644,7 +643,9 @@ def save_bearer_token(self, token, request, *args, **kwargs): source_refresh_token=refresh_token_instance, ) - self._create_refresh_token(request, refresh_token_code, access_token) + self._create_refresh_token( + request, refresh_token_code, access_token, refresh_token_instance + ) else: # make sure that the token data we're returning matches # the existing token @@ -688,9 +689,17 @@ def _create_authorization_code(self, request, code, expires=None): claims=json.dumps(request.claims or {}), ) - def _create_refresh_token(self, request, refresh_token_code, access_token): + def _create_refresh_token(self, request, refresh_token_code, access_token, previous_refresh_token): + if previous_refresh_token: + token_family = previous_refresh_token.token_family + else: + token_family = uuid.uuid4() return RefreshToken.objects.create( - user=request.user, token=refresh_token_code, application=request.client, access_token=access_token + user=request.user, + token=refresh_token_code, + application=request.client, + access_token=access_token, + token_family=token_family, ) def revoke_token(self, token, token_type_hint, request, *args, **kwargs): @@ -752,22 +761,25 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs Also attach User instance to the request object """ - null_or_recent = Q(revoked__isnull=True) | Q( - revoked__gt=timezone.now() - timedelta(seconds=oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS) - ) - rt = ( - RefreshToken.objects.filter(null_or_recent, token=refresh_token) - .select_related("access_token") - .first() - ) + rt = RefreshToken.objects.filter(token=refresh_token).select_related("access_token").first() if not rt: return False + if rt.revoked is not None and rt.revoked <= timezone.now() - timedelta( + seconds=oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS + ): + if oauth2_settings.REFRESH_TOKEN_REUSE_PROTECTION and rt.token_family: + rt_token_family = RefreshToken.objects.filter(token_family=rt.token_family) + for related_rt in rt_token_family.all(): + related_rt.revoke() + return False + request.user = rt.user request.refresh_token = rt.token # Temporary store RefreshToken instance to be reused by get_original_scopes and save_bearer_token. request.refresh_token_instance = rt + return rt.application == client @transaction.atomic diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index 950ab5643..329a1b354 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -54,6 +54,7 @@ "ID_TOKEN_EXPIRE_SECONDS": 36000, "REFRESH_TOKEN_EXPIRE_SECONDS": None, "REFRESH_TOKEN_GRACE_PERIOD_SECONDS": 0, + "REFRESH_TOKEN_REUSE_PROTECTION": False, "ROTATE_REFRESH_TOKEN": True, "ERROR_RESPONSE_WITH_SCOPES": False, "APPLICATION_MODEL": APPLICATION_MODEL, diff --git a/tests/migrations/0006_basetestapplication_token_family.py b/tests/migrations/0006_basetestapplication_token_family.py new file mode 100644 index 000000000..6b065a242 --- /dev/null +++ b/tests/migrations/0006_basetestapplication_token_family.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2 on 2024-08-09 16:40 + +from django.db import migrations, models +from oauth2_provider.settings import oauth2_settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('tests', '0005_basetestapplication_allowed_origins_and_more'), + migrations.swappable_dependency(oauth2_settings.REFRESH_TOKEN_MODEL) + ] + + operations = [ + migrations.AddField( + model_name='samplerefreshtoken', + name='token_family', + field=models.UUIDField(blank=True, editable=False, null=True), + ), + ] diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index b77f4f9ba..ae6e7e76e 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -985,6 +985,54 @@ def test_refresh_fail_repeating_requests(self): response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) self.assertEqual(response.status_code, 400) + def test_refresh_repeating_requests_revokes_old_token(self): + """ + If a refresh token is reused, the server should invalidate *all* access tokens that have a relation + to the re-used token. This forces a malicious actor to be logged out. + The server can't determine whether the first or the second client was legitimate, so it needs to + revoke both. + See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#name-recommendations + """ + self.oauth2_settings.REFRESH_TOKEN_REUSE_PROTECTION = True + self.client.login(username="test_user", password="123456") + authorization_code = self.get_auth() + + token_request_data = { + "grant_type": "authorization_code", + "code": authorization_code, + "redirect_uri": "http://example.org", + } + auth_headers = get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + content = json.loads(response.content.decode("utf-8")) + self.assertTrue("refresh_token" in content) + + token_request_data = { + "grant_type": "refresh_token", + "refresh_token": content["refresh_token"], + "scope": content["scope"], + } + # First response works as usual + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 200) + new_tokens = json.loads(response.content.decode("utf-8")) + + # Second request fails + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 400) + + # Previously returned tokens are now invalid as well + new_token_request_data = { + "grant_type": "refresh_token", + "refresh_token": new_tokens["refresh_token"], + "scope": new_tokens["scope"], + } + response = self.client.post( + reverse("oauth2_provider:token"), data=new_token_request_data, **auth_headers + ) + self.assertEqual(response.status_code, 400) + def test_refresh_repeating_requests(self): """ Trying to refresh an access token with the same refresh token more than @@ -1024,6 +1072,63 @@ def test_refresh_repeating_requests(self): response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) self.assertEqual(response.status_code, 400) + def test_refresh_repeating_requests_grace_period_with_reuse_protection(self): + """ + Trying to refresh an access token with the same refresh token more than + once succeeds. Should work within the grace period, but should revoke previous tokens + """ + self.oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS = 120 + self.oauth2_settings.REFRESH_TOKEN_REUSE_PROTECTION = True + self.client.login(username="test_user", password="123456") + authorization_code = self.get_auth() + + token_request_data = { + "grant_type": "authorization_code", + "code": authorization_code, + "redirect_uri": "http://example.org", + } + auth_headers = get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + content = json.loads(response.content.decode("utf-8")) + self.assertTrue("refresh_token" in content) + + refresh_token_1 = content["refresh_token"] + token_request_data = { + "grant_type": "refresh_token", + "refresh_token": refresh_token_1, + "scope": content["scope"], + } + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 200) + refresh_token_2 = json.loads(response.content.decode("utf-8"))["refresh_token"] + + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 200) + refresh_token_3 = json.loads(response.content.decode("utf-8"))["refresh_token"] + + self.assertEqual(refresh_token_2, refresh_token_3) + + # Let the first refresh token expire + rt = RefreshToken.objects.get(token=refresh_token_1) + rt.revoked = timezone.now() - datetime.timedelta(minutes=10) + rt.save() + + # Using the expired token fails + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 400) + + # Because we used the expired token, the recently issued token is also revoked + new_token_request_data = { + "grant_type": "refresh_token", + "refresh_token": refresh_token_2, + "scope": content["scope"], + } + response = self.client.post( + reverse("oauth2_provider:token"), data=new_token_request_data, **auth_headers + ) + self.assertEqual(response.status_code, 400) + def test_refresh_repeating_requests_non_rotating_tokens(self): """ Try refreshing an access token with the same refresh token more than once when not rotating tokens. From dc3d8ff07f66d1e95f934f89d5d530e386f67c65 Mon Sep 17 00:00:00 2001 From: fazeelghafoor <33656455+fazeelghafoor@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:01:17 -0400 Subject: [PATCH 017/115] change token to TextField in AbstractAccessToken model (#1447) * change token field to TextField in AbstractAccessToken model - add TokenChecksumField field - update middleware, validators, and views to use token checksums for token retrieval and validation - modified test migrations to include token_checksum field in "sampleaccesstoken" model - add test for token checksum field --------- Co-authored-by: Alan Crosswell Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan Crosswell --- AUTHORS | 1 + CHANGELOG.md | 4 +++ oauth2_provider/middleware.py | 4 ++- .../migrations/0012_add_token_checksum.py | 26 +++++++++++++++++++ oauth2_provider/models.py | 15 +++++++++-- oauth2_provider/oauth2_validators.py | 8 +++++- oauth2_provider/views/base.py | 4 ++- oauth2_provider/views/introspect.py | 6 ++++- tests/migrations/0002_swapped_models.py | 12 ++++++--- tests/test_models.py | 13 ++++++++++ 10 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 oauth2_provider/migrations/0012_add_token_checksum.py diff --git a/AUTHORS b/AUTHORS index ce5ec2ec8..64986ca08 100644 --- a/AUTHORS +++ b/AUTHORS @@ -51,6 +51,7 @@ Dylan Tack Eduardo Oliveira Egor Poderiagin Emanuele Palazzetti +Fazeel Ghafoor Federico Dolce Florian Demmer Frederico Vieira diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1ec2e89..99be61e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added +* Add migration to include `token_checksum` field in AbstractAccessToken model. * #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION` ### Changed +* Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims + +* Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. ### Deprecated ### Removed * #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index de1689894..65c9cf03c 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,3 +1,4 @@ +import hashlib import logging from django.contrib.auth import authenticate @@ -55,7 +56,8 @@ def __call__(self, request): tokenstring = authheader.split()[1] AccessToken = get_access_token_model() try: - token = AccessToken.objects.get(token=tokenstring) + token_checksum = hashlib.sha256(tokenstring.encode("utf-8")).hexdigest() + token = AccessToken.objects.get(token_checksum=token_checksum) request.access_token = token except AccessToken.DoesNotExist as e: log.exception(e) diff --git a/oauth2_provider/migrations/0012_add_token_checksum.py b/oauth2_provider/migrations/0012_add_token_checksum.py new file mode 100644 index 000000000..7f62955e3 --- /dev/null +++ b/oauth2_provider/migrations/0012_add_token_checksum.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.7 on 2024-07-29 23:13 + +import oauth2_provider.models +from django.db import migrations, models +from oauth2_provider.settings import oauth2_settings + +class Migration(migrations.Migration): + dependencies = [ + ("oauth2_provider", "0011_refreshtoken_token_family"), + migrations.swappable_dependency(oauth2_settings.ACCESS_TOKEN_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="accesstoken", + name="token_checksum", + field=oauth2_provider.models.TokenChecksumField( + blank=True, db_index=True, max_length=64, unique=True + ), + ), + migrations.AlterField( + model_name="accesstoken", + name="token", + field=models.TextField(), + ), + ] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 9895528de..68d30f332 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -1,3 +1,4 @@ +import hashlib import logging import time import uuid @@ -44,6 +45,14 @@ def pre_save(self, model_instance, add): return super().pre_save(model_instance, add) +class TokenChecksumField(models.CharField): + def pre_save(self, model_instance, add): + token = getattr(model_instance, "token") + checksum = hashlib.sha256(token.encode("utf-8")).hexdigest() + setattr(model_instance, self.attname, checksum) + return super().pre_save(model_instance, add) + + class AbstractApplication(models.Model): """ An Application instance represents a Client on the Authorization server. @@ -379,8 +388,10 @@ class AbstractAccessToken(models.Model): null=True, related_name="refreshed_access_token", ) - token = models.CharField( - max_length=255, + token = models.TextField() + token_checksum = TokenChecksumField( + max_length=64, + blank=True, unique=True, db_index=True, ) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index d1cb8b9b6..4ca1479d2 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -1,5 +1,6 @@ import base64 import binascii +import hashlib import http.client import inspect import json @@ -461,7 +462,12 @@ def validate_bearer_token(self, token, scopes, request): return False def _load_access_token(self, token): - return AccessToken.objects.select_related("application", "user").filter(token=token).first() + token_checksum = hashlib.sha256(token.encode("utf-8")).hexdigest() + return ( + AccessToken.objects.select_related("application", "user") + .filter(token_checksum=token_checksum) + .first() + ) def validate_code(self, client_id, code, client, request, *args, **kwargs): try: diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index cad36c757..52cb151d5 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -1,3 +1,4 @@ +import hashlib import json import logging from urllib.parse import parse_qsl, urlencode, urlparse @@ -289,7 +290,8 @@ def post(self, request, *args, **kwargs): if status == 200: access_token = json.loads(body).get("access_token") if access_token is not None: - token = get_access_token_model().objects.get(token=access_token) + token_checksum = hashlib.sha256(access_token.encode("utf-8")).hexdigest() + token = get_access_token_model().objects.get(token_checksum=token_checksum) app_authorized.send(sender=self, request=request, token=token) response = HttpResponse(content=body, status=status) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 04ca92a38..05a77909f 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -1,4 +1,5 @@ import calendar +import hashlib from django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponse @@ -24,8 +25,11 @@ class IntrospectTokenView(ClientProtectedScopedResourceView): @staticmethod def get_token_response(token_value=None): try: + token_checksum = hashlib.sha256(token_value.encode("utf-8")).hexdigest() token = ( - get_access_token_model().objects.select_related("user", "application").get(token=token_value) + get_access_token_model() + .objects.select_related("user", "application") + .get(token_checksum=token_checksum) ) except ObjectDoesNotExist: return JsonResponse({"active": False}, status=200) diff --git a/tests/migrations/0002_swapped_models.py b/tests/migrations/0002_swapped_models.py index 412f19927..e168a053d 100644 --- a/tests/migrations/0002_swapped_models.py +++ b/tests/migrations/0002_swapped_models.py @@ -118,10 +118,14 @@ class Migration(migrations.Migration): field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='s_refreshed_access_token', to=settings.OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL), ), migrations.AddField( - model_name='sampleaccesstoken', - name='token', - field=models.CharField(max_length=255, unique=True), - preserve_default=False, + model_name="sampleaccesstoken", + name="token", + field=models.TextField(), + ), + migrations.AddField( + model_name="sampleaccesstoken", + name="token_checksum", + field=models.CharField(max_length=64, unique=True, db_index=True), ), migrations.AddField( model_name='sampleaccesstoken', diff --git a/tests/test_models.py b/tests/test_models.py index 586bef124..24e4ceafe 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,3 +1,5 @@ +import hashlib +import secrets from datetime import timedelta import pytest @@ -310,6 +312,17 @@ def test_expires_can_be_none(self): self.assertIsNone(access_token.expires) self.assertTrue(access_token.is_expired()) + def test_token_checksum_field(self): + token = secrets.token_urlsafe(32) + access_token = AccessToken.objects.create( + user=self.user, + token=token, + expires=timezone.now() + timedelta(hours=1), + ) + expected_checksum = hashlib.sha256(token.encode()).hexdigest() + + self.assertEqual(access_token.token_checksum, expected_checksum) + class TestRefreshTokenModel(BaseTestModels): def test_str(self): From 2a5845d398fd2112a6ac24fbe67b330123338fb0 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Thu, 15 Aug 2024 01:45:34 +0800 Subject: [PATCH 018/115] use path in urls (#1456) replaces re_path with simple, straightforward path, removing unnecessary regex. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- oauth2_provider/urls.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/oauth2_provider/urls.py b/oauth2_provider/urls.py index 18972612c..155822f45 100644 --- a/oauth2_provider/urls.py +++ b/oauth2_provider/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views @@ -7,24 +7,24 @@ base_urlpatterns = [ - re_path(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"), - re_path(r"^token/$", views.TokenView.as_view(), name="token"), - re_path(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"), - re_path(r"^introspect/$", views.IntrospectTokenView.as_view(), name="introspect"), + path("authorize/", views.AuthorizationView.as_view(), name="authorize"), + path("token/", views.TokenView.as_view(), name="token"), + path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"), + path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"), ] management_urlpatterns = [ # Application management views - re_path(r"^applications/$", views.ApplicationList.as_view(), name="list"), - re_path(r"^applications/register/$", views.ApplicationRegistration.as_view(), name="register"), - re_path(r"^applications/(?P[\w-]+)/$", views.ApplicationDetail.as_view(), name="detail"), - re_path(r"^applications/(?P[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"), - re_path(r"^applications/(?P[\w-]+)/update/$", views.ApplicationUpdate.as_view(), name="update"), + path("applications/", views.ApplicationList.as_view(), name="list"), + path("applications/register/", views.ApplicationRegistration.as_view(), name="register"), + path("applications//", views.ApplicationDetail.as_view(), name="detail"), + path("applications//delete/", views.ApplicationDelete.as_view(), name="delete"), + path("applications//update/", views.ApplicationUpdate.as_view(), name="update"), # Token management views - re_path(r"^authorized_tokens/$", views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), - re_path( - r"^authorized_tokens/(?P[\w-]+)/delete/$", + path("authorized_tokens/", views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), + path( + "authorized_tokens//delete/", views.AuthorizedTokenDeleteView.as_view(), name="authorized-token-delete", ), @@ -40,9 +40,9 @@ views.ConnectDiscoveryInfoView.as_view(), name="oidc-connect-discovery-info", ), - re_path(r"^\.well-known/jwks.json$", views.JwksInfoView.as_view(), name="jwks-info"), - re_path(r"^userinfo/$", views.UserInfoView.as_view(), name="user-info"), - re_path(r"^logout/$", views.RPInitiatedLogoutView.as_view(), name="rp-initiated-logout"), + path(".well-known/jwks.json", views.JwksInfoView.as_view(), name="jwks-info"), + path("userinfo/", views.UserInfoView.as_view(), name="user-info"), + path("logout/", views.RPInitiatedLogoutView.as_view(), name="rp-initiated-logout"), ] From 7e134136879e98b628101a9f6944fc4355d7e8d5 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Thu, 15 Aug 2024 21:03:06 +0800 Subject: [PATCH 019/115] drop support for Django versions below 4.2 (#1455) * drop support for Django below 4.2 --- .github/workflows/test.yml | 26 +++----------------------- CHANGELOG.md | 1 + README.rst | 2 +- docs/index.rst | 2 +- setup.cfg | 6 ++---- tox.ini | 16 ++++------------ 6 files changed, 12 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 627aacf97..552e21281 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,39 +10,19 @@ jobs: fail-fast: false matrix: python-version: - - '3.8' - - '3.9' - '3.10' - '3.11' - '3.12' django-version: - - '3.2' - - '4.0' - - '4.1' - '4.2' - '5.0' + - '5.1' - 'main' - exclude: + include: # https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django - - # < Python 3.10 is not supported by Django 5.0+ - - python-version: '3.8' - django-version: '5.0' - - python-version: '3.9' - django-version: '5.0' - python-version: '3.8' - django-version: 'main' + django-version: '4.2' - python-version: '3.9' - django-version: 'main' - - # Python 3.12 is not supported by Django < 5.0 - - python-version: '3.12' - django-version: '3.2' - - python-version: '3.12' - django-version: '4.0' - - python-version: '3.12' - django-version: '4.1' - - python-version: '3.12' django-version: '4.2' steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 99be61e48..e72d9d550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated ### Removed * #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 +* Remove support for Django versions below 4.2 ### Fixed * #1443 Query strings with invalid hex values now raise a SuspiciousOperation exception (in DRF extension) instead of raising a 500 ValueError: Invalid hex encoding in query string. diff --git a/README.rst b/README.rst index 1935c49b9..ff94b8c62 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ Requirements ------------ * Python 3.8+ -* Django 3.2, 4.0 (4.0.1+ due to a regression), 4.1, 4.2, or 5.0 +* Django 4.2, 5.0 or 5.1 * oauthlib 3.1+ Installation diff --git a/docs/index.rst b/docs/index.rst index e0df769cd..915a4f6b8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,7 +22,7 @@ Requirements ------------ * Python 3.8+ -* Django 3.2, 4.0 (4.0.1+ due to a regression), 4.1, 4.2, or 5.0 +* Django 4.2, 5.0 or 5.1 * oauthlib 3.1+ Index diff --git a/setup.cfg b/setup.cfg index d015d1238..4f25adf1d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,11 +12,9 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django - Framework :: Django :: 3.2 - Framework :: Django :: 4.0 - Framework :: Django :: 4.1 Framework :: Django :: 4.2 Framework :: Django :: 5.0 + Framework :: Django :: 5.1 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -36,7 +34,7 @@ python_requires = >=3.8 # jwcrypto has a direct dependency on six, but does not list it yet in a release # Previously, cryptography also depended on six, so this was unnoticed install_requires = - django >= 3.2, != 4.0.0 + django >= 4.2 requests >= 2.13.0 oauthlib >= 3.1.0 jwcrypto >= 0.8.0 diff --git a/tox.ini b/tox.ini index ba97bd113..56d249661 100644 --- a/tox.ini +++ b/tox.ini @@ -5,11 +5,9 @@ envlist = migrate_swapped, docs, sphinxlint, - py{38,39,310}-dj32, - py{38,39,310}-dj40, - py{38,39,310,311}-dj41, py{38,39,310,311,312}-dj42, py{310,311,312}-dj50, + py{310,311,312}-dj51, py{310,311,312}-djmain, [gh-actions] @@ -22,12 +20,9 @@ python = [gh-actions:env] DJANGO = - 2.2: dj22 - 3.2: dj32 - 4.0: dj40 - 4.1: dj41 4.2: dj42 5.0: dj50 + 5.1: dj51 main: djmain [pytest] @@ -50,12 +45,9 @@ setenv = PYTHONPATH = {toxinidir} PYTHONWARNINGS = all deps = - dj22: Django>=2.2,<3 - dj32: Django>=3.2,<3.3 - dj40: Django>=4.0.0,<4.1 - dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<4.3 - dj50: Django>=5.0b1,<5.1 + dj50: Django>=5.0,<5.1 + dj51: Django>=5.1,<5.2 djmain: https://github.com/django/django/archive/main.tar.gz djangorestframework oauthlib>=3.1.0 From 146e8bfdd12df1efc4250499fce64225a04560e0 Mon Sep 17 00:00:00 2001 From: Sayyid Hamid Mahdavi Date: Thu, 15 Aug 2024 17:11:25 +0330 Subject: [PATCH 020/115] models pk instead of models id (#1446) * use user.pk instead of user.id which allows for a custom model to have a different PK. --------- Co-authored-by: Alan Crosswell --- AUTHORS | 1 + CHANGELOG.md | 3 ++- oauth2_provider/admin.py | 2 +- oauth2_provider/models.py | 4 ++-- oauth2_provider/oauth2_validators.py | 8 ++++---- .../oauth2_provider/application_detail.html | 4 ++-- .../oauth2_provider/application_form.html | 4 ++-- .../oauth2_provider/application_list.html | 2 +- tests/test_token_revocation.py | 16 ++++++++-------- 9 files changed, 23 insertions(+), 21 deletions(-) diff --git a/AUTHORS b/AUTHORS index 64986ca08..584ecf59c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -104,6 +104,7 @@ Rustem Saiargaliev Sandro Rodrigues Shaheed Haque Shaun Stanworth +Sayyid Hamid Mahdavi Silvano Cerza Sora Yanai Sören Wegener diff --git a/CHANGELOG.md b/CHANGELOG.md index e72d9d550..7d213524c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION` ### Changed * Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims - * Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. +* #1446 use generic models pk instead of id. + ### Deprecated ### Removed * #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 diff --git a/oauth2_provider/admin.py b/oauth2_provider/admin.py index cefc75bb6..dd636184c 100644 --- a/oauth2_provider/admin.py +++ b/oauth2_provider/admin.py @@ -19,7 +19,7 @@ class ApplicationAdmin(admin.ModelAdmin): - list_display = ("id", "name", "user", "client_type", "authorization_grant_type") + list_display = ("pk", "name", "user", "client_type", "authorization_grant_type") list_filter = ("client_type", "authorization_grant_type", "skip_authorization") radio_fields = { "client_type": admin.HORIZONTAL, diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 68d30f332..f979eef1c 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -244,7 +244,7 @@ def clean(self): raise ValidationError(_("You cannot use HS256 with public grants or clients")) def get_absolute_url(self): - return reverse("oauth2_provider:detail", args=[str(self.id)]) + return reverse("oauth2_provider:detail", args=[str(self.pk)]) def get_allowed_schemes(self): """ @@ -520,7 +520,7 @@ def revoke(self): self = list(token)[0] try: - access_token_model.objects.get(id=self.access_token_id).revoke() + access_token_model.objects.get(pk=self.access_token_id).revoke() except access_token_model.DoesNotExist: pass self.access_token = None diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 4ca1479d2..78667fa0e 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -622,7 +622,7 @@ def save_bearer_token(self, token, request, *args, **kwargs): # from the db while acquiring a lock on it # We also put it in the "request cache" refresh_token_instance = RefreshToken.objects.select_for_update().get( - id=refresh_token_instance.id + pk=refresh_token_instance.pk ) request.refresh_token_instance = refresh_token_instance @@ -756,7 +756,7 @@ def get_original_scopes(self, refresh_token, request, *args, **kwargs): rt = request.refresh_token_instance if not rt.access_token_id: try: - return AccessToken.objects.get(source_refresh_token_id=rt.id).scope + return AccessToken.objects.get(source_refresh_token_id=rt.pk).scope except AccessToken.DoesNotExist: return [] return rt.access_token.scope @@ -810,9 +810,9 @@ def get_jwt_bearer_token(self, token, token_handler, request): def get_claim_dict(self, request): if self._get_additional_claims_is_request_agnostic(): - claims = {"sub": lambda r: str(r.user.id)} + claims = {"sub": lambda r: str(r.user.pk)} else: - claims = {"sub": str(request.user.id)} + claims = {"sub": str(request.user.pk)} # https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims if self._get_additional_claims_is_request_agnostic(): diff --git a/oauth2_provider/templates/oauth2_provider/application_detail.html b/oauth2_provider/templates/oauth2_provider/application_detail.html index 440518903..74b71ee74 100644 --- a/oauth2_provider/templates/oauth2_provider/application_detail.html +++ b/oauth2_provider/templates/oauth2_provider/application_detail.html @@ -49,8 +49,8 @@

{{ application.name }}

{% endblock content %} diff --git a/oauth2_provider/templates/oauth2_provider/application_form.html b/oauth2_provider/templates/oauth2_provider/application_form.html index dd8a644e8..7d8c07989 100644 --- a/oauth2_provider/templates/oauth2_provider/application_form.html +++ b/oauth2_provider/templates/oauth2_provider/application_form.html @@ -3,7 +3,7 @@ {% load i18n %} {% block content %}
-
+

{% block app-form-title %} {% trans "Edit application" %} {{ application.name }} @@ -31,7 +31,7 @@

- + {% trans "Go Back" %} diff --git a/oauth2_provider/templates/oauth2_provider/application_list.html b/oauth2_provider/templates/oauth2_provider/application_list.html index 807c050d3..509ccfc94 100644 --- a/oauth2_provider/templates/oauth2_provider/application_list.html +++ b/oauth2_provider/templates/oauth2_provider/application_list.html @@ -7,7 +7,7 @@

{% trans "Your applications" %}

{% if applications %} diff --git a/tests/test_token_revocation.py b/tests/test_token_revocation.py index 8655a5b3e..4883e850c 100644 --- a/tests/test_token_revocation.py +++ b/tests/test_token_revocation.py @@ -53,7 +53,7 @@ def test_revoke_access_token(self): response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b"") - self.assertFalse(AccessToken.objects.filter(id=tok.id).exists()) + self.assertFalse(AccessToken.objects.filter(pk=tok.pk).exists()) def test_revoke_access_token_public(self): public_app = Application( @@ -101,7 +101,7 @@ def test_revoke_access_token_with_hint(self): url = reverse("oauth2_provider:revoke-token") response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) - self.assertFalse(AccessToken.objects.filter(id=tok.id).exists()) + self.assertFalse(AccessToken.objects.filter(pk=tok.pk).exists()) def test_revoke_access_token_with_invalid_hint(self): tok = AccessToken.objects.create( @@ -123,7 +123,7 @@ def test_revoke_access_token_with_invalid_hint(self): url = reverse("oauth2_provider:revoke-token") response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) - self.assertFalse(AccessToken.objects.filter(id=tok.id).exists()) + self.assertFalse(AccessToken.objects.filter(pk=tok.pk).exists()) def test_revoke_refresh_token(self): tok = AccessToken.objects.create( @@ -146,9 +146,9 @@ def test_revoke_refresh_token(self): url = reverse("oauth2_provider:revoke-token") response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) - refresh_token = RefreshToken.objects.filter(id=rtok.id).first() + refresh_token = RefreshToken.objects.filter(pk=rtok.pk).first() self.assertIsNotNone(refresh_token.revoked) - self.assertFalse(AccessToken.objects.filter(id=rtok.access_token.id).exists()) + self.assertFalse(AccessToken.objects.filter(pk=rtok.access_token.pk).exists()) def test_revoke_refresh_token_with_revoked_access_token(self): tok = AccessToken.objects.create( @@ -172,8 +172,8 @@ def test_revoke_refresh_token_with_revoked_access_token(self): response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) - self.assertFalse(AccessToken.objects.filter(id=tok.id).exists()) - refresh_token = RefreshToken.objects.filter(id=rtok.id).first() + self.assertFalse(AccessToken.objects.filter(pk=tok.pk).exists()) + refresh_token = RefreshToken.objects.filter(pk=rtok.pk).first() self.assertIsNotNone(refresh_token.revoked) def test_revoke_token_with_wrong_hint(self): @@ -202,4 +202,4 @@ def test_revoke_token_with_wrong_hint(self): url = reverse("oauth2_provider:revoke-token") response = self.client.post(url, data=data) self.assertEqual(response.status_code, 200) - self.assertFalse(AccessToken.objects.filter(id=tok.id).exists()) + self.assertFalse(AccessToken.objects.filter(pk=tok.pk).exists()) From 1dcef1b30c1e575fb305946465c0841478aae5d5 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Thu, 15 Aug 2024 22:15:14 +0800 Subject: [PATCH 021/115] compat with LoginRequiredMiddleware middleware (#1454) * compat with LoginRequiredMiddleware and login_not_required --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 + oauth2_provider/compat.py | 11 +++++++++++ oauth2_provider/views/base.py | 5 +++++ oauth2_provider/views/introspect.py | 6 ++++-- oauth2_provider/views/oidc.py | 5 +++++ tests/conftest.py | 11 +++++++++++ tests/test_introspection_auth.py | 3 ++- tests/test_rest_framework.py | 1 + tox.ini | 1 + 9 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d213524c..738927c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added * Add migration to include `token_checksum` field in AbstractAccessToken model. +* Added compatibility with `LoginRequiredMiddleware` introduced in Django 5.1 * #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION` ### Changed * Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims diff --git a/oauth2_provider/compat.py b/oauth2_provider/compat.py index 0c83cb37a..846e32d0e 100644 --- a/oauth2_provider/compat.py +++ b/oauth2_provider/compat.py @@ -2,3 +2,14 @@ The `compat` module provides support for backwards compatibility with older versions of Django and Python. """ + +try: + # Django 5.1 introduced LoginRequiredMiddleware, and login_not_required decorator + from django.contrib.auth.decorators import login_not_required +except ImportError: + + def login_not_required(view_func): + return view_func + + +__all__ = ["login_not_required"] diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index 52cb151d5..d2644f35f 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -13,6 +13,7 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic import FormView, View +from ..compat import login_not_required from ..exceptions import OAuthToolkitError from ..forms import AllowForm from ..http import OAuth2ResponseRedirect @@ -26,6 +27,8 @@ log = logging.getLogger("oauth2_provider") +# login_not_required decorator to bypass LoginRequiredMiddleware +@method_decorator(login_not_required, name="dispatch") class BaseAuthorizationView(LoginRequiredMixin, OAuthLibMixin, View): """ Implements a generic endpoint to handle *Authorization Requests* as in :rfc:`4.1.1`. The view @@ -274,6 +277,7 @@ def handle_no_permission(self): @method_decorator(csrf_exempt, name="dispatch") +@method_decorator(login_not_required, name="dispatch") class TokenView(OAuthLibMixin, View): """ Implements an endpoint to provide access tokens @@ -301,6 +305,7 @@ def post(self, request, *args, **kwargs): @method_decorator(csrf_exempt, name="dispatch") +@method_decorator(login_not_required, name="dispatch") class RevokeTokenView(OAuthLibMixin, View): """ Implements an endpoint to revoke access or refresh tokens diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 05a77909f..5474c3a7e 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -6,11 +6,13 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt -from oauth2_provider.models import get_access_token_model -from oauth2_provider.views.generic import ClientProtectedScopedResourceView +from ..compat import login_not_required +from ..models import get_access_token_model +from ..views.generic import ClientProtectedScopedResourceView @method_decorator(csrf_exempt, name="dispatch") +@method_decorator(login_not_required, name="dispatch") class IntrospectTokenView(ClientProtectedScopedResourceView): """ Implements an endpoint for token introspection based diff --git a/oauth2_provider/views/oidc.py b/oauth2_provider/views/oidc.py index c9d10c25e..c746c30ce 100644 --- a/oauth2_provider/views/oidc.py +++ b/oauth2_provider/views/oidc.py @@ -14,6 +14,7 @@ from jwcrypto.jwt import JWTExpired from oauthlib.common import add_params_to_uri +from ..compat import login_not_required from ..exceptions import ( ClientIdMissmatch, InvalidIDTokenError, @@ -39,6 +40,7 @@ Application = get_application_model() +@method_decorator(login_not_required, name="dispatch") class ConnectDiscoveryInfoView(OIDCOnlyMixin, View): """ View used to show oidc provider configuration information per @@ -106,6 +108,7 @@ def get(self, request, *args, **kwargs): return response +@method_decorator(login_not_required, name="dispatch") class JwksInfoView(OIDCOnlyMixin, View): """ View used to show oidc json web key set document @@ -134,6 +137,7 @@ def get(self, request, *args, **kwargs): @method_decorator(csrf_exempt, name="dispatch") +@method_decorator(login_not_required, name="dispatch") class UserInfoView(OIDCOnlyMixin, OAuthLibMixin, View): """ View used to show Claims about the authenticated End-User @@ -211,6 +215,7 @@ def _validate_claims(request, claims): return True +@method_decorator(login_not_required, name="dispatch") class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView): template_name = "oauth2_provider/logout_confirm.html" form_class = ConfirmLogoutForm diff --git a/tests/conftest.py b/tests/conftest.py index eff48f7fb..2510025ce 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from urllib.parse import parse_qs, urlparse import pytest +from django import VERSION from django.conf import settings as test_settings from django.contrib.auth import get_user_model from django.urls import reverse @@ -294,3 +295,13 @@ def oidc_non_confidential_tokens(oauth2_settings, public_application, test_user, "openid", "http://other.org", ) + + +@pytest.fixture(autouse=True) +def django_login_required_middleware(settings, request): + if "nologinrequiredmiddleware" in request.keywords: + return + + # Django 5.1 introduced LoginRequiredMiddleware + if VERSION[0] >= 5 and VERSION[1] >= 1: + settings.MIDDLEWARE = [*settings.MIDDLEWARE, "django.contrib.auth.middleware.LoginRequiredMiddleware"] diff --git a/tests/test_introspection_auth.py b/tests/test_introspection_auth.py index 100ef064e..d96a013e3 100644 --- a/tests/test_introspection_auth.py +++ b/tests/test_introspection_auth.py @@ -11,6 +11,7 @@ from django.utils import timezone from oauthlib.common import Request +from oauth2_provider.compat import login_not_required from oauth2_provider.models import get_access_token_model, get_application_model from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.settings import oauth2_settings @@ -93,7 +94,7 @@ def mocked_introspect_request_short_living_token(url, data, *args, **kwargs): urlpatterns = [ path("oauth2/", include("oauth2_provider.urls")), - path("oauth2-test-resource/", ScopeResourceView.as_view()), + path("oauth2-test-resource/", login_not_required(ScopeResourceView.as_view())), ] diff --git a/tests/test_rest_framework.py b/tests/test_rest_framework.py index 632c62e26..84b4ad7d9 100644 --- a/tests/test_rest_framework.py +++ b/tests/test_rest_framework.py @@ -127,6 +127,7 @@ class AuthenticationNoneOAuth2View(MockView): @override_settings(ROOT_URLCONF=__name__) +@pytest.mark.nologinrequiredmiddleware @pytest.mark.usefixtures("oauth2_settings") @pytest.mark.oauth2_settings(presets.REST_FRAMEWORK_SCOPES) class TestOAuth2Authentication(TestCase): diff --git a/tox.ini b/tox.ini index 56d249661..42819568f 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,7 @@ addopts = -s markers = oauth2_settings: Custom OAuth2 settings to use - use with oauth2_settings fixture + nologinrequiredmiddleware [testenv] commands = From 3e0329db561e882388b2a4a0c7022e28c0a61e52 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Thu, 15 Aug 2024 22:26:31 +0800 Subject: [PATCH 022/115] check format using ruff (#1457) * check format using ruff instead of black --- .github/workflows/lint.yaml | 20 ++++++++++++++++++++ .gitignore | 1 + .pre-commit-config.yaml | 7 +++---- pyproject.toml | 15 ++++----------- tox.ini | 1 - 5 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..fe0637ca4 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,20 @@ +name: Lint + +on: [push, pull_request] + +jobs: + ruff: + name: Ruff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install Ruff + run: | + python -m pip install ruff>=0.5 + - name: Format check (Ruff) + run: | + ruff format --check diff --git a/.gitignore b/.gitignore index 70d81b559..d64e1776b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ pip-log.txt .coverage .tox .pytest_cache/ +.ruff_cache/ nosetests.xml # Translations diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a2e65601..1b5e05178 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,8 @@ repos: - - repo: https://github.com/psf/black - rev: 24.8.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.7 hooks: - - id: black - exclude: ^(oauth2_provider/migrations/|tests/migrations/) + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 884f7aec4..568f7f3de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,10 @@ -[tool.black] -line-length = 110 -target-version = ['py38'] -exclude = ''' -^/( - oauth2_provider/migrations/ - | tests/migrations/ - | .tox -) -''' - # Ref: https://github.com/codespell-project/codespell#using-a-config-file [tool.codespell] skip = '.git,package-lock.json,locale,AUTHORS,tox.ini' check-hidden = true ignore-regex = '.*pragma: codespell-ignore.*' ignore-words-list = 'assertIn' + +[tool.ruff] +line-length = 110 +exclude = [".tox", "oauth2_provider/migrations/", "tests/migrations/", "manage.py"] diff --git a/tox.ini b/tox.ini index 42819568f..f1c07f998 100644 --- a/tox.ini +++ b/tox.ini @@ -99,7 +99,6 @@ deps = flake8 flake8-isort flake8-quotes - flake8-black [testenv:migrations] setenv = From 56149aa6ffb2eb434f25cd2312ce34cdb2bb5e8d Mon Sep 17 00:00:00 2001 From: 9128305 <129173596+9128305@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:53:38 +0300 Subject: [PATCH 023/115] Make pytz optional (#1458) Make pytz optional --- setup.cfg | 1 - tox.ini | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4f25adf1d..e2cc2a577 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,6 @@ install_requires = requests >= 2.13.0 oauthlib >= 3.1.0 jwcrypto >= 0.8.0 - pytz >= 2024.1 [options.packages.find] exclude = diff --git a/tox.ini b/tox.ini index f1c07f998..bf945d882 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,7 @@ deps = pytest-xdist pytest-mock requests + pytz; python_version < '3.9' passenv = PYTEST_ADDOPTS From 7f9085fbebc944f8cbe2fc8d9cb7b7a844c03ca3 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Fri, 16 Aug 2024 00:32:28 +0800 Subject: [PATCH 024/115] replace isort with ruff (#1459) --- .github/workflows/lint.yaml | 5 +++-- .pre-commit-config.yaml | 9 +++------ docs/contributing.rst | 11 +++++------ pyproject.toml | 7 +++++++ tox.ini | 14 -------------- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index fe0637ca4..d5137af4a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,7 +14,8 @@ jobs: python-version: "3.10" - name: Install Ruff run: | - python -m pip install ruff>=0.5 - - name: Format check (Ruff) + python -m pip install ruff>=0.6 + - name: Lint using Ruff run: | ruff format --check + ruff check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b5e05178..ab4763002 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,9 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.6.0 hooks: + - id: ruff + args: [ --fix ] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -14,11 +16,6 @@ repos: - id: check-yaml - id: mixed-line-ending args: ['--fix=lf'] - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - exclude: ^(oauth2_provider/migrations/|tests/migrations/) - repo: https://github.com/PyCQA/flake8 rev: 7.1.1 hooks: diff --git a/docs/contributing.rst b/docs/contributing.rst index c31e72990..ca72a74a5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -28,13 +28,12 @@ Code Style ========== The project uses `flake8 `_ for linting, -`black `_ for formatting the code, -`isort `_ for formatting and sorting imports, +`ruff `_ for formatting the code and sorting imports, and `pre-commit `_ for checking/fixing commits for correctness before they are made. You will need to install ``pre-commit`` yourself, and then ``pre-commit`` will -take care of installing ``flake8``, ``black`` and ``isort``. +take care of installing ``flake8`` and ``ruff``. After cloning your repository, go into it and run:: @@ -42,14 +41,14 @@ After cloning your repository, go into it and run:: to install the hooks. On the next commit that you make, ``pre-commit`` will download and install the necessary hooks (a one off task). If anything in the -commit would fail the hooks, the commit will be abandoned. For ``black`` and -``isort``, any necessary changes will be made automatically, but not staged. +commit would fail the hooks, the commit will be abandoned. For ``ruff``, any +necessary changes will be made automatically, but not staged. Review the changes, and then re-stage and commit again. Using ``pre-commit`` ensures that code that would fail in QA does not make it into a commit in the first place, and will save you time in the long run. You can also (largely) stop worrying about code style, although you should always -check how the code looks after ``black`` has formatted it, and think if there +check how the code looks after ``ruff`` has formatted it, and think if there is a better way to structure the code so that it is more readable. Documentation diff --git a/pyproject.toml b/pyproject.toml index 568f7f3de..0de9634fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,3 +8,10 @@ ignore-words-list = 'assertIn' [tool.ruff] line-length = 110 exclude = [".tox", "oauth2_provider/migrations/", "tests/migrations/", "manage.py"] + +[tool.ruff.lint] +select = ["I", "Q"] + +[tool.ruff.lint.isort] +lines-after-imports = 2 +known-first-party = ["oauth2_provider"] diff --git a/tox.ini b/tox.ini index bf945d882..52a5c76de 100644 --- a/tox.ini +++ b/tox.ini @@ -98,8 +98,6 @@ skip_install = True commands = flake8 {toxinidir} deps = flake8 - flake8-isort - flake8-quotes [testenv:migrations] setenv = @@ -138,15 +136,3 @@ exclude = docs/, oauth2_provider/migrations/, tests/migrations/, .tox/, build/, application-import-names = oauth2_provider inline-quotes = double extend-ignore = E203, W503 - -[isort] -default_section = THIRDPARTY -known_first_party = oauth2_provider -line_length = 110 -lines_after_imports = 2 -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 -use_parentheses = True -ensure_newline_before_comments = True -skip = oauth2_provider/migrations/, .tox/, tests/migrations/ From 0706fcb24807146c205d11e84194d9e88d68dfdb Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Fri, 16 Aug 2024 00:54:19 +0800 Subject: [PATCH 025/115] CI: bum actions/setup-python to v5 (#1460) --- .github/workflows/lint.yaml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d5137af4a..6e3710489 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install Ruff diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d4683cfd..d9ac5b254 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 552e21281..d7af13b60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} From 779da2b5de0e2815fa61251cd2112ba50058be46 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Fri, 16 Aug 2024 01:02:28 +0800 Subject: [PATCH 026/115] replace flake8 with ruff (#1462) --- .pre-commit-config.yaml | 5 ----- docs/contributing.rst | 10 ++++------ pyproject.toml | 4 ++-- tox.ini | 20 +++++++------------- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab4763002..ee5e97386 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,6 @@ repos: - id: check-yaml - id: mixed-line-ending args: ['--fix=lf'] - - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 - hooks: - - id: flake8 - exclude: ^(oauth2_provider/migrations/|tests/migrations/) - repo: https://github.com/sphinx-contrib/sphinx-lint rev: v0.9.1 hooks: diff --git a/docs/contributing.rst b/docs/contributing.rst index ca72a74a5..425008a62 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -27,13 +27,11 @@ add a comment stating you're working on it. Code Style ========== -The project uses `flake8 `_ for linting, -`ruff `_ for formatting the code and sorting imports, -and `pre-commit `_ for checking/fixing commits for -correctness before they are made. +The project uses `ruff `_ for linting, formatting the code and sorting imports, +and `pre-commit `_ for checking/fixing commits for correctness before they are made. You will need to install ``pre-commit`` yourself, and then ``pre-commit`` will -take care of installing ``flake8`` and ``ruff``. +take care of installing ``ruff``. After cloning your repository, go into it and run:: @@ -264,7 +262,7 @@ add a comment. If you think a function is not trivial, add a docstrings. To see if your code formatting will pass muster use:: - tox -e flake8 + tox -e lint The contents of this page are heavily based on the docs from `django-admin2 `_ diff --git a/pyproject.toml b/pyproject.toml index 0de9634fd..49990b57f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,10 @@ ignore-words-list = 'assertIn' [tool.ruff] line-length = 110 -exclude = [".tox", "oauth2_provider/migrations/", "tests/migrations/", "manage.py"] +exclude = [".tox", "build/", "dist/", "docs/", "oauth2_provider/migrations/", "tests/migrations/", "manage.py"] [tool.ruff.lint] -select = ["I", "Q"] +select = ["E", "F", "I", "Q", "W"] [tool.ruff.lint.isort] lines-after-imports = 2 diff --git a/tox.ini b/tox.ini index 52a5c76de..62e199868 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist = - flake8, migrations, migrate_swapped, docs, + lint, sphinxlint, py{38,39,310,311,312}-dj42, py{310,311,312}-dj50, @@ -12,7 +12,7 @@ envlist = [gh-actions] python = - 3.8: py38, docs, flake8, migrations, migrate_swapped, sphinxlint + 3.8: py38, docs, lint, migrations, migrate_swapped, sphinxlint 3.9: py39 3.10: py310 3.11: py311 @@ -92,12 +92,13 @@ deps = jwcrypto django -[testenv:flake8] +[testenv:lint] basepython = python3.8 +deps = ruff>=0.6 skip_install = True -commands = flake8 {toxinidir} -deps = - flake8 +commands = + ruff format --check + ruff check [testenv:migrations] setenv = @@ -129,10 +130,3 @@ omit = */migrations/* [coverage:report] show_missing = True - -[flake8] -max-line-length = 110 -exclude = docs/, oauth2_provider/migrations/, tests/migrations/, .tox/, build/, dist/ -application-import-names = oauth2_provider -inline-quotes = double -extend-ignore = E203, W503 From 9fceef11c59a200711d1e7023495131e42dfae0e Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Fri, 16 Aug 2024 01:18:40 +0800 Subject: [PATCH 027/115] modernize packaging using pyproject.toml (#1461) --- .github/workflows/release.yml | 6 ++--- .github/workflows/test.yml | 2 +- pyproject.toml | 49 +++++++++++++++++++++++++++++++++++ setup.cfg | 45 -------------------------------- setup.py | 6 ----- tox.ini | 7 ++--- 6 files changed, 56 insertions(+), 59 deletions(-) delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9ac5b254..64302e819 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,13 +22,11 @@ jobs: - name: Install dependencies run: | - python -m pip install -U pip - python -m pip install -U setuptools twine wheel + python -m pip install -U pip build twine - name: Build package run: | - python setup.py --version - python setup.py sdist --format=gztar bdist_wheel + python -m build twine check dist/* - name: Upload packages to Jazzband diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d7af13b60..f0bf9f155 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,7 @@ jobs: with: path: ${{ steps.pip-cache.outputs.dir }} key: - ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }} restore-keys: | ${{ matrix.python-version }}-v1- diff --git a/pyproject.toml b/pyproject.toml index 49990b57f..8a7eb8d9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,52 @@ +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "django-oauth-toolkit" +dynamic = ["version"] +requires-python = ">= 3.8" +authors = [ + {name = "Federico Frenguelli"}, + {name = "Massimiliano Pippi"}, + {email = "synasius@gmail.com"}, +] +description = "OAuth2 Provider for Django" +keywords = ["django", "oauth", "oauth2", "oauthlib"] +license = {file = "LICENSE"} +readme = "README.rst" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet :: WWW/HTTP", +] +dependencies = [ + "django >= 4.2", + "requests >= 2.13.0", + "oauthlib >= 3.1.0", + "jwcrypto >= 0.8.0", +] + +[project.urls] +Homepage = "https://django-oauth-toolkit.readthedocs.io/" +Repository = "https://github.com/jazzband/django-oauth-toolkit" + +[tool.setuptools.dynamic] +version = {attr = "oauth2_provider.__version__"} + # Ref: https://github.com/codespell-project/codespell#using-a-config-file [tool.codespell] skip = '.git,package-lock.json,locale,AUTHORS,tox.ini' diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e2cc2a577..000000000 --- a/setup.cfg +++ /dev/null @@ -1,45 +0,0 @@ -[metadata] -name = django-oauth-toolkit -version = attr: oauth2_provider.__version__ -description = OAuth2 Provider for Django -long_description = file: README.rst -long_description_content_type = text/x-rst -author = Federico Frenguelli, Massimiliano Pippi -author_email = synasius@gmail.com -url = https://github.com/jazzband/django-oauth-toolkit -keywords = django, oauth, oauth2, oauthlib -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Django - Framework :: Django :: 4.2 - Framework :: Django :: 5.0 - Framework :: Django :: 5.1 - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Topic :: Internet :: WWW/HTTP - -[options] -packages = find: -include_package_data = True -zip_safe = False -python_requires = >=3.8 -# jwcrypto has a direct dependency on six, but does not list it yet in a release -# Previously, cryptography also depended on six, so this was unnoticed -install_requires = - django >= 4.2 - requests >= 2.13.0 - oauthlib >= 3.1.0 - jwcrypto >= 0.8.0 - -[options.packages.find] -exclude = - tests - tests.* diff --git a/setup.py b/setup.py deleted file mode 100755 index dd4e63e40..000000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - - -setup() diff --git a/tox.ini b/tox.ini index 62e199868..2372f044b 100644 --- a/tox.ini +++ b/tox.ini @@ -117,12 +117,13 @@ commands = [testenv:build] deps = - setuptools>=39.0 - wheel + build + twine allowlist_externals = rm commands = rm -rf dist - python setup.py sdist bdist_wheel + python -m build + twine check dist/* [coverage:run] source = oauth2_provider From e1cfb4ccb1023b528922ff68e2f5001b20ea485e Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Tue, 27 Aug 2024 01:08:00 +0800 Subject: [PATCH 028/115] centralize tools config in pyproject.toml (#1463) --- pyproject.toml | 20 ++++++++++++++++++++ tox.ini | 18 ------------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8a7eb8d9f..51dda61f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,26 @@ check-hidden = true ignore-regex = '.*pragma: codespell-ignore.*' ignore-words-list = 'assertIn' +[tool.coverage.run] +source = ["oauth2_provider"] +omit = ["*/migrations/*"] + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +django_find_project = false +addopts = [ + "--cov=oauth2_provider", + "--cov-report=", + "--cov-append", + "-s" +] +markers = [ + "oauth2_settings: Custom OAuth2 settings to use - use with oauth2_settings fixture", + "nologinrequiredmiddleware", +] + [tool.ruff] line-length = 110 exclude = [".tox", "build/", "dist/", "docs/", "oauth2_provider/migrations/", "tests/migrations/", "manage.py"] diff --git a/tox.ini b/tox.ini index 2372f044b..a461b5de5 100644 --- a/tox.ini +++ b/tox.ini @@ -25,17 +25,6 @@ DJANGO = 5.1: dj51 main: djmain -[pytest] -django_find_project = false -addopts = - --cov=oauth2_provider - --cov-report= - --cov-append - -s -markers = - oauth2_settings: Custom OAuth2 settings to use - use with oauth2_settings fixture - nologinrequiredmiddleware - [testenv] commands = pytest {posargs} @@ -124,10 +113,3 @@ commands = rm -rf dist python -m build twine check dist/* - -[coverage:run] -source = oauth2_provider -omit = */migrations/* - -[coverage:report] -show_missing = True From 460387af8c987a769b7684be2277ea08dcebbf79 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Tue, 27 Aug 2024 01:18:58 +0800 Subject: [PATCH 029/115] CI: remove lint job (#1464) --- .github/workflows/lint.yaml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 6e3710489..000000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: Lint - -on: [push, pull_request] - -jobs: - ruff: - name: Ruff - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install Ruff - run: | - python -m pip install ruff>=0.6 - - name: Lint using Ruff - run: | - ruff format --check - ruff check From 3426a38384704395c944db1de7c8d082a248a826 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Tue, 27 Aug 2024 01:28:54 +0800 Subject: [PATCH 030/115] bump oauthlib to 3.2 (#1465) --- CHANGELOG.md | 1 + README.rst | 2 +- docs/index.rst | 2 +- docs/requirements.txt | 2 +- pyproject.toml | 2 +- tox.ini | 4 ++-- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 738927c5d..371abb56c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims * Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. * #1446 use generic models pk instead of id. +* Bump oauthlib version to 3.2.0 and above ### Deprecated ### Removed diff --git a/README.rst b/README.rst index ff94b8c62..73707e079 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ Requirements * Python 3.8+ * Django 4.2, 5.0 or 5.1 -* oauthlib 3.1+ +* oauthlib 3.2+ Installation ------------ diff --git a/docs/index.rst b/docs/index.rst index 915a4f6b8..bb224d358 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,7 +23,7 @@ Requirements * Python 3.8+ * Django 4.2, 5.0 or 5.1 -* oauthlib 3.1+ +* oauthlib 3.2+ Index ===== diff --git a/docs/requirements.txt b/docs/requirements.txt index b47039487..f5dfe94aa 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ Django -oauthlib>=3.1.0 +oauthlib>=3.2.0 m2r>=0.2.1 mistune<2 sphinx==7.2.6 diff --git a/pyproject.toml b/pyproject.toml index 51dda61f2..354645b47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ classifiers = [ dependencies = [ "django >= 4.2", "requests >= 2.13.0", - "oauthlib >= 3.1.0", + "oauthlib >= 3.2.0", "jwcrypto >= 0.8.0", ] diff --git a/tox.ini b/tox.ini index a461b5de5..63b8b7124 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,7 @@ deps = dj51: Django>=5.1,<5.2 djmain: https://github.com/django/django/archive/main.tar.gz djangorestframework - oauthlib>=3.1.0 + oauthlib>=3.2.0 jwcrypto coverage pytest @@ -73,7 +73,7 @@ commands = deps = Jinja2<3.1 sphinx<3 - oauthlib>=3.1.0 + oauthlib>=3.2.0 m2r>=0.2.1 mistune<2 sphinx-rtd-theme From 34912ff53d948831cf4d86f210290b06c04e4d09 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:46:49 -0400 Subject: [PATCH 031/115] [pre-commit.ci] pre-commit autoupdate (#1467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.0 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.0...v0.6.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee5e97386..d240cdf98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.0 + rev: v0.6.2 hooks: - id: ruff args: [ --fix ] From e63999d1c782cd9c4cc4dd2642687d4704a57fb7 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Wed, 28 Aug 2024 15:45:04 +0200 Subject: [PATCH 032/115] Work around double parsing of ui_locales (#1469) * Work around double parsing of ui_locales * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 + oauth2_provider/views/base.py | 4 +++ tests/test_ui_locales.py | 57 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 tests/test_ui_locales.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 371abb56c..3dfa94c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * #1443 Query strings with invalid hex values now raise a SuspiciousOperation exception (in DRF extension) instead of raising a 500 ValueError: Invalid hex encoding in query string. +* #1468 `ui_locales` request parameter triggers `AttributeError` under certain circumstances ### Security ## [2.4.0] - 2024-05-13 diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index d2644f35f..1e0d12dea 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -186,6 +186,10 @@ def get(self, request, *args, **kwargs): # a successful response depending on "approval_prompt" url parameter require_approval = request.GET.get("approval_prompt", oauth2_settings.REQUEST_APPROVAL_PROMPT) + if "ui_locales" in credentials and isinstance(credentials["ui_locales"], list): + # Make sure ui_locales a space separated string for oauthlib to handle it correctly. + credentials["ui_locales"] = " ".join(credentials["ui_locales"]) + try: # If skip_authorization field is True, skip the authorization screen even # if this is the first use of the application and there was no previous authorization. diff --git a/tests/test_ui_locales.py b/tests/test_ui_locales.py new file mode 100644 index 000000000..d375dc55c --- /dev/null +++ b/tests/test_ui_locales.py @@ -0,0 +1,57 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase, override_settings +from django.urls import reverse + +from oauth2_provider.models import get_application_model + + +UserModel = get_user_model() +Application = get_application_model() + + +@override_settings( + OAUTH2_PROVIDER={ + "OIDC_ENABLED": True, + "PKCE_REQUIRED": False, + "SCOPES": { + "openid": "OpenID connect", + }, + } +) +class TestUILocalesParam(TestCase): + @classmethod + def setUpTestData(cls): + cls.application = Application.objects.create( + name="Test Application", + client_id="test", + redirect_uris="https://www.example.com/", + client_type=Application.CLIENT_PUBLIC, + authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, + ) + cls.trusted_application = Application.objects.create( + name="Trusted Application", + client_id="trusted", + redirect_uris="https://www.example.com/", + client_type=Application.CLIENT_PUBLIC, + authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, + skip_authorization=True, + ) + cls.user = UserModel.objects.create_user("test_user") + cls.url = reverse("oauth2_provider:authorize") + + def setUp(self): + self.client.force_login(self.user) + + def test_application_ui_locales_param(self): + response = self.client.get( + f"{self.url}?response_type=code&client_id=test&scope=openid&ui_locales=de", + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "oauth2_provider/authorize.html") + + def test_trusted_application_ui_locales_param(self): + response = self.client.get( + f"{self.url}?response_type=code&client_id=trusted&scope=openid&ui_locales=de", + ) + self.assertEqual(response.status_code, 302) + self.assertRegex(response.url, r"https://www\.example\.com/\?code=[a-zA-Z0-9]+") From 3b429c95f3e8af109d2fabae828e059ea9ea9d66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:49:19 -0400 Subject: [PATCH 033/115] Bump svelte from 4.2.18 to 4.2.19 in /tests/app/rp (#1473) Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 4.2.18 to 4.2.19. - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/svelte@4.2.19/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@4.2.19/packages/svelte) --- updated-dependencies: - dependency-name: svelte dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/app/rp/package-lock.json | 8 ++++---- tests/app/rp/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 80d8b1372..627ce8af4 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -16,7 +16,7 @@ "@sveltejs/kit": "^2.5.10", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", - "svelte": "^4.0.0", + "svelte": "^4.2.19", "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", @@ -2128,9 +2128,9 @@ } }, "node_modules/svelte": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", - "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", + "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index d36c7b769..8caf72fe6 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -17,7 +17,7 @@ "@sveltejs/kit": "^2.5.10", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", - "svelte": "^4.0.0", + "svelte": "^4.2.19", "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", From aede24bef889d9bc9b5947e23145245e5f2e6e12 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:17:32 -0400 Subject: [PATCH 034/115] [pre-commit.ci] pre-commit autoupdate (#1475) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d240cdf98..b124c7342 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.2 + rev: v0.6.3 hooks: - id: ruff args: [ --fix ] From 62508b4a2dc563e850145675a7d92de0a730c255 Mon Sep 17 00:00:00 2001 From: Miriam Forner Date: Tue, 3 Sep 2024 15:53:17 +0100 Subject: [PATCH 035/115] Raise InvalidGrantError if no grant associated with auth code exists (#1476) Previously, when invalidating an authorization code after it has been used, if for whatever reason the associated grant object no longer exists, an uncaught exception would be raised - Grant.DoesNotExist. This could be caused by concurrent requests being made using the same authorization token. We now handle this scenario gracefully by catching Grant.DoesNotExist and returning an InvalidGrantError. --- AUTHORS | 1 + CHANGELOG.md | 1 + oauth2_provider/oauth2_validators.py | 13 +++++++++---- tests/test_oauth2_validators.py | 20 +++++++++++++++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 584ecf59c..ba9afa8da 100644 --- a/AUTHORS +++ b/AUTHORS @@ -119,3 +119,4 @@ pySilver Wouter Klein Heerenbrink Yaroslav Halchenko Yuri Savin +Miriam Forner diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfa94c4b..68d7f0081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. * #1446 use generic models pk instead of id. * Bump oauthlib version to 3.2.0 and above +* Update the OAuth2Validator's invalidate_authorization_code method to return an InvalidGrantError if the associated grant does not exist. ### Deprecated ### Removed diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 78667fa0e..7cb1ecfd5 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -24,7 +24,7 @@ from jwcrypto import jws, jwt from jwcrypto.common import JWException from jwcrypto.jwt import JWTExpired -from oauthlib.oauth2.rfc6749 import utils +from oauthlib.oauth2.rfc6749 import errors, utils from oauthlib.openid import RequestValidator from .exceptions import FatalClientError @@ -318,10 +318,15 @@ def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **k def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs): """ - Remove the temporary grant used to swap the authorization token + Remove the temporary grant used to swap the authorization token. + + :raises: InvalidGrantError if the grant does not exist. """ - grant = Grant.objects.get(code=code, application=request.client) - grant.delete() + try: + grant = Grant.objects.get(code=code, application=request.client) + grant.delete() + except Grant.DoesNotExist: + raise errors.InvalidGrantError(request=request) def validate_client_id(self, client_id, request, *args, **kwargs): """ diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index ca80aedb0..f499faf2d 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -9,9 +9,15 @@ from django.utils import timezone from jwcrypto import jwt from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import errors as rfc6749_errors from oauth2_provider.exceptions import FatalClientError -from oauth2_provider.models import get_access_token_model, get_application_model, get_refresh_token_model +from oauth2_provider.models import ( + get_access_token_model, + get_application_model, + get_grant_model, + get_refresh_token_model, +) from oauth2_provider.oauth2_backends import get_oauthlib_core from oauth2_provider.oauth2_validators import OAuth2Validator @@ -28,6 +34,7 @@ UserModel = get_user_model() Application = get_application_model() AccessToken = get_access_token_model() +Grant = get_grant_model() RefreshToken = get_refresh_token_model() CLEARTEXT_SECRET = "1234567890abcdefghijklmnopqrstuvwxyz" @@ -578,3 +585,14 @@ def test_validate_id_token_bad_token_no_aud(oauth2_settings, mocker, oidc_key): validator = OAuth2Validator() status = validator.validate_id_token(token.serialize(), ["openid"], mocker.sentinel.request) assert status is False + + +@pytest.mark.django_db +def test_invalidate_authorization_token_returns_invalid_grant_error_when_grant_does_not_exist(): + client_id = "123" + code = "12345" + request = Request("/") + assert Grant.objects.all().count() == 0 + with pytest.raises(rfc6749_errors.InvalidGrantError): + validator = OAuth2Validator() + validator.invalidate_authorization_code(client_id=client_id, code=code, request=request) From 1d19e3d962ad6f4a2dfe7768504a2df96123b323 Mon Sep 17 00:00:00 2001 From: Cristian Prigoana Date: Wed, 4 Sep 2024 18:58:27 +0100 Subject: [PATCH 036/115] bump oauthlib to 3.2.2 (#1481) --- CHANGELOG.md | 2 +- README.rst | 2 +- docs/index.rst | 2 +- docs/requirements.txt | 2 +- pyproject.toml | 2 +- tox.ini | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d7f0081..7acd162ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims * Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. * #1446 use generic models pk instead of id. -* Bump oauthlib version to 3.2.0 and above +* Bump oauthlib version to 3.2.2 and above * Update the OAuth2Validator's invalidate_authorization_code method to return an InvalidGrantError if the associated grant does not exist. ### Deprecated diff --git a/README.rst b/README.rst index 73707e079..dee670e4b 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ Requirements * Python 3.8+ * Django 4.2, 5.0 or 5.1 -* oauthlib 3.2+ +* oauthlib 3.2.2+ Installation ------------ diff --git a/docs/index.rst b/docs/index.rst index bb224d358..07ed24314 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,7 +23,7 @@ Requirements * Python 3.8+ * Django 4.2, 5.0 or 5.1 -* oauthlib 3.2+ +* oauthlib 3.2.2+ Index ===== diff --git a/docs/requirements.txt b/docs/requirements.txt index f5dfe94aa..aa59757a1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ Django -oauthlib>=3.2.0 +oauthlib>=3.2.2 m2r>=0.2.1 mistune<2 sphinx==7.2.6 diff --git a/pyproject.toml b/pyproject.toml index 354645b47..84e800fe2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ classifiers = [ dependencies = [ "django >= 4.2", "requests >= 2.13.0", - "oauthlib >= 3.2.0", + "oauthlib >= 3.2.2", "jwcrypto >= 0.8.0", ] diff --git a/tox.ini b/tox.ini index 63b8b7124..fc1c24507 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,7 @@ deps = dj51: Django>=5.1,<5.2 djmain: https://github.com/django/django/archive/main.tar.gz djangorestframework - oauthlib>=3.2.0 + oauthlib>=3.2.2 jwcrypto coverage pytest @@ -73,7 +73,7 @@ commands = deps = Jinja2<3.1 sphinx<3 - oauthlib>=3.2.0 + oauthlib>=3.2.2 m2r>=0.2.1 mistune<2 sphinx-rtd-theme From 956186666803a3f78bc02d7b15ad3bf33917a95f Mon Sep 17 00:00:00 2001 From: Sean Perry Date: Wed, 4 Sep 2024 18:29:33 -0700 Subject: [PATCH 037/115] Honor database assignment from router (#1450) * Improve multiple database support. The token models might not be stored in the default database. There might not _be_ a default database. Intead, the code now relies on Django's routers to determine the actual database to use when creating transactions. This required moving from decorators to context managers for those transactions. To test the multiple database scenario a new settings file as added which derives from settings.py and then defines different databases and the routers needed to access them. The commit is larger than might be expected because when there are multiple databases the Django tests have to be told which databases to work on. Rather than copying the various test cases or making multiple database specific ones the decision was made to add wrappers around the standard Django TestCase classes and programmatically define the databases for them. This enables all of the same test code to work for both the one database and the multi database scenarios with minimal maintenance costs. A tox environment that uses the multi db settings file has been added to ensure both scenarios are always tested. * changelog entry and authors update * PR review response. Document multiple database requires in advanced_topics.rst. Add an ImproperlyConfigured validator to the ready method of the DOTConfig app. Fix IDToken doc string. Document the use of _save_bearer_token. Define LocalIDToken and use it for validating the configuration test. Questionably, define py39-multi-db-invalid-token-configuration-dj42. This will consistently cause tox runs to fail until it is worked out how to mark this as an expected failure. * move migration * update migration * use django checks system * drop misconfigured db check. Let's find a better way. * run checks * maybe a better test definition * listing tests was breaking things * No more magic. * Oops. Debugger. * Use retrieven_current_databases in django_db marked tests. * Updates. Prove the checks work. Document test requirements. * fix typo --------- Co-authored-by: Alan Crosswell Co-authored-by: Alan Crosswell --- AUTHORS | 1 + CHANGELOG.md | 2 + docs/advanced_topics.rst | 11 +++ docs/contributing.rst | 20 +++++ oauth2_provider/apps.py | 4 + oauth2_provider/checks.py | 28 +++++++ oauth2_provider/models.py | 17 +++-- oauth2_provider/oauth2_validators.py | 25 ++++-- tests/common_testing.py | 33 ++++++++ tests/db_router.py | 76 +++++++++++++++++++ tests/migrations/0007_add_localidtoken.py | 34 +++++++++ tests/models.py | 7 ++ tests/multi_db_settings.py | 19 +++++ ...db_settings_invalid_token_configuration.py | 8 ++ tests/test_application_views.py | 2 +- tests/test_auth_backends.py | 4 +- tests/test_authorization_code.py | 3 +- tests/test_client_credential.py | 3 +- tests/test_commands.py | 2 +- tests/test_decorators.py | 4 +- tests/test_django_checks.py | 20 +++++ tests/test_generator.py | 3 +- tests/test_hybrid.py | 8 +- tests/test_implicit.py | 3 +- tests/test_introspection_auth.py | 3 +- tests/test_introspection_view.py | 6 +- tests/test_mixins.py | 3 +- tests/test_models.py | 15 ++-- tests/test_oauth2_backends.py | 3 +- tests/test_oauth2_validators.py | 10 ++- tests/test_oidc_views.py | 64 ++++++++-------- tests/test_password.py | 3 +- tests/test_rest_framework.py | 2 +- tests/test_scopes.py | 3 +- tests/test_settings.py | 2 +- tests/test_token_endpoint_cors.py | 3 +- tests/test_token_revocation.py | 4 +- tests/test_token_view.py | 3 +- tests/test_validators.py | 3 +- tox.ini | 7 ++ 40 files changed, 392 insertions(+), 79 deletions(-) create mode 100644 oauth2_provider/checks.py create mode 100644 tests/common_testing.py create mode 100644 tests/db_router.py create mode 100644 tests/migrations/0007_add_localidtoken.py create mode 100644 tests/multi_db_settings.py create mode 100644 tests/multi_db_settings_invalid_token_configuration.py create mode 100644 tests/test_django_checks.py diff --git a/AUTHORS b/AUTHORS index ba9afa8da..431edeabd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -102,6 +102,7 @@ Rodney Richardson Rustem Saiargaliev Rustem Saiargaliev Sandro Rodrigues +Sean 'Shaleh' Perry Shaheed Haque Shaun Stanworth Sayyid Hamid Mahdavi diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acd162ae..8ce7d2294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims * Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. * #1446 use generic models pk instead of id. +* Transactions wrapping writes of the Tokens now rely on Django's database routers to determine the correct + database to use instead of assuming that 'default' is the correct one. * Bump oauthlib version to 3.2.2 and above * Update the OAuth2Validator's invalidate_authorization_code method to return an InvalidGrantError if the associated grant does not exist. diff --git a/docs/advanced_topics.rst b/docs/advanced_topics.rst index 0b2ee20b0..204e3f860 100644 --- a/docs/advanced_topics.rst +++ b/docs/advanced_topics.rst @@ -65,6 +65,17 @@ That's all, now Django OAuth Toolkit will use your model wherever an Application is because of the way Django currently implements swappable models. See `issue #90 `_ for details. +Configuring multiple databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There is no requirement that the tokens are stored in the default database or that there is a +default database provided the database routers can determine the correct Token locations. Because the +Tokens have foreign keys to the ``User`` model, you likely want to keep the tokens in the same database +as your User model. It is also important that all of the tokens are stored in the same database. +This could happen for instance if one of the Tokens is locally overridden and stored in a separate database. +The reason for this is transactions will only be made for the database where AccessToken is stored +even when writing to RefreshToken or other tokens. + Multiple Grants ~~~~~~~~~~~~~~~ diff --git a/docs/contributing.rst b/docs/contributing.rst index 425008a62..648993024 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -252,6 +252,26 @@ Open :file:`mycoverage/index.html` in your browser and you can see a coverage su There's no need to wait for Codecov to complain after you submit your PR. +The tests are generic and written to work with both single database and multiple database configurations. tox will run +tests both ways. You can see the configurations used in tests/settings.py and tests/multi_db_settings.py. + +When there are multiple databases defined, Django tests will not work unless they are told which database(s) to work with. +For test writers this means any test must either: +- instead of Django's TestCase or TransactionTestCase use the versions of those + classes defined in tests/common_testing.py +- when using pytest's `django_db` mark, define it like this: + `@pytest.mark.django_db(databases=retrieve_current_databases())` + +In test code, anywhere the database is referenced the Django router needs to be used exactly like the package's code. + +.. code-block:: python + + token_database = router.db_for_write(AccessToken) + with self.assertNumQueries(1, using=token_database): + # call something using the database + +Without the 'using' option, this test fails in the multiple database scenario because 'default' will be used instead. + Code conventions matter ----------------------- diff --git a/oauth2_provider/apps.py b/oauth2_provider/apps.py index 887e4e3fb..3ad08b715 100644 --- a/oauth2_provider/apps.py +++ b/oauth2_provider/apps.py @@ -4,3 +4,7 @@ class DOTConfig(AppConfig): name = "oauth2_provider" verbose_name = "Django OAuth Toolkit" + + def ready(self): + # Import checks to ensure they run. + from . import checks # noqa: F401 diff --git a/oauth2_provider/checks.py b/oauth2_provider/checks.py new file mode 100644 index 000000000..848ba1af7 --- /dev/null +++ b/oauth2_provider/checks.py @@ -0,0 +1,28 @@ +from django.apps import apps +from django.core import checks +from django.db import router + +from .settings import oauth2_settings + + +@checks.register(checks.Tags.database) +def validate_token_configuration(app_configs, **kwargs): + databases = set( + router.db_for_write(apps.get_model(model)) + for model in ( + oauth2_settings.ACCESS_TOKEN_MODEL, + oauth2_settings.ID_TOKEN_MODEL, + oauth2_settings.REFRESH_TOKEN_MODEL, + ) + ) + + # This is highly unlikely, but let's warn people just in case it does. + # If the tokens were allowed to be in different databases this would require all + # writes to have a transaction around each database. Instead, let's enforce that + # they all live together in one database. + # The tokens are not required to live in the default database provided the Django + # routers know the correct database for them. + if len(databases) > 1: + return [checks.Error("The token models are expected to be stored in the same database.")] + + return [] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index f979eef1c..831fc551f 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -2,6 +2,7 @@ import logging import time import uuid +from contextlib import suppress from datetime import timedelta from urllib.parse import parse_qsl, urlparse @@ -9,7 +10,7 @@ from django.conf import settings from django.contrib.auth.hashers import identify_hasher, make_password from django.core.exceptions import ImproperlyConfigured -from django.db import models, transaction +from django.db import models, router, transaction from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -512,17 +513,19 @@ def revoke(self): Mark this refresh token revoked and revoke related access token """ access_token_model = get_access_token_model() + access_token_database = router.db_for_write(access_token_model) refresh_token_model = get_refresh_token_model() - with transaction.atomic(): + + # Use the access_token_database instead of making the assumption it is in 'default'. + with transaction.atomic(using=access_token_database): token = refresh_token_model.objects.select_for_update().filter(pk=self.pk, revoked__isnull=True) if not token: return self = list(token)[0] - try: - access_token_model.objects.get(pk=self.access_token_id).revoke() - except access_token_model.DoesNotExist: - pass + with suppress(access_token_model.DoesNotExist): + access_token_model.objects.get(id=self.access_token_id).revoke() + self.access_token = None self.revoked = timezone.now() self.save() @@ -655,7 +658,7 @@ def get_access_token_model(): def get_id_token_model(): - """Return the AccessToken model that is active in this project.""" + """Return the IDToken model that is active in this project.""" return apps.get_model(oauth2_settings.ID_TOKEN_MODEL) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 7cb1ecfd5..b20d0dd6c 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -15,7 +15,7 @@ from django.contrib.auth import authenticate, get_user_model from django.contrib.auth.hashers import check_password, identify_hasher from django.core.exceptions import ObjectDoesNotExist -from django.db import transaction +from django.db import router, transaction from django.http import HttpRequest from django.utils import dateformat, timezone from django.utils.crypto import constant_time_compare @@ -567,11 +567,23 @@ def rotate_refresh_token(self, request): """ return oauth2_settings.ROTATE_REFRESH_TOKEN - @transaction.atomic def save_bearer_token(self, token, request, *args, **kwargs): """ - Save access and refresh token, If refresh token is issued, remove or - reuse old refresh token as in rfc:`6` + Save access and refresh token. + + Override _save_bearer_token and not this function when adding custom logic + for the storing of these token. This allows the transaction logic to be + separate from the token handling. + """ + # Use the AccessToken's database instead of making the assumption it is in 'default'. + with transaction.atomic(using=router.db_for_write(AccessToken)): + return self._save_bearer_token(token, request, *args, **kwargs) + + def _save_bearer_token(self, token, request, *args, **kwargs): + """ + Save access and refresh token. + + If refresh token is issued, remove or reuse old refresh token as in rfc:`6`. @see: https://rfc-editor.org/rfc/rfc6749.html#section-6 """ @@ -793,7 +805,6 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs return rt.application == client - @transaction.atomic def _save_id_token(self, jti, request, expires, *args, **kwargs): scopes = request.scope or " ".join(request.scopes) @@ -894,7 +905,9 @@ def finalize_id_token(self, id_token, token, token_handler, request): claims=json.dumps(id_token, default=str), ) jwt_token.make_signed_token(request.client.jwk_key) - id_token = self._save_id_token(id_token["jti"], request, expiration_time) + # Use the IDToken's database instead of making the assumption it is in 'default'. + with transaction.atomic(using=router.db_for_write(IDToken)): + id_token = self._save_id_token(id_token["jti"], request, expiration_time) # this is needed by django rest framework request.access_token = id_token request.id_token = id_token diff --git a/tests/common_testing.py b/tests/common_testing.py new file mode 100644 index 000000000..6f6a5b745 --- /dev/null +++ b/tests/common_testing.py @@ -0,0 +1,33 @@ +from django.conf import settings +from django.test import TestCase as DjangoTestCase +from django.test import TransactionTestCase as DjangoTransactionTestCase + + +# The multiple database scenario setup for these tests purposefully defines 'default' as +# an empty database in order to catch any assumptions in this package about database names +# and in particular to ensure there is no assumption that 'default' is a valid database. +# +# When there are multiple databases defined, Django tests will not work unless they are +# told which database(s) to work with. + + +def retrieve_current_databases(): + if len(settings.DATABASES) > 1: + return [name for name in settings.DATABASES if name != "default"] + else: + return ["default"] + + +class OAuth2ProviderBase: + @classmethod + def setUpClass(cls): + cls.databases = retrieve_current_databases() + super().setUpClass() + + +class OAuth2ProviderTestCase(OAuth2ProviderBase, DjangoTestCase): + """Place holder to allow overriding behaviors.""" + + +class OAuth2ProviderTransactionTestCase(OAuth2ProviderBase, DjangoTransactionTestCase): + """Place holder to allow overriding behaviors.""" diff --git a/tests/db_router.py b/tests/db_router.py new file mode 100644 index 000000000..7aa354ed8 --- /dev/null +++ b/tests/db_router.py @@ -0,0 +1,76 @@ +apps_in_beta = {"some_other_app", "this_one_too"} + +# These are bare minimum routers to fake the scenario where there is actually a +# decision around where an application's models might live. + + +class AlphaRouter: + # alpha is where the core Django models are stored including user. To keep things + # simple this is where the oauth2 provider models are stored as well because they + # have a foreign key to User. + + def db_for_read(self, model, **hints): + if model._meta.app_label not in apps_in_beta: + return "alpha" + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label not in apps_in_beta: + return "alpha" + return None + + def allow_relation(self, obj1, obj2, **hints): + if obj1._state.db == "alpha" and obj2._state.db == "alpha": + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if app_label not in apps_in_beta: + return db == "alpha" + return None + + +class BetaRouter: + def db_for_read(self, model, **hints): + if model._meta.app_label in apps_in_beta: + return "beta" + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label in apps_in_beta: + return "beta" + return None + + def allow_relation(self, obj1, obj2, **hints): + if obj1._state.db == "beta" and obj2._state.db == "beta": + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if app_label in apps_in_beta: + return db == "beta" + + +class CrossDatabaseRouter: + # alpha is where the core Django models are stored including user. To keep things + # simple this is where the oauth2 provider models are stored as well because they + # have a foreign key to User. + def db_for_read(self, model, **hints): + if model._meta.model_name == "accesstoken": + return "beta" + return None + + def db_for_write(self, model, **hints): + if model._meta.model_name == "accesstoken": + return "beta" + return None + + def allow_relation(self, obj1, obj2, **hints): + if obj1._state.db == "beta" and obj2._state.db == "beta": + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if model_name == "accesstoken": + return db == "beta" + return None diff --git a/tests/migrations/0007_add_localidtoken.py b/tests/migrations/0007_add_localidtoken.py new file mode 100644 index 000000000..f74cce5b6 --- /dev/null +++ b/tests/migrations/0007_add_localidtoken.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.25 on 2024-08-08 22:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('tests', '0006_basetestapplication_token_family'), + ] + + operations = [ + migrations.CreateModel( + name='LocalIDToken', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('jti', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='JWT Token ID')), + ('expires', models.DateTimeField()), + ('scope', models.TextField(blank=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tests_localidtoken', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/tests/models.py b/tests/models.py index 355bc1b57..9f3643db8 100644 --- a/tests/models.py +++ b/tests/models.py @@ -4,6 +4,7 @@ AbstractAccessToken, AbstractApplication, AbstractGrant, + AbstractIDToken, AbstractRefreshToken, ) from oauth2_provider.settings import oauth2_settings @@ -54,3 +55,9 @@ class SampleRefreshToken(AbstractRefreshToken): class SampleGrant(AbstractGrant): custom_field = models.CharField(max_length=255) + + +class LocalIDToken(AbstractIDToken): + """Exists to be improperly configured for multiple databases.""" + + # The other token types will be in 'alpha' database. diff --git a/tests/multi_db_settings.py b/tests/multi_db_settings.py new file mode 100644 index 000000000..a6daf04a3 --- /dev/null +++ b/tests/multi_db_settings.py @@ -0,0 +1,19 @@ +# Import the test settings and then override DATABASES. + +from .settings import * # noqa: F401, F403 + + +DATABASES = { + "alpha": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + }, + "beta": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + }, + # As https://docs.djangoproject.com/en/4.2/topics/db/multi-db/#defining-your-databases + # indicates, it is ok to have no default database. + "default": {}, +} +DATABASE_ROUTERS = ["tests.db_router.AlphaRouter", "tests.db_router.BetaRouter"] diff --git a/tests/multi_db_settings_invalid_token_configuration.py b/tests/multi_db_settings_invalid_token_configuration.py new file mode 100644 index 000000000..ed2804f79 --- /dev/null +++ b/tests/multi_db_settings_invalid_token_configuration.py @@ -0,0 +1,8 @@ +from .multi_db_settings import * # noqa: F401, F403 + + +OAUTH2_PROVIDER = { + # The other two tokens will be in alpha. This will cause a failure when the + # app's ready method is called. + "ID_TOKEN_MODEL": "tests.LocalIDToken", +} diff --git a/tests/test_application_views.py b/tests/test_application_views.py index c8c145d9b..88617807d 100644 --- a/tests/test_application_views.py +++ b/tests/test_application_views.py @@ -1,11 +1,11 @@ import pytest from django.contrib.auth import get_user_model -from django.test import TestCase from django.urls import reverse from oauth2_provider.models import get_application_model from oauth2_provider.views.application import ApplicationRegistration +from .common_testing import OAuth2ProviderTestCase as TestCase from .models import SampleApplication diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index b0ff145ab..49729b1c4 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import AnonymousUser from django.core.exceptions import SuspiciousOperation from django.http import HttpResponse -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.test.utils import modify_settings, override_settings from django.utils.timezone import now, timedelta @@ -13,6 +13,8 @@ from oauth2_provider.middleware import OAuth2ExtraTokenMiddleware, OAuth2TokenMiddleware from oauth2_provider.models import get_access_token_model, get_application_model +from .common_testing import OAuth2ProviderTestCase as TestCase + UserModel = get_user_model() ApplicationModel = get_application_model() diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index ae6e7e76e..122474950 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -7,7 +7,7 @@ import pytest from django.conf import settings from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from django.utils import timezone from django.utils.crypto import get_random_string @@ -23,6 +23,7 @@ from oauth2_provider.views import ProtectedResourceView from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header diff --git a/tests/test_client_credential.py b/tests/test_client_credential.py index 4c6e384d0..3572f432d 100644 --- a/tests/test_client_credential.py +++ b/tests/test_client_credential.py @@ -4,7 +4,7 @@ import pytest from django.contrib.auth import get_user_model from django.core.exceptions import SuspiciousOperation -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from django.views.generic import View from oauthlib.oauth2 import BackendApplicationServer @@ -16,6 +16,7 @@ from oauth2_provider.views.mixins import OAuthLibMixin from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header diff --git a/tests/test_commands.py b/tests/test_commands.py index 8861f5698..5204ebf77 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -5,11 +5,11 @@ from django.contrib.auth.hashers import check_password from django.core.management import call_command from django.core.management.base import CommandError -from django.test import TestCase from oauth2_provider.models import get_application_model from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase Application = get_application_model() diff --git a/tests/test_decorators.py b/tests/test_decorators.py index a8ee788b5..f91ada2ac 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,12 +1,14 @@ from datetime import timedelta from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.utils import timezone from oauth2_provider.decorators import protected_resource, rw_protected_resource from oauth2_provider.models import get_access_token_model, get_application_model +from .common_testing import OAuth2ProviderTestCase as TestCase + Application = get_application_model() AccessToken = get_access_token_model() diff --git a/tests/test_django_checks.py b/tests/test_django_checks.py new file mode 100644 index 000000000..77025b115 --- /dev/null +++ b/tests/test_django_checks.py @@ -0,0 +1,20 @@ +from django.core.management import call_command +from django.core.management.base import SystemCheckError +from django.test import override_settings + +from .common_testing import OAuth2ProviderTestCase as TestCase + + +class DjangoChecksTestCase(TestCase): + def test_checks_pass(self): + call_command("check") + + # CrossDatabaseRouter claims AccessToken is in beta while everything else is in alpha. + # This will cause the database checks to fail. + @override_settings( + DATABASE_ROUTERS=["tests.db_router.CrossDatabaseRouter", "tests.db_router.AlphaRouter"] + ) + def test_checks_fail_when_router_crosses_databases(self): + message = "The token models are expected to be stored in the same database." + with self.assertRaisesMessage(SystemCheckError, message): + call_command("check") diff --git a/tests/test_generator.py b/tests/test_generator.py index cc7928017..201200b00 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -1,8 +1,9 @@ import pytest -from django.test import TestCase from oauth2_provider.generators import BaseHashGenerator, generate_client_id, generate_client_secret +from .common_testing import OAuth2ProviderTestCase as TestCase + class MockHashGenerator(BaseHashGenerator): def hash(self): diff --git a/tests/test_hybrid.py b/tests/test_hybrid.py index 40cd8c56f..67c29a54e 100644 --- a/tests/test_hybrid.py +++ b/tests/test_hybrid.py @@ -5,7 +5,7 @@ import pytest from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from django.utils import timezone from jwcrypto import jwt @@ -21,6 +21,8 @@ from oauth2_provider.views import ProtectedResourceView, ScopedProtectedResourceView from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase +from .common_testing import retrieve_current_databases from .utils import get_basic_auth_header, spy_on @@ -1318,7 +1320,7 @@ def test_pre_auth_default_scopes(self): self.assertEqual(form["client_id"].value(), self.application.client_id) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_id_token_nonce_in_token_response(oauth2_settings, test_user, hybrid_application, client, oidc_key): client.force_login(test_user) @@ -1367,7 +1369,7 @@ def test_id_token_nonce_in_token_response(oauth2_settings, test_user, hybrid_app assert claims["nonce"] == "random_nonce_string" -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_claims_passed_to_code_generation( oauth2_settings, test_user, hybrid_application, client, mocker, oidc_key diff --git a/tests/test_implicit.py b/tests/test_implicit.py index 3f16cf71f..85e773d22 100644 --- a/tests/test_implicit.py +++ b/tests/test_implicit.py @@ -3,7 +3,7 @@ import pytest from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from jwcrypto import jwt @@ -11,6 +11,7 @@ from oauth2_provider.views import ProtectedResourceView from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase Application = get_application_model() diff --git a/tests/test_introspection_auth.py b/tests/test_introspection_auth.py index d96a013e3..e1a096428 100644 --- a/tests/test_introspection_auth.py +++ b/tests/test_introspection_auth.py @@ -6,7 +6,7 @@ from django.conf.urls import include from django.contrib.auth import get_user_model from django.http import HttpResponse -from django.test import TestCase, override_settings +from django.test import override_settings from django.urls import path from django.utils import timezone from oauthlib.common import Request @@ -18,6 +18,7 @@ from oauth2_provider.views import ScopedProtectedResourceView from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase try: diff --git a/tests/test_introspection_view.py b/tests/test_introspection_view.py index b82e922be..3db23bbcd 100644 --- a/tests/test_introspection_view.py +++ b/tests/test_introspection_view.py @@ -3,13 +3,14 @@ import pytest from django.contrib.auth import get_user_model -from django.test import TestCase +from django.db import router from django.urls import reverse from django.utils import timezone from oauth2_provider.models import get_access_token_model, get_application_model from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header @@ -343,5 +344,6 @@ def test_view_post_invalid_client_creds_plaintext(self): self.assertEqual(response.status_code, 403) def test_select_related_in_view_for_less_db_queries(self): - with self.assertNumQueries(1): + token_database = router.db_for_write(AccessToken) + with self.assertNumQueries(1, using=token_database): self.client.post(reverse("oauth2_provider:introspect")) diff --git a/tests/test_mixins.py b/tests/test_mixins.py index 327a99194..1cefa1334 100644 --- a/tests/test_mixins.py +++ b/tests/test_mixins.py @@ -3,7 +3,7 @@ import pytest from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.views.generic import View from oauthlib.oauth2 import Server @@ -18,6 +18,7 @@ ) from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase @pytest.mark.usefixtures("oauth2_settings") diff --git a/tests/test_models.py b/tests/test_models.py index 24e4ceafe..58765db69 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -6,7 +6,6 @@ from django.contrib.auth import get_user_model from django.contrib.auth.hashers import check_password from django.core.exceptions import ImproperlyConfigured, ValidationError -from django.test import TestCase from django.test.utils import override_settings from django.utils import timezone @@ -20,6 +19,8 @@ ) from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase +from .common_testing import retrieve_current_databases CLEARTEXT_SECRET = "1234567890abcdefghijklmnopqrstuvwxyz" @@ -466,7 +467,7 @@ def test_clear_expired_tokens_with_tokens(self): assert remaining_gt_count == initial_gt_count // 2, "half the remaining grants should still exist." -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_id_token_methods(oidc_tokens, rf): id_token = IDToken.objects.get() @@ -501,7 +502,7 @@ def test_id_token_methods(oidc_tokens, rf): assert IDToken.objects.filter(jti=id_token.jti).count() == 0 -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_clear_expired_id_tokens(oauth2_settings, oidc_tokens, rf): id_token = IDToken.objects.get() @@ -540,7 +541,7 @@ def test_clear_expired_id_tokens(oauth2_settings, oidc_tokens, rf): assert not IDToken.objects.filter(jti=id_token.jti).exists() -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_application_key(oauth2_settings, application): # RS256 key @@ -565,7 +566,7 @@ def test_application_key(oauth2_settings, application): assert "This application does not support signed tokens" == str(exc.value) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_application_clean(oauth2_settings, application): # RS256, RSA key is configured @@ -605,7 +606,7 @@ def test_application_clean(oauth2_settings, application): application.clean() -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.ALLOWED_SCHEMES_DEFAULT) def test_application_origin_allowed_default_https(oauth2_settings, cors_application): """Test that http schemes are not allowed because ALLOWED_SCHEMES allows only https""" @@ -613,7 +614,7 @@ def test_application_origin_allowed_default_https(oauth2_settings, cors_applicat assert not cors_application.origin_allowed("http://example.com") -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.ALLOWED_SCHEMES_HTTP) def test_application_origin_allowed_http(oauth2_settings, cors_application): """Test that http schemes are allowed because http was added to ALLOWED_SCHEMES""" diff --git a/tests/test_oauth2_backends.py b/tests/test_oauth2_backends.py index 21dd7a0c3..a4408f8e6 100644 --- a/tests/test_oauth2_backends.py +++ b/tests/test_oauth2_backends.py @@ -3,12 +3,13 @@ import pytest from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.utils.timezone import now, timedelta from oauth2_provider.backends import get_oauthlib_core from oauth2_provider.models import get_access_token_model, get_application_model, redirect_to_uri_allowed from oauth2_provider.oauth2_backends import JSONOAuthLibCore, OAuthLibCore +from tests.common_testing import OAuth2ProviderTestCase as TestCase try: diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index f499faf2d..31d97f64a 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -5,7 +5,6 @@ import pytest from django.contrib.auth import get_user_model from django.contrib.auth.hashers import make_password -from django.test import TestCase, TransactionTestCase from django.utils import timezone from jwcrypto import jwt from oauthlib.common import Request @@ -22,6 +21,9 @@ from oauth2_provider.oauth2_validators import OAuth2Validator from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase +from .common_testing import OAuth2ProviderTransactionTestCase as TransactionTestCase +from .common_testing import retrieve_current_databases from .utils import get_basic_auth_header @@ -552,7 +554,7 @@ def test_get_jwt_bearer_token(oauth2_settings, mocker): assert mock_get_id_token.call_args[1] == {} -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_validate_id_token_expired_jwt(oauth2_settings, mocker, oidc_tokens): mocker.patch("oauth2_provider.oauth2_validators.jwt.JWT", side_effect=jwt.JWTExpired) @@ -568,7 +570,7 @@ def test_validate_id_token_no_token(oauth2_settings, mocker): assert status is False -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_validate_id_token_app_removed(oauth2_settings, mocker, oidc_tokens): oidc_tokens.application.delete() @@ -577,7 +579,7 @@ def test_validate_id_token_app_removed(oauth2_settings, mocker, oidc_tokens): assert status is False -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) def test_validate_id_token_bad_token_no_aud(oauth2_settings, mocker, oidc_key): token = jwt.JWT(header=json.dumps({"alg": "RS256"}), claims=json.dumps({"bad": "token"})) diff --git a/tests/test_oidc_views.py b/tests/test_oidc_views.py index f44a808e7..8bdf18360 100644 --- a/tests/test_oidc_views.py +++ b/tests/test_oidc_views.py @@ -1,7 +1,7 @@ import pytest from django.contrib.auth import get_user from django.contrib.auth.models import AnonymousUser -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from django.utils import timezone from pytest_django.asserts import assertRedirects @@ -18,6 +18,8 @@ from oauth2_provider.views.oidc import RPInitiatedLogoutView, _load_id_token, _validate_claims from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase +from .common_testing import retrieve_current_databases @pytest.mark.usefixtures("oauth2_settings") @@ -220,7 +222,7 @@ def mock_request_for(user): return request -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_validate_logout_request(oidc_tokens, public_application, rp_settings): oidc_tokens = oidc_tokens application = oidc_tokens.application @@ -298,7 +300,7 @@ def test_validate_logout_request(oidc_tokens, public_application, rp_settings): ) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.parametrize("ALWAYS_PROMPT", [True, False]) def test_must_prompt(oidc_tokens, other_user, rp_settings, ALWAYS_PROMPT): rp_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT = ALWAYS_PROMPT @@ -319,14 +321,14 @@ def is_logged_in(client): return get_user(client).is_authenticated -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get(logged_in_client, rp_settings): rsp = logged_in_client.get(reverse("oauth2_provider:rp-initiated-logout"), data={}) assert rsp.status_code == 200 assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_id_token(logged_in_client, oidc_tokens, rp_settings): rsp = logged_in_client.get( reverse("oauth2_provider:rp-initiated-logout"), data={"id_token_hint": oidc_tokens.id_token} @@ -336,7 +338,7 @@ def test_rp_initiated_logout_get_id_token(logged_in_client, oidc_tokens, rp_sett assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_revoked_id_token(logged_in_client, oidc_tokens, rp_settings): validator = oauth2_settings.OAUTH2_VALIDATOR_CLASS() validator._load_id_token(oidc_tokens.id_token).revoke() @@ -347,7 +349,7 @@ def test_rp_initiated_logout_get_revoked_id_token(logged_in_client, oidc_tokens, assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_id_token_redirect(logged_in_client, oidc_tokens, rp_settings): rsp = logged_in_client.get( reverse("oauth2_provider:rp-initiated-logout"), @@ -358,7 +360,7 @@ def test_rp_initiated_logout_get_id_token_redirect(logged_in_client, oidc_tokens assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_id_token_redirect_with_state(logged_in_client, oidc_tokens, rp_settings): rsp = logged_in_client.get( reverse("oauth2_provider:rp-initiated-logout"), @@ -373,7 +375,7 @@ def test_rp_initiated_logout_get_id_token_redirect_with_state(logged_in_client, assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_id_token_missmatch_client_id( logged_in_client, oidc_tokens, public_application, rp_settings ): @@ -385,7 +387,7 @@ def test_rp_initiated_logout_get_id_token_missmatch_client_id( assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_public_client_redirect_client_id( logged_in_client, oidc_non_confidential_tokens, public_application, rp_settings ): @@ -401,7 +403,7 @@ def test_rp_initiated_logout_public_client_redirect_client_id( assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_public_client_strict_redirect_client_id( logged_in_client, oidc_non_confidential_tokens, public_application, oauth2_settings ): @@ -418,7 +420,7 @@ def test_rp_initiated_logout_public_client_strict_redirect_client_id( assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_get_client_id(logged_in_client, oidc_tokens, rp_settings): rsp = logged_in_client.get( reverse("oauth2_provider:rp-initiated-logout"), data={"client_id": oidc_tokens.application.client_id} @@ -427,7 +429,7 @@ def test_rp_initiated_logout_get_client_id(logged_in_client, oidc_tokens, rp_set assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_post(logged_in_client, oidc_tokens, rp_settings): form_data = { "client_id": oidc_tokens.application.client_id, @@ -437,7 +439,7 @@ def test_rp_initiated_logout_post(logged_in_client, oidc_tokens, rp_settings): assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_post_allowed(logged_in_client, oidc_tokens, rp_settings): form_data = {"client_id": oidc_tokens.application.client_id, "allow": True} rsp = logged_in_client.post(reverse("oauth2_provider:rp-initiated-logout"), form_data) @@ -446,7 +448,7 @@ def test_rp_initiated_logout_post_allowed(logged_in_client, oidc_tokens, rp_sett assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_rp_initiated_logout_post_no_session(client, oidc_tokens, rp_settings): form_data = {"client_id": oidc_tokens.application.client_id, "allow": True} rsp = client.post(reverse("oauth2_provider:rp-initiated-logout"), form_data) @@ -455,7 +457,7 @@ def test_rp_initiated_logout_post_no_session(client, oidc_tokens, rp_settings): assert not is_logged_in(client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT) def test_rp_initiated_logout_expired_tokens_accept(logged_in_client, application, expired_id_token): # Accepting expired (but otherwise valid and signed by us) tokens is enabled. Logout should go through. @@ -470,7 +472,7 @@ def test_rp_initiated_logout_expired_tokens_accept(logged_in_client, application assert not is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT_DENY_EXPIRED) def test_rp_initiated_logout_expired_tokens_deny(logged_in_client, application, expired_id_token): # Expired tokens should not be accepted by default. @@ -485,14 +487,14 @@ def test_rp_initiated_logout_expired_tokens_deny(logged_in_client, application, assert is_logged_in(logged_in_client) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT) def test_load_id_token_accept_expired(expired_id_token): id_token, _ = _load_id_token(expired_id_token) assert isinstance(id_token, get_id_token_model()) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT) def test_load_id_token_wrong_aud(id_token_wrong_aud): id_token, claims = _load_id_token(id_token_wrong_aud) @@ -500,7 +502,7 @@ def test_load_id_token_wrong_aud(id_token_wrong_aud): assert claims is None -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT_DENY_EXPIRED) def test_load_id_token_deny_expired(expired_id_token): id_token, claims = _load_id_token(expired_id_token) @@ -508,7 +510,7 @@ def test_load_id_token_deny_expired(expired_id_token): assert claims is None -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT) def test_validate_claims_wrong_iss(id_token_wrong_iss): id_token, claims = _load_id_token(id_token_wrong_iss) @@ -517,7 +519,7 @@ def test_validate_claims_wrong_iss(id_token_wrong_iss): assert not _validate_claims(mock_request(), claims) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT) def test_validate_claims(oidc_tokens): id_token, claims = _load_id_token(oidc_tokens.id_token) @@ -525,7 +527,7 @@ def test_validate_claims(oidc_tokens): assert _validate_claims(mock_request_for(oidc_tokens.user), claims) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.parametrize("method", ["get", "post"]) def test_userinfo_endpoint(oidc_tokens, client, method): auth_header = "Bearer %s" % oidc_tokens.access_token @@ -538,7 +540,7 @@ def test_userinfo_endpoint(oidc_tokens, client, method): assert data["sub"] == str(oidc_tokens.user.pk) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_userinfo_endpoint_bad_token(oidc_tokens, client): # No access token rsp = client.get(reverse("oauth2_provider:user-info")) @@ -551,7 +553,7 @@ def test_userinfo_endpoint_bad_token(oidc_tokens, client): assert rsp.status_code == 401 -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_token_deletion_on_logout(oidc_tokens, logged_in_client, rp_settings): AccessToken = get_access_token_model() IDToken = get_id_token_model() @@ -574,7 +576,7 @@ def test_token_deletion_on_logout(oidc_tokens, logged_in_client, rp_settings): assert all([token.revoked <= timezone.now() for token in RefreshToken.objects.all()]) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_token_deletion_on_logout_expired_session(oidc_tokens, client, rp_settings): AccessToken = get_access_token_model() IDToken = get_id_token_model() @@ -615,7 +617,7 @@ def test_token_deletion_on_logout_expired_session(oidc_tokens, client, rp_settin assert all(token.revoked <= timezone.now() for token in RefreshToken.objects.all()) -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RP_LOGOUT_KEEP_TOKENS) def test_token_deletion_on_logout_disabled(oidc_tokens, logged_in_client, rp_settings): rp_settings.OIDC_RP_INITIATED_LOGOUT_DELETE_TOKENS = False @@ -651,7 +653,7 @@ def claim_user_email(request): return EXAMPLE_EMAIL -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_userinfo_endpoint_custom_claims_callable(oidc_tokens, client, oauth2_settings): class CustomValidator(OAuth2Validator): oidc_claim_scope = None @@ -679,7 +681,7 @@ def get_additional_claims(self): assert data["email"] == EXAMPLE_EMAIL -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_userinfo_endpoint_custom_claims_email_scope_callable( oidc_email_scope_tokens, client, oauth2_settings ): @@ -706,7 +708,7 @@ def get_additional_claims(self): assert data["email"] == EXAMPLE_EMAIL -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_userinfo_endpoint_custom_claims_plain(oidc_tokens, client, oauth2_settings): class CustomValidator(OAuth2Validator): oidc_claim_scope = None @@ -734,7 +736,7 @@ def get_additional_claims(self, request): assert data["email"] == EXAMPLE_EMAIL -@pytest.mark.django_db +@pytest.mark.django_db(databases=retrieve_current_databases()) def test_userinfo_endpoint_custom_claims_email_scopeplain(oidc_email_scope_tokens, client, oauth2_settings): class CustomValidator(OAuth2Validator): def get_additional_claims(self, request): diff --git a/tests/test_password.py b/tests/test_password.py index ec9f17f54..65cf5a8b5 100644 --- a/tests/test_password.py +++ b/tests/test_password.py @@ -2,12 +2,13 @@ import pytest from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from oauth2_provider.models import get_application_model from oauth2_provider.views import ProtectedResourceView +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header diff --git a/tests/test_rest_framework.py b/tests/test_rest_framework.py index 84b4ad7d9..f8ff86f23 100644 --- a/tests/test_rest_framework.py +++ b/tests/test_rest_framework.py @@ -5,7 +5,6 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse -from django.test import TestCase from django.test.utils import override_settings from django.urls import path, re_path from django.utils import timezone @@ -25,6 +24,7 @@ from oauth2_provider.models import get_access_token_model, get_application_model from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase Application = get_application_model() diff --git a/tests/test_scopes.py b/tests/test_scopes.py index ec36da418..4dae0d3c4 100644 --- a/tests/test_scopes.py +++ b/tests/test_scopes.py @@ -4,12 +4,13 @@ import pytest from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from oauth2_provider.models import get_access_token_model, get_application_model, get_grant_model from oauth2_provider.views import ReadWriteScopedResourceView, ScopedProtectedResourceView +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header diff --git a/tests/test_settings.py b/tests/test_settings.py index f9f540339..b64fc31db 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,6 +1,5 @@ import pytest from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase from django.test.utils import override_settings from oauthlib.common import Request @@ -19,6 +18,7 @@ CustomIDTokenAdmin, CustomRefreshTokenAdmin, ) +from tests.common_testing import OAuth2ProviderTestCase as TestCase from . import presets diff --git a/tests/test_token_endpoint_cors.py b/tests/test_token_endpoint_cors.py index 791237b4a..6eaea6560 100644 --- a/tests/test_token_endpoint_cors.py +++ b/tests/test_token_endpoint_cors.py @@ -3,12 +3,13 @@ import pytest from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from oauth2_provider.models import get_application_model from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase from .utils import get_basic_auth_header diff --git a/tests/test_token_revocation.py b/tests/test_token_revocation.py index 4883e850c..fa836b6a2 100644 --- a/tests/test_token_revocation.py +++ b/tests/test_token_revocation.py @@ -1,12 +1,14 @@ import datetime from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase +from django.test import RequestFactory from django.urls import reverse from django.utils import timezone from oauth2_provider.models import get_access_token_model, get_application_model, get_refresh_token_model +from .common_testing import OAuth2ProviderTestCase as TestCase + Application = get_application_model() AccessToken = get_access_token_model() diff --git a/tests/test_token_view.py b/tests/test_token_view.py index fc73c2a66..63e76ed2f 100644 --- a/tests/test_token_view.py +++ b/tests/test_token_view.py @@ -1,12 +1,13 @@ import datetime from django.contrib.auth import get_user_model -from django.test import TestCase from django.urls import reverse from django.utils import timezone from oauth2_provider.models import get_access_token_model, get_application_model +from .common_testing import OAuth2ProviderTestCase as TestCase + Application = get_application_model() AccessToken = get_access_token_model() diff --git a/tests/test_validators.py b/tests/test_validators.py index a28e54a4d..eb382c154 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,9 +1,10 @@ import pytest from django.core.validators import ValidationError -from django.test import TestCase from oauth2_provider.validators import AllowedURIValidator +from .common_testing import OAuth2ProviderTestCase as TestCase + @pytest.mark.usefixtures("oauth2_settings") class TestAllowedURIValidator(TestCase): diff --git a/tox.ini b/tox.ini index fc1c24507..303b0d51d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ envlist = py{310,311,312}-dj50, py{310,311,312}-dj51, py{310,311,312}-djmain, + py39-multi-db-dj-42 [gh-actions] python = @@ -96,6 +97,12 @@ setenv = PYTHONWARNINGS = all commands = django-admin makemigrations --dry-run --check +[testenv:py39-multi-db-dj42] +setenv = + DJANGO_SETTINGS_MODULE = tests.multi_db_settings + PYTHONPATH = {toxinidir} + PYTHONWARNINGS = all + [testenv:migrate_swapped] setenv = DJANGO_SETTINGS_MODULE = tests.settings_swapped From 72d05513f9bcc790e1388c388ceb9d70a926b95c Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Wed, 4 Sep 2024 23:05:42 -0400 Subject: [PATCH 038/115] add link to new gh discussions (#1480) * add link to new gh discussions * fix typo in link --------- Co-authored-by: Darrel O'Pry --- README.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index dee670e4b..e8b49d2a6 100644 --- a/README.rst +++ b/README.rst @@ -114,6 +114,12 @@ info and the open especially those labeled `help-wanted `__. +Discussions +~~~~~~~~~~~ +Have questions or want to discuss the project? +See `the discussions `__. + + Submit PRs and Perform Reviews ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -134,6 +140,5 @@ release for the leads to deal with “unexpected” merged PRs. Become a Project Lead ~~~~~~~~~~~~~~~~~~~~~ -If you are interested in stepping up to be a Project Lead, please join -the -`discussion `__. +If you are interested in stepping up to be a Project Lead, please take a look at +the `discussion about this `__. From 5ce5e7fc8698bdb9956a3b98b1b5b6fb6c5670bf Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Thu, 5 Sep 2024 18:23:02 -0400 Subject: [PATCH 039/115] Release 3.0.0 Changlelog, version and minor version dependency updates. See also #1474 (#1485) --- CHANGELOG.md | 39 ++++++++++++++++++++++++------------- oauth2_provider/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce7d2294..2ba7e52f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,29 +14,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> -## [unreleased] +## [3.0.0] - 2024-09-05 + +### WARNING - POTENTIAL BREAKING CHANGES +* Changes to the `AbstractAccessToken` model require doing a `manage.py migrate` after upgrading. +* If you use swappable models you will need to make sure your custom models are also updated (usually `manage.py makemigrations`). +* Old Django versions below 4.2 are no longer supported. +* A few deprecations warned about in 2.4.0 (#1345) have been removed. See below. + ### Added -* Add migration to include `token_checksum` field in AbstractAccessToken model. -* Added compatibility with `LoginRequiredMiddleware` introduced in Django 5.1 -* #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION` +* #1366 Add Docker containerized apps for testing IDP and RP. +* #1454 Added compatibility with `LoginRequiredMiddleware` introduced in Django 5.1. + ### Changed -* Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims -* Update middleware, validators, and views to use token checksums instead of token for token retrieval and validation. -* #1446 use generic models pk instead of id. -* Transactions wrapping writes of the Tokens now rely on Django's database routers to determine the correct +* Many documentation and project internals improvements. +* #1446 Use generic models `pk` instead of `id`. This enables, for example, custom swapped models to have a different primary key field. +* #1447 Update token to TextField from CharField. Removing the 255 character limit enables supporting JWT tokens with additional claims. + This adds a SHA-256 `token_checksum` field that is used to validate tokens. +* #1450 Transactions wrapping writes of the Tokens now rely on Django's database routers to determine the correct database to use instead of assuming that 'default' is the correct one. -* Bump oauthlib version to 3.2.2 and above -* Update the OAuth2Validator's invalidate_authorization_code method to return an InvalidGrantError if the associated grant does not exist. +* #1455 Changed minimum supported Django version to >=4.2. -### Deprecated ### Removed * #1425 Remove deprecated `RedirectURIValidator`, `WildcardSet` per #1345; `validate_logout_request` per #1274 -* Remove support for Django versions below 4.2 ### Fixed -* #1443 Query strings with invalid hex values now raise a SuspiciousOperation exception (in DRF extension) instead of raising a 500 ValueError: Invalid hex encoding in query string. -* #1468 `ui_locales` request parameter triggers `AttributeError` under certain circumstances +* #1444, #1476 Fix several 500 errors to instead raise appropriate errors. +* #1469 Fix `ui_locales` request parameter triggers `AttributeError` under certain circumstances + ### Security +* #1452 Add a new setting [`REFRESH_TOKEN_REUSE_PROTECTION`](https://django-oauth-toolkit.readthedocs.io/en/latest/settings.html#refresh-token-reuse-protection). + In combination with [`ROTATE_REFRESH_TOKEN`](https://django-oauth-toolkit.readthedocs.io/en/latest/settings.html#rotate-refresh-token), + this prevents refresh tokens from being used more than once. See more at + [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#name-recommendations) +* #1481 Bump oauthlib version required to 3.2.2 and above to address [CVE-2022-36087](https://github.com/advisories/GHSA-3pgj-pg6c-r5p7). ## [2.4.0] - 2024-05-13 diff --git a/oauth2_provider/__init__.py b/oauth2_provider/__init__.py index 3d67cd6bb..528787cfc 100644 --- a/oauth2_provider/__init__.py +++ b/oauth2_provider/__init__.py @@ -1 +1 @@ -__version__ = "2.4.0" +__version__ = "3.0.0" diff --git a/pyproject.toml b/pyproject.toml index 84e800fe2..ccd154d4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "django >= 4.2", "requests >= 2.13.0", "oauthlib >= 3.2.2", - "jwcrypto >= 0.8.0", + "jwcrypto >= 1.5.0", ] [project.urls] From f2202357615d8f27a34c5605e0a6bd27cd0908c9 Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Fri, 6 Sep 2024 12:42:22 -0400 Subject: [PATCH 040/115] Fix test for changed error message from newer Django (djmain) (#1486) * fix djmain changes the error message text * remove unnecceesary verbose assert message and avoid E501 * conditionalize error message test based on Django version --- tests/test_commands.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index 5204ebf77..c4d359ce5 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -130,6 +130,8 @@ def test_application_created_with_algorithm(self): self.assertEqual(app.algorithm, "RS256") def test_validation_failed_message(self): + import django + output = StringIO() call_command( "createapplication", @@ -140,6 +142,10 @@ def test_validation_failed_message(self): stdout=output, ) - self.assertIn("user", output.getvalue()) - self.assertIn("783", output.getvalue()) - self.assertIn("does not exist", output.getvalue()) + output_str = output.getvalue() + self.assertIn("user", output_str) + self.assertIn("783", output_str) + if django.VERSION < (5, 2): + self.assertIn("does not exist", output_str) + else: + self.assertIn("is not a valid choice", output_str) From 1d19e54c926f475b4b090533cb23d184ae2f39e2 Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Sat, 7 Sep 2024 08:42:59 -0400 Subject: [PATCH 041/115] 3.0.1: fix for migration error on upgrade to 3.0.0 (#1491) --- CHANGELOG.md | 4 ++++ oauth2_provider/__init__.py | 2 +- .../migrations/0012_add_token_checksum.py | 20 ++++++++++++++++--- oauth2_provider/models.py | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba7e52f8..483336b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## [3.0.1] - 2024-09-07 +### Fixed +* #1491 Fix migration error when there are pre-existing Access Tokens. + ## [3.0.0] - 2024-09-05 ### WARNING - POTENTIAL BREAKING CHANGES diff --git a/oauth2_provider/__init__.py b/oauth2_provider/__init__.py index 528787cfc..055276878 100644 --- a/oauth2_provider/__init__.py +++ b/oauth2_provider/__init__.py @@ -1 +1 @@ -__version__ = "3.0.0" +__version__ = "3.0.1" diff --git a/oauth2_provider/migrations/0012_add_token_checksum.py b/oauth2_provider/migrations/0012_add_token_checksum.py index 7f62955e3..476c3b402 100644 --- a/oauth2_provider/migrations/0012_add_token_checksum.py +++ b/oauth2_provider/migrations/0012_add_token_checksum.py @@ -4,6 +4,16 @@ from django.db import migrations, models from oauth2_provider.settings import oauth2_settings +def forwards_func(apps, schema_editor): + """ + Forward migration touches every "old" accesstoken.token which will cause the checksum to be computed. + """ + AccessToken = apps.get_model(oauth2_settings.ACCESS_TOKEN_MODEL) + accesstokens = AccessToken._default_manager.all() + for accesstoken in accesstokens: + accesstoken.save(update_fields=['token_checksum']) + + class Migration(migrations.Migration): dependencies = [ ("oauth2_provider", "0011_refreshtoken_token_family"), @@ -14,13 +24,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name="accesstoken", name="token_checksum", - field=oauth2_provider.models.TokenChecksumField( - blank=True, db_index=True, max_length=64, unique=True - ), + field=oauth2_provider.models.TokenChecksumField(blank=True, null=True, max_length=64), ), migrations.AlterField( model_name="accesstoken", name="token", field=models.TextField(), ), + migrations.RunPython(forwards_func, migrations.RunPython.noop), + migrations.AlterField( + model_name='accesstoken', + name='token_checksum', + field=oauth2_provider.models.TokenChecksumField(blank=False, max_length=64, db_index=True, unique=True), + ), ] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 831fc551f..a987b4435 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -392,7 +392,7 @@ class AbstractAccessToken(models.Model): token = models.TextField() token_checksum = TokenChecksumField( max_length=64, - blank=True, + blank=False, unique=True, db_index=True, ) From 90d7300c06be80e565c6557f0b8f260968748634 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:08:14 -0400 Subject: [PATCH 042/115] [pre-commit.ci] pre-commit autoupdate (#1492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b124c7342..371333ad6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.6.4 hooks: - id: ruff args: [ --fix ] From a1538231ce6d076d42b63cb358dfec91d3c10740 Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Fri, 20 Sep 2024 11:39:20 -0400 Subject: [PATCH 043/115] deal with 404 or 405 validator error (#1499) * deal with 404 or 405 validator error (apparently varies with version of django) * refactor: more precise test name * mock the post request instead of POSTing to example.com --------- Co-authored-by: Darrel O'Pry --- tests/test_oauth2_validators.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index 31d97f64a..14c74506e 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -3,6 +3,7 @@ import json import pytest +import requests from django.contrib.auth import get_user_model from django.contrib.auth.hashers import make_password from django.utils import timezone @@ -501,18 +502,26 @@ def setUpTestData(cls): cls.introspection_token = "test_introspection_token" cls.validator = OAuth2Validator() - def test_response_when_auth_server_response_return_404(self): - with self.assertLogs(logger="oauth2_provider") as mock_log: - self.validator._get_token_from_authentication_server( - self.token, self.introspection_url, self.introspection_token, None - ) - self.assertIn( - "ERROR:oauth2_provider:Introspection: Failed to " - "get a valid response from authentication server. " - "Status code: 404, Reason: " - "Not Found.\nNoneType: None", - mock_log.output, - ) + def test_response_when_auth_server_response_not_200(self): + """ + Ensure we log the error when the authentication server returns a non-200 response. + """ + mock_response = requests.Response() + mock_response.status_code = 404 + mock_response.reason = "Not Found" + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock_response + with self.assertLogs(logger="oauth2_provider") as mock_log: + self.validator._get_token_from_authentication_server( + self.token, self.introspection_url, self.introspection_token, None + ) + self.assertIn( + "ERROR:oauth2_provider:Introspection: Failed to " + "get a valid response from authentication server. " + "Status code: 404, Reason: " + "Not Found.\nNoneType: None", + mock_log.output, + ) @pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) From 88f052634a3f958726880310e7a7f99f5a3f8a44 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:55:25 -0400 Subject: [PATCH 044/115] [pre-commit.ci] pre-commit autoupdate (#1497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5) - [github.com/sphinx-contrib/sphinx-lint: v0.9.1 → v1.0.0](https://github.com/sphinx-contrib/sphinx-lint/compare/v0.9.1...v1.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 371333ad6..a29f52aea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.4 + rev: v0.6.5 hooks: - id: ruff args: [ --fix ] @@ -17,7 +17,7 @@ repos: - id: mixed-line-ending args: ['--fix=lf'] - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.9.1 + rev: v1.0.0 hooks: - id: sphinx-lint # Configuration for codespell is in pyproject.toml From 610177e8840e3eec6aeca011478de5d44f149aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:05:24 -0400 Subject: [PATCH 045/115] Bump vite from 5.2.13 to 5.4.6 in /tests/app/rp (#1500) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.13 to 5.4.6. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.6/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.6/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/app/rp/package-lock.json | 364 +++++++++++++++++---------------- tests/app/rp/package.json | 2 +- 2 files changed, 185 insertions(+), 181 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 627ce8af4..b1836da61 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -20,7 +20,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.0.3" + "vite": "^5.4.6" } }, "node_modules/@ampproject/remapping": { @@ -45,9 +45,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -61,9 +61,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -77,9 +77,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -93,9 +93,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -109,9 +109,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -125,9 +125,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -141,9 +141,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -157,9 +157,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -173,9 +173,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -189,9 +189,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -205,9 +205,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -221,9 +221,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -237,9 +237,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -253,9 +253,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -269,9 +269,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -285,9 +285,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -301,9 +301,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -317,9 +317,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -333,9 +333,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -349,9 +349,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -365,9 +365,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -381,9 +381,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -397,9 +397,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -656,9 +656,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", + "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", "cpu": [ "arm" ], @@ -669,9 +669,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", + "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", "cpu": [ "arm64" ], @@ -682,9 +682,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", + "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", "cpu": [ "arm64" ], @@ -695,9 +695,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", + "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", "cpu": [ "x64" ], @@ -708,9 +708,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", + "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", "cpu": [ "arm" ], @@ -721,9 +721,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", + "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", "cpu": [ "arm" ], @@ -734,9 +734,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", + "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", "cpu": [ "arm64" ], @@ -747,9 +747,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", + "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", + "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", "cpu": [ "ppc64" ], @@ -773,9 +773,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", + "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", "cpu": [ "riscv64" ], @@ -786,9 +786,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", + "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", "cpu": [ "s390x" ], @@ -799,9 +799,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", + "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", "cpu": [ "x64" ], @@ -812,9 +812,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", + "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", "cpu": [ "x64" ], @@ -825,9 +825,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", + "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", "cpu": [ "arm64" ], @@ -838,9 +838,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", + "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", "cpu": [ "ia32" ], @@ -851,9 +851,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", + "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", "cpu": [ "x64" ], @@ -1274,9 +1274,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -1286,29 +1286,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/esm-env": { @@ -1791,9 +1791,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -1809,9 +1809,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -1829,8 +1829,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1951,9 +1951,9 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", + "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1966,22 +1966,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.21.3", + "@rollup/rollup-android-arm64": "4.21.3", + "@rollup/rollup-darwin-arm64": "4.21.3", + "@rollup/rollup-darwin-x64": "4.21.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", + "@rollup/rollup-linux-arm-musleabihf": "4.21.3", + "@rollup/rollup-linux-arm64-gnu": "4.21.3", + "@rollup/rollup-linux-arm64-musl": "4.21.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", + "@rollup/rollup-linux-riscv64-gnu": "4.21.3", + "@rollup/rollup-linux-s390x-gnu": "4.21.3", + "@rollup/rollup-linux-x64-gnu": "4.21.3", + "@rollup/rollup-linux-x64-musl": "4.21.3", + "@rollup/rollup-win32-arm64-msvc": "4.21.3", + "@rollup/rollup-win32-ia32-msvc": "4.21.3", + "@rollup/rollup-win32-x64-msvc": "4.21.3", "fsevents": "~2.3.2" } }, @@ -2095,9 +2095,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2312,14 +2312,14 @@ } }, "node_modules/vite": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", - "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -2338,6 +2338,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -2355,6 +2356,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 8caf72fe6..7f784006f 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -21,7 +21,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.0.3" + "vite": "^5.4.6" }, "type": "module", "dependencies": { From e34819a51da022279c4b0b14dfdae8d01adf5d27 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Fri, 20 Sep 2024 12:13:38 -0400 Subject: [PATCH 046/115] feat: VS Code Testing Activity Support (#1501) --- .env | 2 ++ .vscode/settings.json | 8 ++++++++ docs/contributing.rst | 21 +++++++++++++++++++++ pyproject.toml | 8 ++++++++ 4 files changed, 39 insertions(+) create mode 100644 .env create mode 100644 .vscode/settings.json diff --git a/.env b/.env new file mode 100644 index 000000000..dc223bf0b --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +# required for vscode testing activity to discover tests +DJANGO_SETTINGS_MODULE=tests.settings \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..fee847fe4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.testing.pytestArgs": [ + "tests", + "--no-cov" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/docs/contributing.rst b/docs/contributing.rst index 648993024..4f0b88b32 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -272,6 +272,27 @@ In test code, anywhere the database is referenced the Django router needs to be Without the 'using' option, this test fails in the multiple database scenario because 'default' will be used instead. +Debugging the Tests Interactively +--------------------------------- + +Interactive Debugging allows you to set breakpoints and inspect the state of the program at runtime. We strongly +recommend using an interactive debugger to streamline your development process. + +VS Code +^^^^^^^ + +VS Code is a popular IDE that supports debugging Python code. You can debug the tests interactively in VS Code by +following these steps: + +.. code-block:: bash + + pip install .[dev] + # open the project in VS Code + # click Testing (erlenmeyer flask) on the Activity Bar + # select the test you want to run or debug + + + Code conventions matter ----------------------- diff --git a/pyproject.toml b/pyproject.toml index ccd154d4d..401d33cab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,14 @@ dependencies = [ "jwcrypto >= 1.5.0", ] +[project.optional-dependencies] +dev = [ + "pytest", + "pytest-cov", + "m2r", + "sphinx-rtd-theme", +] + [project.urls] Homepage = "https://django-oauth-toolkit.readthedocs.io/" Repository = "https://github.com/jazzband/django-oauth-toolkit" From 937ae211d7c239cde79359ca85881cad5177dbbb Mon Sep 17 00:00:00 2001 From: Matej Spiller Muys Date: Sun, 22 Sep 2024 19:56:25 +0200 Subject: [PATCH 047/115] Support for specifying client secret hasher (#1498) Co-authored-by: Matej Spiller Muys --- AUTHORS | 1 + CHANGELOG.md | 1 + docs/settings.rst | 4 ++++ oauth2_provider/models.py | 2 +- oauth2_provider/settings.py | 1 + tests/custom_hasher.py | 10 ++++++++++ tests/settings.py | 2 ++ tests/test_models.py | 16 ++++++++++++++++ 8 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/custom_hasher.py diff --git a/AUTHORS b/AUTHORS index 431edeabd..d10ff1fb4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -86,6 +86,7 @@ Ludwig Hähne Łukasz Skarżyński Madison Swain-Bowden Marcus Sonestedt +Matej Spiller Muys Matias Seniquiel Michael Howitz Owen Gong diff --git a/CHANGELOG.md b/CHANGELOG.md index 483336b04..39e11d4b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +--> + ## [3.0.1] - 2024-09-07 ### Fixed diff --git a/docs/settings.rst b/docs/settings.rst index 0b76129f9..545736ccb 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -63,6 +63,37 @@ assigned ports. Note that you may override ``Application.get_allowed_schemes()`` to set this on a per-application basis. +ALLOW_URI_WILDCARDS +~~~~~~~~~~~~~~~~~~~ + +Default: ``False`` + +SECURITY WARNING: Enabling this setting can introduce security vulnerabilities. Only enable +this setting if you understand the risks. https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2 +states "The redirection endpoint URI MUST be an absolute URI as defined by [RFC3986] Section 4.3." The +intent of the URI restrictions is to prevent open redirects and phishing attacks. If you do enable this +ensure that the wildcards restrict URIs to resources under your control. You are strongly encouragd not +to use this feature in production. + +When set to ``True``, the server will allow wildcard characters in the domains for allowed_origins and +redirect_uris. + +``*`` is the only wildcard character allowed. + +``*`` can only be used as a prefix to a domain, must be the first character in +the domain, and cannot be in the top or second level domain. Matching is done using an +endsWith check. + +For example, +``https://*.example.com`` is allowed, +``https://*-myproject.example.com`` is allowed, +``https://*.sub.example.com`` is not allowed, +``https://*.com`` is not allowed, and +``https://example.*.com`` is not allowed. + +This feature is useful for working with CI service such as cloudflare, netlify, and vercel that offer branch +deployments for development previews and user acceptance testing. + ALLOWED_SCHEMES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 621ce5b34..0467ddfa1 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -213,7 +213,11 @@ def clean(self): if redirect_uris: validator = AllowedURIValidator( - allowed_schemes, name="redirect uri", allow_path=True, allow_query=True + allowed_schemes, + name="redirect uri", + allow_path=True, + allow_query=True, + allow_hostname_wildcard=oauth2_settings.ALLOW_URI_WILDCARDS, ) for uri in redirect_uris: validator(uri) @@ -227,7 +231,11 @@ def clean(self): allowed_origins = self.allowed_origins.strip().split() if allowed_origins: # oauthlib allows only https scheme for CORS - validator = AllowedURIValidator(oauth2_settings.ALLOWED_SCHEMES, "allowed origin") + validator = AllowedURIValidator( + oauth2_settings.ALLOWED_SCHEMES, + "allowed origin", + allow_hostname_wildcard=oauth2_settings.ALLOW_URI_WILDCARDS, + ) for uri in allowed_origins: validator(uri) @@ -777,12 +785,28 @@ def redirect_to_uri_allowed(uri, allowed_uris): :param allowed_uris: A list of URIs that are allowed """ + if not isinstance(allowed_uris, list): + raise ValueError("allowed_uris must be a list") + parsed_uri = urlparse(uri) uqs_set = set(parse_qsl(parsed_uri.query)) for allowed_uri in allowed_uris: parsed_allowed_uri = urlparse(allowed_uri) + if parsed_allowed_uri.scheme != parsed_uri.scheme: + # match failed, continue + continue + + """ check hostname """ + if oauth2_settings.ALLOW_URI_WILDCARDS and parsed_allowed_uri.hostname.startswith("*"): + """ wildcard hostname """ + if not parsed_uri.hostname.endswith(parsed_allowed_uri.hostname[1:]): + continue + elif parsed_allowed_uri.hostname != parsed_uri.hostname: + continue + # From RFC 8252 (Section 7.3) + # https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 # # Loopback redirect URIs use the "http" scheme # [...] @@ -790,26 +814,26 @@ def redirect_to_uri_allowed(uri, allowed_uris): # time of the request for loopback IP redirect URIs, to accommodate # clients that obtain an available ephemeral port from the operating # system at the time of the request. + allowed_uri_is_loopback = parsed_allowed_uri.scheme == "http" and parsed_allowed_uri.hostname in [ + "127.0.0.1", + "::1", + ] + """ check port """ + if not allowed_uri_is_loopback and parsed_allowed_uri.port != parsed_uri.port: + continue + + """ check path """ + if parsed_allowed_uri.path != parsed_uri.path: + continue + + """ check querystring """ + aqs_set = set(parse_qsl(parsed_allowed_uri.query)) + if not aqs_set.issubset(uqs_set): + continue # circuit break - allowed_uri_is_loopback = ( - parsed_allowed_uri.scheme == "http" - and parsed_allowed_uri.hostname in ["127.0.0.1", "::1"] - and parsed_allowed_uri.port is None - ) - if ( - allowed_uri_is_loopback - and parsed_allowed_uri.scheme == parsed_uri.scheme - and parsed_allowed_uri.hostname == parsed_uri.hostname - and parsed_allowed_uri.path == parsed_uri.path - ) or ( - parsed_allowed_uri.scheme == parsed_uri.scheme - and parsed_allowed_uri.netloc == parsed_uri.netloc - and parsed_allowed_uri.path == parsed_uri.path - ): - aqs_set = set(parse_qsl(parsed_allowed_uri.query)) - if aqs_set.issubset(uqs_set): - return True + return True + # if uris matched then it's not allowed return False @@ -833,4 +857,5 @@ def is_origin_allowed(origin, allowed_origins): and parsed_allowed_origin.netloc == parsed_origin.netloc ): return True + return False diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index f5a6a25d6..9771aa4e7 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -71,6 +71,7 @@ "REQUEST_APPROVAL_PROMPT": "force", "ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https"], "ALLOWED_SCHEMES": ["https"], + "ALLOW_URI_WILDCARDS": False, "OIDC_ENABLED": False, "OIDC_ISS_ENDPOINT": "", "OIDC_USERINFO_ENDPOINT": "", diff --git a/oauth2_provider/validators.py b/oauth2_provider/validators.py index b238b12d6..b2370cfd0 100644 --- a/oauth2_provider/validators.py +++ b/oauth2_provider/validators.py @@ -21,7 +21,15 @@ class URIValidator(URLValidator): class AllowedURIValidator(URIValidator): # TODO: find a way to get these associated with their form fields in place of passing name # TODO: submit PR to get `cause` included in the parent class ValidationError params` - def __init__(self, schemes, name, allow_path=False, allow_query=False, allow_fragments=False): + def __init__( + self, + schemes, + name, + allow_path=False, + allow_query=False, + allow_fragments=False, + allow_hostname_wildcard=False, + ): """ :param schemes: List of allowed schemes. E.g.: ["https"] :param name: Name of the validated URI. It is required for validation message. E.g.: "Origin" @@ -34,6 +42,7 @@ def __init__(self, schemes, name, allow_path=False, allow_query=False, allow_fra self.allow_path = allow_path self.allow_query = allow_query self.allow_fragments = allow_fragments + self.allow_hostname_wildcard = allow_hostname_wildcard def __call__(self, value): value = force_str(value) @@ -68,8 +77,57 @@ def __call__(self, value): params={"name": self.name, "value": value, "cause": "path not allowed"}, ) + if self.allow_hostname_wildcard and "*" in netloc: + domain_parts = netloc.split(".") + if netloc.count("*") > 1: + raise ValidationError( + "%(name)s URI validation error. %(cause)s: %(value)s", + params={ + "name": self.name, + "value": value, + "cause": "only one wildcard is allowed in the hostname", + }, + ) + if not netloc.startswith("*"): + raise ValidationError( + "%(name)s URI validation error. %(cause)s: %(value)s", + params={ + "name": self.name, + "value": value, + "cause": "wildcards must be at the beginning of the hostname", + }, + ) + if len(domain_parts) < 3: + raise ValidationError( + "%(name)s URI validation error. %(cause)s: %(value)s", + params={ + "name": self.name, + "value": value, + "cause": "wildcards cannot be in the top level or second level domain", + }, + ) + + # strip the wildcard from the netloc, we'll reassamble the value later to pass to URI Validator + if netloc.startswith("*."): + netloc = netloc[2:] + else: + netloc = netloc[1:] + + # domains cannot start with a hyphen, but can have them in the middle, so we strip hyphens + # after the wildcard so the final domain is valid and will succeed in URIVAlidator + if netloc.startswith("-"): + netloc = netloc[1:] + + # we stripped the wildcard from the netloc and path if they were allowed and present since they would + # fail validation we'll reassamble the URI to pass to the URIValidator + reassambled_uri = f"{scheme}://{netloc}{path}" + if query: + reassambled_uri += f"?{query}" + if fragment: + reassambled_uri += f"#{fragment}" + try: - super().__call__(value) + super().__call__(reassambled_uri) except ValidationError as e: raise ValidationError( "%(name)s URI validation error. %(cause)s: %(value)s", diff --git a/tests/test_application_views.py b/tests/test_application_views.py index 88617807d..d4c7e28a9 100644 --- a/tests/test_application_views.py +++ b/tests/test_application_views.py @@ -63,6 +63,156 @@ def test_application_registration_user(self): self.assertEqual(app.algorithm, form_data["algorithm"]) +@pytest.mark.usefixtures("oauth2_settings") +@pytest.mark.oauth2_settings({"ALLOW_URI_WILDCARDS": True}) +class TestApplicationRegistrationViewRedirectURIWithWildcard(BaseTest): + def _test_valid(self, redirect_uri): + self.client.login(username="foo_user", password="123456") + + form_data = { + "name": "Foo app", + "client_id": "client_id", + "client_secret": "client_secret", + "client_type": Application.CLIENT_CONFIDENTIAL, + "redirect_uris": redirect_uri, + "post_logout_redirect_uris": "http://example.com", + "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, + "algorithm": "", + } + + response = self.client.post(reverse("oauth2_provider:register"), form_data) + self.assertEqual(response.status_code, 302) + + app = get_application_model().objects.get(name="Foo app") + self.assertEqual(app.user.username, "foo_user") + app = Application.objects.get() + self.assertEqual(app.name, form_data["name"]) + self.assertEqual(app.client_id, form_data["client_id"]) + self.assertEqual(app.redirect_uris, form_data["redirect_uris"]) + self.assertEqual(app.post_logout_redirect_uris, form_data["post_logout_redirect_uris"]) + self.assertEqual(app.client_type, form_data["client_type"]) + self.assertEqual(app.authorization_grant_type, form_data["authorization_grant_type"]) + self.assertEqual(app.algorithm, form_data["algorithm"]) + + def _test_invalid(self, uri, error_message): + self.client.login(username="foo_user", password="123456") + + form_data = { + "name": "Foo app", + "client_id": "client_id", + "client_secret": "client_secret", + "client_type": Application.CLIENT_CONFIDENTIAL, + "redirect_uris": uri, + "post_logout_redirect_uris": "http://example.com", + "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, + "algorithm": "", + } + + response = self.client.post(reverse("oauth2_provider:register"), form_data) + self.assertEqual(response.status_code, 200) + self.assertContains(response, error_message) + + def test_application_registration_valid_3ld_wildcard(self): + self._test_valid("https://*.example.com") + + def test_application_registration_valid_3ld_partial_wildcard(self): + self._test_valid("https://*-partial.example.com") + + def test_application_registration_invalid_star(self): + self._test_invalid("*", "invalid_scheme: *") + + def test_application_registration_invalid_tld_wildcard(self): + self._test_invalid("https://*", "wildcards cannot be in the top level or second level domain") + + def test_application_registration_invalid_tld_partial_wildcard(self): + self._test_invalid("https://*-partial", "wildcards cannot be in the top level or second level domain") + + def test_application_registration_invalid_tld_not_startswith_wildcard_tld(self): + self._test_invalid("https://example.*", "wildcards must be at the beginning of the hostname") + + def test_application_registration_invalid_2ld_wildcard(self): + self._test_invalid("https://*.com", "wildcards cannot be in the top level or second level domain") + + def test_application_registration_invalid_2ld_partial_wildcard(self): + self._test_invalid( + "https://*-partial.com", "wildcards cannot be in the top level or second level domain" + ) + + def test_application_registration_invalid_2ld_not_startswith_wildcard_tld(self): + self._test_invalid("https://example.*.com", "wildcards must be at the beginning of the hostname") + + def test_application_registration_invalid_3ld_partial_not_startswith_wildcard_2ld(self): + self._test_invalid( + "https://invalid-*.example.com", "wildcards must be at the beginning of the hostname" + ) + + def test_application_registration_invalid_4ld_not_startswith_wildcard_3ld(self): + self._test_invalid( + "https://invalid.*.invalid.example.com", + "wildcards must be at the beginning of the hostname", + ) + + def test_application_registration_invalid_4ld_partial_not_startswith_wildcard_2ld(self): + self._test_invalid( + "https://invalid-*.invalid.example.com", + "wildcards must be at the beginning of the hostname", + ) + + +@pytest.mark.usefixtures("oauth2_settings") +@pytest.mark.oauth2_settings({"ALLOW_URI_WILDCARDS": True}) +class TestApplicationRegistrationViewAllowedOriginWithWildcard( + TestApplicationRegistrationViewRedirectURIWithWildcard +): + def _test_valid(self, uris): + self.client.login(username="foo_user", password="123456") + + form_data = { + "name": "Foo app", + "client_id": "client_id", + "client_secret": "client_secret", + "client_type": Application.CLIENT_CONFIDENTIAL, + "allowed_origins": uris, + "redirect_uris": "https://example.com", + "post_logout_redirect_uris": "http://example.com", + "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, + "algorithm": "", + } + + response = self.client.post(reverse("oauth2_provider:register"), form_data) + self.assertEqual(response.status_code, 302) + + app = get_application_model().objects.get(name="Foo app") + self.assertEqual(app.user.username, "foo_user") + app = Application.objects.get() + self.assertEqual(app.name, form_data["name"]) + self.assertEqual(app.client_id, form_data["client_id"]) + self.assertEqual(app.redirect_uris, form_data["redirect_uris"]) + self.assertEqual(app.post_logout_redirect_uris, form_data["post_logout_redirect_uris"]) + self.assertEqual(app.client_type, form_data["client_type"]) + self.assertEqual(app.authorization_grant_type, form_data["authorization_grant_type"]) + self.assertEqual(app.algorithm, form_data["algorithm"]) + + def _test_invalid(self, uri, error_message): + self.client.login(username="foo_user", password="123456") + + form_data = { + "name": "Foo app", + "client_id": "client_id", + "client_secret": "client_secret", + "client_type": Application.CLIENT_CONFIDENTIAL, + "allowed_origins": uri, + "redirect_uris": "http://example.com", + "post_logout_redirect_uris": "http://example.com", + "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, + "algorithm": "", + } + + response = self.client.post(reverse("oauth2_provider:register"), form_data) + self.assertEqual(response.status_code, 200) + self.assertContains(response, error_message) + + class TestApplicationViews(BaseTest): @classmethod def _create_application(cls, name, user): diff --git a/tests/test_models.py b/tests/test_models.py index 123c41b35..32ca07627 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -16,6 +16,7 @@ get_grant_model, get_id_token_model, get_refresh_token_model, + redirect_to_uri_allowed, ) from . import presets @@ -622,6 +623,79 @@ def test_application_clean(oauth2_settings, application): application.clean() +def _test_wildcard_redirect_uris_valid(oauth2_settings, application, uris): + oauth2_settings.ALLOW_URI_WILDCARDS = True + application.redirect_uris = uris + application.clean() + + +def _test_wildcard_redirect_uris_invalid(oauth2_settings, application, uris): + oauth2_settings.ALLOW_URI_WILDCARDS = True + application.redirect_uris = uris + with pytest.raises(ValidationError): + application.clean() + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_valid_3ld(oauth2_settings, application): + _test_wildcard_redirect_uris_valid(oauth2_settings, application, "https://*.example.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_valid_partial_3ld(oauth2_settings, application): + _test_wildcard_redirect_uris_valid(oauth2_settings, application, "https://*-partial.example.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_3ld_not_starting_with_wildcard( + oauth2_settings, application +): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://invalid-*.example.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_2ld(oauth2_settings, application): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://*.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_partial_2ld(oauth2_settings, application): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://*-partial.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_2ld_not_starting_with_wildcard( + oauth2_settings, application +): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://invalid-*.com/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_tld(oauth2_settings, application): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://*/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_tld_partial(oauth2_settings, application): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://*-partial/path") + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +@pytest.mark.oauth2_settings(presets.OIDC_SETTINGS_RW) +def test_application_clean_wildcard_redirect_uris_invalid_tld_not_starting_with_wildcard( + oauth2_settings, application +): + _test_wildcard_redirect_uris_invalid(oauth2_settings, application, "https://invalid-*/path") + + @pytest.mark.django_db(databases=retrieve_current_databases()) @pytest.mark.oauth2_settings(presets.ALLOWED_SCHEMES_DEFAULT) def test_application_origin_allowed_default_https(oauth2_settings, cors_application): @@ -636,3 +710,35 @@ def test_application_origin_allowed_http(oauth2_settings, cors_application): """Test that http schemes are allowed because http was added to ALLOWED_SCHEMES""" assert cors_application.origin_allowed("https://example.com") assert cors_application.origin_allowed("http://example.com") + + +def test_redirect_to_uri_allowed_expects_allowed_uri_list(): + with pytest.raises(ValueError): + redirect_to_uri_allowed("https://example.com", "https://example.com") + assert redirect_to_uri_allowed("https://example.com", ["https://example.com"]) + + +valid_wildcard_redirect_to_params = [ + ("https://valid.example.com", ["https://*.example.com"]), + ("https://valid.valid.example.com", ["https://*.example.com"]), + ("https://valid-partial.example.com", ["https://*-partial.example.com"]), + ("https://valid.valid-partial.example.com", ["https://*-partial.example.com"]), +] + + +@pytest.mark.parametrize("uri, allowed_uri", valid_wildcard_redirect_to_params) +def test_wildcard_redirect_to_uri_allowed_valid(uri, allowed_uri, oauth2_settings): + oauth2_settings.ALLOW_URI_WILDCARDS = True + assert redirect_to_uri_allowed(uri, allowed_uri) + + +invalid_wildcard_redirect_to_params = [ + ("https://invalid.com", ["https://*.example.com"]), + ("https://invalid.example.com", ["https://*-partial.example.com"]), +] + + +@pytest.mark.parametrize("uri, allowed_uri", invalid_wildcard_redirect_to_params) +def test_wildcard_redirect_to_uri_allowed_invalid(uri, allowed_uri, oauth2_settings): + oauth2_settings.ALLOW_URI_WILDCARDS = True + assert not redirect_to_uri_allowed(uri, allowed_uri) diff --git a/tests/test_validators.py b/tests/test_validators.py index eb382c154..a77a1e16a 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -171,3 +171,27 @@ def test_allow_fragment_invalid_urls(self): for uri in bad_uris: with self.assertRaises(ValidationError): validator(uri) + + def test_allow_hostname_wildcard(self): + validator = AllowedURIValidator(["https"], "test", allow_hostname_wildcard=True) + good_uris = [ + "https://*.example.com", + "https://*-partial.example.com", + "https://*.partial.example.com", + "https://*-partial.valid.example.com", + ] + for uri in good_uris: + # Check ValidationError not thrown + validator(uri) + + bad_uris = [ + "https://*/", + "https://*-partial", + "https://*.com", + "https://*-partial.com", + "https://*.*.example.com", + "https://invalid.*.example.com", + ] + for uri in bad_uris: + with self.assertRaises(ValidationError): + validator(uri) From ce34da45a5254c44726e33ff68e4185eb673ceba Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Mon, 7 Oct 2024 19:20:37 +0200 Subject: [PATCH 052/115] Add client_secret to sensitive_post_parameters (#1512) The client_secret is posted to the token endpoint when using the client_credentials grant. --- oauth2_provider/views/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index 1e0d12dea..c5c904b14 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -292,7 +292,7 @@ class TokenView(OAuthLibMixin, View): * Client credentials """ - @method_decorator(sensitive_post_parameters("password")) + @method_decorator(sensitive_post_parameters("password", "client_secret")) def post(self, request, *args, **kwargs): url, headers, body, status = self.create_token_response(request) if status == 200: From eed322a33392521b3cb8e440426a1bc3d53de887 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:59:13 -0400 Subject: [PATCH 053/115] [pre-commit.ci] pre-commit autoupdate (#1513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9) - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2046a327..7a50f8d2b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.6.9 hooks: - id: ruff args: [ --fix ] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-ast - id: trailing-whitespace From f2f2e247bee66a8db692734fa2d431aeffbf3e60 Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Mon, 7 Oct 2024 18:03:31 -0400 Subject: [PATCH 054/115] bump to actions/setup-node@v4 (#1514) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0bf9f155..d0521b5bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,7 +78,7 @@ jobs: uses: actions/checkout@v4 - name: Set up NodeJS - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} From 28b512a677e8d916392865dd2802616285b864d5 Mon Sep 17 00:00:00 2001 From: Dulmandakh Date: Tue, 8 Oct 2024 06:14:53 +0800 Subject: [PATCH 055/115] Bump actions/cache to v4 (#1516) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d0521b5bc..e3073dc7a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: From 59bd7af7d6c2b1126e28ea40c5a7618bbbe754ab Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Wed, 9 Oct 2024 11:11:17 -0400 Subject: [PATCH 056/115] fix: OP prompts for logout when no OP session (#1517) The OAuth provider is prompting users who no longer have an user session with the OAuth Provider to logout of the OP. This happens in scenarios given the user has logged out of the OP directly or via another client. In cases where the user does not have a session on the OP we should not prompt them to log out of the OP as there is no session, but we should still clear out their tokens to terminate the session for the Application. --- CHANGELOG.md | 6 +++- oauth2_provider/views/oidc.py | 67 ++++++++++++++++++++++++++++++----- tests/test_oidc_views.py | 35 ++++++++++++++---- 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b1d8b78..f16317265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added -* Support for Wildcard Origin and Redirect URIs, https://github.com/jazzband/django-oauth-toolkit/issues/1506 +* #1506 Support for Wildcard Origin and Redirect URIs ### Fixed +* #1517 OP prompts for logout when no OP session +* #1512 client_secret not marked sensitive + diff --git a/oauth2_provider/views/oidc.py b/oauth2_provider/views/oidc.py index c746c30ce..a252f1be4 100644 --- a/oauth2_provider/views/oidc.py +++ b/oauth2_provider/views/oidc.py @@ -367,17 +367,66 @@ def validate_logout_request(self, id_token_hint, client_id, post_logout_redirect return application, id_token.user if id_token else None def must_prompt(self, token_user): - """Indicate whether the logout has to be confirmed by the user. This happens if the - specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`. + """ + per: https://openid.net/specs/openid-connect-rpinitiated-1_0.html + + > At the Logout Endpoint, the OP SHOULD ask the End-User whether to log + > out of the OP as well. Furthermore, the OP MUST ask the End-User this + > question if an id_token_hint was not provided or if the supplied ID + > Token does not belong to the current OP session with the RP and/or + > currently logged in End-User. - A logout without user interaction (i.e. no prompt) is only allowed - if an ID Token is provided that matches the current user. """ - return ( - oauth2_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT - or token_user is None - or token_user != self.request.user - ) + + if not self.request.user.is_authenticated: + """ + > the OP MUST ask ask the End-User whether to log out of the OP as + + If the user does not have an active session with the OP, they cannot + end their OP session, so there is nothing to prompt for. This occurs + in cases where the user has logged out of the OP via another channel + such as the OP's own logout page, session timeout or another RP's + logout page. + """ + return False + + if oauth2_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT: + """ + > At the Logout Endpoint, the OP SHOULD ask the End-User whether to + > log out of the OP as well + + The admin has configured the OP to always prompt the userfor logout + per the SHOULD recommendation. + """ + return True + + if token_user is None: + """ + > the OP MUST ask ask the End-User whether to log out of the OP as + > well if the supplied ID Token does not belong to the current OP + > session with the RP. + + token_user will only be populated if an ID token was found for the + RP (Application) that is requesting the logout. If token_user is not + then we must prompt the user. + """ + return True + + if token_user != self.request.user: + """ + > the OP MUST ask ask the End-User whether to log out of the OP as + > well if the supplied ID Token does not belong to the logged in + > End-User. + + is_authenticated indicates that there is a logged in user and was + tested in the first condition. + token_user != self.request.user indicates that the token does not + belong to the logged in user, Therefore we need to prompt the user. + """ + return True + + """ We didn't find a reason to prompt the user """ + return False def do_logout(self, application=None, post_logout_redirect_uri=None, state=None, token_user=None): user = token_user or self.request.user diff --git a/tests/test_oidc_views.py b/tests/test_oidc_views.py index 8bdf18360..65197cbd1 100644 --- a/tests/test_oidc_views.py +++ b/tests/test_oidc_views.py @@ -311,6 +311,10 @@ def test_must_prompt(oidc_tokens, other_user, rp_settings, ALWAYS_PROMPT): == ALWAYS_PROMPT ) assert RPInitiatedLogoutView(request=mock_request_for(other_user)).must_prompt(oidc_tokens.user) is True + assert ( + RPInitiatedLogoutView(request=mock_request_for(AnonymousUser())).must_prompt(oidc_tokens.user) + is False + ) def test__load_id_token(): @@ -577,13 +581,14 @@ def test_token_deletion_on_logout(oidc_tokens, logged_in_client, rp_settings): @pytest.mark.django_db(databases=retrieve_current_databases()) -def test_token_deletion_on_logout_expired_session(oidc_tokens, client, rp_settings): +def test_token_deletion_on_logout_without_op_session_get(oidc_tokens, client, rp_settings): AccessToken = get_access_token_model() IDToken = get_id_token_model() RefreshToken = get_refresh_token_model() assert AccessToken.objects.count() == 1 assert IDToken.objects.count() == 1 assert RefreshToken.objects.count() == 1 + rsp = client.get( reverse("oauth2_provider:rp-initiated-logout"), data={ @@ -591,15 +596,31 @@ def test_token_deletion_on_logout_expired_session(oidc_tokens, client, rp_settin "client_id": oidc_tokens.application.client_id, }, ) - assert rsp.status_code == 200 + assert rsp.status_code == 302 assert not is_logged_in(client) # Check that all tokens are active. - access_token = AccessToken.objects.get() - assert not access_token.is_expired() - id_token = IDToken.objects.get() - assert not id_token.is_expired() + assert AccessToken.objects.count() == 0 + assert IDToken.objects.count() == 0 + assert RefreshToken.objects.count() == 1 + + with pytest.raises(AccessToken.DoesNotExist): + AccessToken.objects.get() + + with pytest.raises(IDToken.DoesNotExist): + IDToken.objects.get() + refresh_token = RefreshToken.objects.get() - assert refresh_token.revoked is None + assert refresh_token.revoked is not None + + +@pytest.mark.django_db(databases=retrieve_current_databases()) +def test_token_deletion_on_logout_without_op_session_post(oidc_tokens, client, rp_settings): + AccessToken = get_access_token_model() + IDToken = get_id_token_model() + RefreshToken = get_refresh_token_model() + assert AccessToken.objects.count() == 1 + assert IDToken.objects.count() == 1 + assert RefreshToken.objects.count() == 1 rsp = client.post( reverse("oauth2_provider:rp-initiated-logout"), From 907d70f08c1bef94a485bde8fd3edb51952aec03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:29:47 -0400 Subject: [PATCH 057/115] [pre-commit.ci] pre-commit autoupdate (#1518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a50f8d2b..42a6cb209 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.1 hooks: - id: ruff args: [ --fix ] From 0d32dec28b2368e292c54c51665ec6e7837b3c3b Mon Sep 17 00:00:00 2001 From: Alex Kerkum Date: Mon, 4 Nov 2024 16:49:27 +0100 Subject: [PATCH 058/115] Use iterator in access token migration (#1522) --- AUTHORS | 1 + CHANGELOG.md | 1 + oauth2_provider/migrations/0012_add_token_checksum.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index d10ff1fb4..e2da60020 100644 --- a/AUTHORS +++ b/AUTHORS @@ -122,3 +122,4 @@ Wouter Klein Heerenbrink Yaroslav Halchenko Yuri Savin Miriam Forner +Alex Kerkum diff --git a/CHANGELOG.md b/CHANGELOG.md index f16317265..f86b8a8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * #1517 OP prompts for logout when no OP session * #1512 client_secret not marked sensitive +* #1521 Fix 0012 migration loading access token table into memory diff --git a/oauth2_provider/migrations/0012_add_token_checksum.py b/oauth2_provider/migrations/0012_add_token_checksum.py index 476c3b402..d27c65e54 100644 --- a/oauth2_provider/migrations/0012_add_token_checksum.py +++ b/oauth2_provider/migrations/0012_add_token_checksum.py @@ -9,7 +9,7 @@ def forwards_func(apps, schema_editor): Forward migration touches every "old" accesstoken.token which will cause the checksum to be computed. """ AccessToken = apps.get_model(oauth2_settings.ACCESS_TOKEN_MODEL) - accesstokens = AccessToken._default_manager.all() + accesstokens = AccessToken._default_manager.iterator() for accesstoken in accesstokens: accesstoken.save(update_fields=['token_checksum']) From 1c5e36d59cd8f740650a72ad80a455f8ed05ff3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:43:40 -0500 Subject: [PATCH 059/115] [pre-commit.ci] pre-commit autoupdate (#1523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42a6cb209..f59dff364 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.7.2 hooks: - id: ruff args: [ --fix ] From ad77eac9b7cc6d984c35ab3e8cb35778719bc84c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:31:08 -0500 Subject: [PATCH 060/115] [pre-commit.ci] pre-commit autoupdate (#1524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f59dff364..086c7af38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.2 + rev: v0.7.3 hooks: - id: ruff args: [ --fix ] From 1285ab0ec99d507c72f87242f545c50f9a5eef3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:19:09 -0500 Subject: [PATCH 061/115] [pre-commit.ci] pre-commit autoupdate (#1528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.7.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.7.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 086c7af38..7d546c18f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.3 + rev: v0.7.4 hooks: - id: ruff args: [ --fix ] From e950255f3be99ae57153f4f94cbb1fd012281005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:36:43 -0500 Subject: [PATCH 062/115] Bump @sveltejs/kit from 2.5.10 to 2.8.3 in /tests/app/rp (#1529) Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 2.5.10 to 2.8.3. - [Release notes](https://github.com/sveltejs/kit/releases) - [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md) - [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.8.3/packages/kit) --- updated-dependencies: - dependency-name: "@sveltejs/kit" dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/app/rp/package-lock.json | 34 +++++++++++++++++----------------- tests/app/rp/package.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 6ab5dc90e..1a22dffe4 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -13,7 +13,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.5.10", + "@sveltejs/kit": "^2.8.3", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.2.19", @@ -496,9 +496,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "dev": true }, "node_modules/@rollup/plugin-commonjs": { @@ -891,15 +891,15 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.5.10", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.10.tgz", - "integrity": "sha512-OqoyTmFG2cYmCFAdBfW+Qxbg8m23H4dv6KqwEt7ofr/ROcfcIl3Z/VT56L22H9f0uNZyr+9Bs1eh2gedOCK9kA==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.3.tgz", + "integrity": "sha512-DVBVwugfzzn0SxKA+eAmKqcZ7aHZROCHxH7/pyrOi+HLtQ721eEsctGb9MkhEuqj6q/9S/OFYdn37vdxzFPdvw==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", - "devalue": "^5.0.0", + "devalue": "^5.1.0", "esm-env": "^1.0.0", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", @@ -907,7 +907,7 @@ "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^2.0.4", + "sirv": "^3.0.0", "tiny-glob": "^0.2.9" }, "bin": { @@ -917,7 +917,7 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3" } @@ -1262,9 +1262,9 @@ } }, "node_modules/devalue": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", - "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "dev": true }, "node_modules/es6-promise": { @@ -2066,9 +2066,9 @@ "dev": true }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -2076,7 +2076,7 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/sorcery": { diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 7f784006f..8bd3bb8fe 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.5.10", + "@sveltejs/kit": "^2.8.3", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.2.19", From d2a4c3fa45d72816e04249911de751e445db1da7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:23:10 -0500 Subject: [PATCH 063/115] [pre-commit.ci] pre-commit autoupdate (#1530) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d546c18f..ca719c9a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.0 hooks: - id: ruff args: [ --fix ] From c403b2e5974a259b814330923da37012a875e729 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:30:41 -0500 Subject: [PATCH 064/115] [pre-commit.ci] pre-commit autoupdate (#1531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.0 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.0...v0.8.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca719c9a2..40c6824a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.8.1 hooks: - id: ruff args: [ --fix ] From 7b244a34d640ca5ac0fd1c024624a7144eca021e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:16:06 -0500 Subject: [PATCH 065/115] [pre-commit.ci] pre-commit autoupdate (#1532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40c6824a8..e134ab26f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.1 + rev: v0.8.2 hooks: - id: ruff args: [ --fix ] From ee65197cbb3833e28bd655b33b59c884745c5299 Mon Sep 17 00:00:00 2001 From: IT-Native Date: Thu, 12 Dec 2024 15:12:35 +0100 Subject: [PATCH 066/115] Fixes typo (#1533) Update getting_started.rst --- docs/getting_started.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index e95618723..d2ce14ca1 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -243,9 +243,9 @@ Start the development server:: python manage.py runserver -Point your browser to http://127.0.0.1:8000/o/applications/register/ lets create an application. +Point your browser to http://127.0.0.1:8000/o/applications/register/ and let's create an application. -Fill the form as show in the screenshot below and before save take note of ``Client id`` and ``Client secret``, we will use it in a minute. +Fill the form as shown in the screenshot below and before clicking on save take note of ``Client id`` and ``Client secret``, we will use it in a minute. If you want to use this application with OIDC and ``HS256`` (see :doc:`OpenID Connect `), uncheck ``Hash client secret`` to allow verifying tokens using JWT signatures. This means your client secret will be stored in cleartext but is the only way to successfully use signed JWT's with ``HS256``. From f6db0fa362e916762d4e7a5bcb84fa9c49a34610 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:55:09 -0500 Subject: [PATCH 067/115] [pre-commit.ci] pre-commit autoupdate (#1534) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e134ab26f..b72e72b11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.2 + rev: v0.8.3 hooks: - id: ruff args: [ --fix ] From aa6d566881965c43cedae8e04a84beda9633be90 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:52:24 -0500 Subject: [PATCH 068/115] [pre-commit.ci] pre-commit autoupdate (#1537) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b72e72b11..e5429e8a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.3 + rev: v0.8.4 hooks: - id: ruff args: [ --fix ] From c61d852ef500ef8a925383b654d1273a89639bb4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:16:36 -0500 Subject: [PATCH 069/115] [pre-commit.ci] pre-commit autoupdate (#1538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e5429e8a8..7c3e7e799 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.8.6 hooks: - id: ruff args: [ --fix ] From 1b0520ac992e6c6179cdbcef68f383eb7a0c7980 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:21:47 -0500 Subject: [PATCH 070/115] [pre-commit.ci] pre-commit autoupdate (#1540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.9.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.9.1) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- oauth2_provider/models.py | 2 +- tests/test_models.py | 48 +++++++++++++++++++-------------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c3e7e799..d337a46d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.1 hooks: - id: ruff args: [ --fix ] diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 0467ddfa1..a76db37c0 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -715,7 +715,7 @@ def batch_delete(queryset, query): flat_queryset = queryset.values_list("id", flat=True)[:CLEAR_EXPIRED_TOKENS_BATCH_SIZE] batch_length = flat_queryset.count() queryset.model.objects.filter(id__in=list(flat_queryset)).delete() - logger.debug(f"{batch_length} tokens deleted, {current_no-batch_length} left") + logger.debug(f"{batch_length} tokens deleted, {current_no - batch_length} left") queryset = queryset.model.objects.filter(query) time.sleep(CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL) current_no = queryset.count() diff --git a/tests/test_models.py b/tests/test_models.py index 32ca07627..2c7ff8114 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -433,25 +433,25 @@ def test_clear_expired_tokens_with_tokens(self): initial_at_count = AccessToken.objects.count() assert initial_at_count == 2 * self.num_tokens, f"{2 * self.num_tokens} access tokens should exist." initial_expired_at_count = AccessToken.objects.filter(expires__lte=self.now).count() - assert ( - initial_expired_at_count == self.num_tokens - ), f"{self.num_tokens} expired access tokens should exist." + assert initial_expired_at_count == self.num_tokens, ( + f"{self.num_tokens} expired access tokens should exist." + ) initial_current_at_count = AccessToken.objects.filter(expires__gt=self.now).count() - assert ( - initial_current_at_count == self.num_tokens - ), f"{self.num_tokens} current access tokens should exist." + assert initial_current_at_count == self.num_tokens, ( + f"{self.num_tokens} current access tokens should exist." + ) initial_rt_count = RefreshToken.objects.count() - assert ( - initial_rt_count == self.num_tokens // 2 - ), f"{self.num_tokens // 2} refresh tokens should exist." + assert initial_rt_count == self.num_tokens // 2, ( + f"{self.num_tokens // 2} refresh tokens should exist." + ) initial_rt_expired_at_count = RefreshToken.objects.filter(access_token__expires__lte=self.now).count() - assert ( - initial_rt_expired_at_count == initial_rt_count / 2 - ), "half the refresh tokens should be for expired access tokens." + assert initial_rt_expired_at_count == initial_rt_count / 2, ( + "half the refresh tokens should be for expired access tokens." + ) initial_rt_current_at_count = RefreshToken.objects.filter(access_token__expires__gt=self.now).count() - assert ( - initial_rt_current_at_count == initial_rt_count / 2 - ), "half the refresh tokens should be for current access tokens." + assert initial_rt_current_at_count == initial_rt_count / 2, ( + "half the refresh tokens should be for current access tokens." + ) initial_gt_count = Grant.objects.count() assert initial_gt_count == self.num_tokens * 2, f"{self.num_tokens * 2} grants should exist." @@ -459,15 +459,15 @@ def test_clear_expired_tokens_with_tokens(self): # after clear_expired(): remaining_at_count = AccessToken.objects.count() - assert ( - remaining_at_count == initial_at_count // 2 - ), "half the initial access tokens should still exist." + assert remaining_at_count == initial_at_count // 2, ( + "half the initial access tokens should still exist." + ) remaining_expired_at_count = AccessToken.objects.filter(expires__lte=self.now).count() assert remaining_expired_at_count == 0, "no remaining expired access tokens should still exist." remaining_current_at_count = AccessToken.objects.filter(expires__gt=self.now).count() - assert ( - remaining_current_at_count == initial_current_at_count - ), "all current access tokens should still exist." + assert remaining_current_at_count == initial_current_at_count, ( + "all current access tokens should still exist." + ) remaining_rt_count = RefreshToken.objects.count() assert remaining_rt_count == initial_rt_count // 2, "half the refresh tokens should still exist." remaining_rt_expired_at_count = RefreshToken.objects.filter( @@ -477,9 +477,9 @@ def test_clear_expired_tokens_with_tokens(self): remaining_rt_current_at_count = RefreshToken.objects.filter( access_token__expires__gt=self.now ).count() - assert ( - remaining_rt_current_at_count == initial_rt_current_at_count - ), "all the refresh tokens for current access tokens should still exist." + assert remaining_rt_current_at_count == initial_rt_current_at_count, ( + "all the refresh tokens for current access tokens should still exist." + ) remaining_gt_count = Grant.objects.count() assert remaining_gt_count == initial_gt_count // 2, "half the remaining grants should still exist." From e11cfb2b63fd133a808142bd67488f4d7fe33d21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:58:25 -0500 Subject: [PATCH 071/115] [pre-commit.ci] pre-commit autoupdate (#1541) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d337a46d8..3d89ddfa1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.1 + rev: v0.9.2 hooks: - id: ruff args: [ --fix ] From ffcf60987f4e6ade6632dccd97fd5d1824f45b9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:13:57 -0500 Subject: [PATCH 072/115] Bump vite from 5.4.6 to 5.4.14 in /tests/app/rp (#1542) --- tests/app/rp/package-lock.json | 9 +++++---- tests/app/rp/package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 1a22dffe4..be5db7cb4 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -20,7 +20,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.6" + "vite": "^5.4.14" } }, "node_modules/@ampproject/remapping": { @@ -2312,10 +2312,11 @@ } }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 8bd3bb8fe..3ec9569bd 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -21,7 +21,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.6" + "vite": "^5.4.14" }, "type": "module", "dependencies": { From a24d0a8e83af98d827d313881b71fa049d193f35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 13:44:03 -0500 Subject: [PATCH 073/115] [pre-commit.ci] pre-commit autoupdate (#1544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.2 → v0.9.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.2...v0.9.3) - [github.com/codespell-project/codespell: v2.3.0 → v2.4.0](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.0) * codespell --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan Crosswell --- .pre-commit-config.yaml | 4 ++-- CHANGELOG.md | 2 +- tests/test_authorization_code.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d89ddfa1..facfd8f7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.2 + rev: v0.9.3 hooks: - id: ruff args: [ --fix ] @@ -22,7 +22,7 @@ repos: - id: sphinx-lint # Configuration for codespell is in pyproject.toml - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.0 hooks: - id: codespell exclude: (package-lock.json|/locale/) diff --git a/CHANGELOG.md b/CHANGELOG.md index f86b8a8af..8dfe6c3e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -411,7 +411,7 @@ This is a major release with **BREAKING** changes. Please make sure to review th * **New feature**: The new setting `ERROR_RESPONSE_WITH_SCOPES` can now be set to True to include required scopes when DRF authorization fails due to improper scopes. * **New feature**: The new setting `REFRESH_TOKEN_GRACE_PERIOD_SECONDS` controls a grace period during which - refresh tokens may be re-used. + refresh tokens may be reused. * An `app_authorized` signal is fired when a token is generated. ## 1.0.0 [2017-06-07] diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index 122474950..f162e211a 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -989,7 +989,7 @@ def test_refresh_fail_repeating_requests(self): def test_refresh_repeating_requests_revokes_old_token(self): """ If a refresh token is reused, the server should invalidate *all* access tokens that have a relation - to the re-used token. This forces a malicious actor to be logged out. + to the reused token. This forces a malicious actor to be logged out. The server can't determine whether the first or the second client was legitimate, so it needs to revoke both. See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#name-recommendations From 48f4d545a6025ed88e3b42119be17d7316e4d229 Mon Sep 17 00:00:00 2001 From: Raphael Lullis Date: Wed, 29 Jan 2025 17:56:21 +0000 Subject: [PATCH 074/115] Fix range of allowed django versions (#1547) * Fix range of allowed django versions Running `docker-compose build` fails at the moment due to a package resolution conflict. Allowing to use django 4.2 solves the issue. * Update tests/app/idp/requirements.txt Co-authored-by: Alan Crosswell --------- Co-authored-by: Alan Crosswell --- tests/app/idp/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/app/idp/requirements.txt b/tests/app/idp/requirements.txt index ba8e75052..ec77fcf9d 100644 --- a/tests/app/idp/requirements.txt +++ b/tests/app/idp/requirements.txt @@ -1,5 +1,5 @@ -Django>=3.2,<4.2 +Django>=4.2,<=5.1 django-cors-headers==3.14.0 django-environ==0.11.2 --e ../../../ \ No newline at end of file +-e ../../../ From 6d21bfb5ccea3564ab010b919cfc613f714bb51e Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Fri, 31 Jan 2025 18:28:11 -0500 Subject: [PATCH 075/115] fix: idp image can't find templates or statics (#1550) --- CHANGELOG.md | 1 + Dockerfile | 5 ++++- docs/contributing.rst | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dfe6c3e5..8c4770459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1517 OP prompts for logout when no OP session * #1512 client_secret not marked sensitive * #1521 Fix 0012 migration loading access token table into memory +* #1584 Fix IDP container in docker compose environment could not find templates and static files. diff --git a/Dockerfile b/Dockerfile index e501e84d2..4565df5ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,7 +57,10 @@ ENV DATABASE_URL="sqlite:////data/db.sqlite3" COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" COPY --from=builder /code /code -RUN mkdir -p /code/tests/app/idp/static /code/tests/app/idp/templates +RUN mkdir -p /data/static /data/templates +COPY --from=builder /code/tests/app/idp/static /data/static +COPY --from=builder /code/tests/app/idp/templates /data/templates + WORKDIR /code/tests/app/idp RUN apt-get update && apt-get install -y \ libpq5 \ diff --git a/docs/contributing.rst b/docs/contributing.rst index 4f0b88b32..569f5eab2 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -325,6 +325,28 @@ Reviewing and Merging PRs PRs that are incorrectly merged may (reluctantly) be reverted by the Project Leads. +End to End Testing +------------------ + +There is a demonstration Identity Provider (IDP) and Relying Party (RP) to allow for +end to end testing. They can be launched directly by following the instructions in +/test/apps/README.md or via docker compose. To launch via docker compose + +.. code-block:: bash + + # build the images with the current code + docker compose build + # wipe any existing services and volumes + docker compose rm -v + # start the services + docker compose up -d + +Please verify the RP behaves as expected by logging in, reloading, and logging out. + +open http://localhost:5173 in your browser and login with the following credentials: + +username: superuser +password: password Publishing a Release -------------------- From bc7edb05aab8d962704e4ec294d6dbd3953f45b2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:46:55 -0500 Subject: [PATCH 076/115] [pre-commit.ci] pre-commit autoupdate (#1551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.3 → v0.9.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.3...v0.9.4) - [github.com/codespell-project/codespell: v2.4.0 → v2.4.1](https://github.com/codespell-project/codespell/compare/v2.4.0...v2.4.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index facfd8f7d..b67b72438 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.3 + rev: v0.9.4 hooks: - id: ruff args: [ --fix ] @@ -22,7 +22,7 @@ repos: - id: sphinx-lint # Configuration for codespell is in pyproject.toml - repo: https://github.com/codespell-project/codespell - rev: v2.4.0 + rev: v2.4.1 hooks: - id: codespell exclude: (package-lock.json|/locale/) From 87bcd28226996f11cd9daaaeaf542c71f800abcd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:47:45 -0500 Subject: [PATCH 077/115] [pre-commit.ci] pre-commit autoupdate (#1553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.4 → v0.9.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.4...v0.9.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b67b72438..50f835567 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.4 + rev: v0.9.6 hooks: - id: ruff args: [ --fix ] From c9218e4f2e7ec3a68391d25d724c751b8505f496 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:00:02 -0500 Subject: [PATCH 078/115] [pre-commit.ci] pre-commit autoupdate (#1554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.9.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.9.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 50f835567..7fba749ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 + rev: v0.9.7 hooks: - id: ruff args: [ --fix ] From 9805863a3b718adbc2d0b8c95d9c9967d066e4f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:57:01 -0500 Subject: [PATCH 079/115] [pre-commit.ci] pre-commit autoupdate (#1555) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fba749ce..3b24f4649 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 + rev: v0.9.9 hooks: - id: ruff args: [ --fix ] From be86257d327ccf5396f8986308027b0fdc216590 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 13:59:33 -0400 Subject: [PATCH 080/115] [pre-commit.ci] pre-commit autoupdate (#1556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b24f4649..f03a3a002 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.9 + rev: v0.9.10 hooks: - id: ruff args: [ --fix ] From 25ac4682f6eb981647286aab78cddec4f67083af Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:12:05 -0400 Subject: [PATCH 081/115] [pre-commit.ci] pre-commit autoupdate (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f03a3a002..a35ef3e49 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.10 + rev: v0.11.0 hooks: - id: ruff args: [ --fix ] From ec5984591b348b06ac60ef9d156be9a295d65110 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 13:44:28 -0400 Subject: [PATCH 082/115] [pre-commit.ci] pre-commit autoupdate (#1560) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a35ef3e49..3671e158b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.0 + rev: v0.11.2 hooks: - id: ruff args: [ --fix ] From 172c636cf6c0db0740b02bac67e74b3702f6029d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:15:52 -0400 Subject: [PATCH 083/115] [pre-commit.ci] pre-commit autoupdate (#1561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3671e158b..239764310 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.11.4 hooks: - id: ruff args: [ --fix ] From 231669f06889562df1793e49702e2aafba3063fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:11:09 -0400 Subject: [PATCH 084/115] [pre-commit.ci] pre-commit autoupdate (#1563) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 239764310..e64c2528f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.11.5 hooks: - id: ruff args: [ --fix ] From 839952f0e27a87583ff5506b750db18f576ff600 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:35:34 -0400 Subject: [PATCH 085/115] Bump @sveltejs/kit from 2.8.3 to 2.20.6 in /tests/app/rp (#1564) --- tests/app/rp/package-lock.json | 50 ++++++++++------------------------ tests/app/rp/package.json | 2 +- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index be5db7cb4..6f33dbbec 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -13,7 +13,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.8.3", + "@sveltejs/kit": "^2.20.6", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.2.19", @@ -891,24 +891,23 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.3.tgz", - "integrity": "sha512-DVBVwugfzzn0SxKA+eAmKqcZ7aHZROCHxH7/pyrOi+HLtQ721eEsctGb9MkhEuqj6q/9S/OFYdn37vdxzFPdvw==", + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.6.tgz", + "integrity": "sha512-ImUkSQ//Xf4N9r0HHAe5vRA7RyQ7U1Ue1YUT235Ig+IiIqbsixEulHTHrP5LtBiC8xOkJoPZQ1VZ/nWHNOaGGw==", "dev": true, - "hasInstallScript": true, + "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", - "esm-env": "^1.0.0", + "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0", - "tiny-glob": "^0.2.9" + "sirv": "^3.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -917,9 +916,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3" + "vite": "^5.0.3 || ^6.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { @@ -1312,10 +1311,11 @@ } }, "node_modules/esm-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", @@ -1425,18 +1425,6 @@ "node": ">= 6" } }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2261,16 +2249,6 @@ "node": ">=0.4.0" } }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dev": true, - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 3ec9569bd..2e1a3afb6 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.8.3", + "@sveltejs/kit": "^2.20.6", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "svelte": "^4.2.19", From b63c5766b5f8c37fa42f8a0134261198574df8bc Mon Sep 17 00:00:00 2001 From: Brian Helba Date: Fri, 18 Apr 2025 13:31:16 -0400 Subject: [PATCH 086/115] Improve settings documentation (#1567) Previously, it was unclear that the swappable model settings should always be set without a namespace, as the sub-section titles didn't include the required `OAUTH2_PROVIDER_` prefix. The warning at the top may not be noticed by people looking for a specific settings, and it was still unclear given the sub-section titles. Additionally, this documents the `OAUTH2_PROVIDER_ID_TOKEN_MODEL` and `OAUTH2_PROVIDER_GRANT_MODEL` settings, which were previously undocumented. Also, this corrects a mistake where `ACCESS_TOKEN_GENERATOR` was mentioned as the setting which enables the use of `SettingsScopes`; the actual setting is `SCOPES_BACKEND_CLASS`. --- docs/settings.rst | 80 ++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 545736ccb..985ca5d2c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,10 +1,8 @@ Settings ======== -Our configurations are all namespaced under the ``OAUTH2_PROVIDER`` settings with the exception of -``OAUTH2_PROVIDER_APPLICATION_MODEL``, ``OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL``, ``OAUTH2_PROVIDER_GRANT_MODEL``, -``OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL``: this is because of the way Django currently implements -swappable models. See `issue #90 `_ for details. +Our configurations are all namespaced under the ``OAUTH2_PROVIDER`` settings, with the exception +of the `List of non-namespaced settings`_. For example: @@ -24,24 +22,17 @@ For example: A big *thank you* to the guys from Django REST Framework for inspiring this. -List of available settings --------------------------- +List of available settings within OAUTH2_PROVIDER +------------------------------------------------- ACCESS_TOKEN_EXPIRE_SECONDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Default: ``36000`` The number of seconds an access token remains valid. Requesting a protected resource after this duration will fail. Keep this value high enough so clients can cache the token for a reasonable amount of time. -ACCESS_TOKEN_MODEL -~~~~~~~~~~~~~~~~~~ -The import string of the class (model) representing your access tokens. Overwrite -this value if you wrote your own implementation (subclass of -``oauth2_provider.models.AccessToken``). - ACCESS_TOKEN_GENERATOR ~~~~~~~~~~~~~~~~~~~~~~ Import path of a callable used to generate access tokens. @@ -49,7 +40,6 @@ Import path of a callable used to generate access tokens. ALLOWED_REDIRECT_URI_SCHEMES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Default: ``["http", "https"]`` A list of schemes that the ``redirect_uri`` field will be validated against. @@ -65,7 +55,6 @@ a per-application basis. ALLOW_URI_WILDCARDS ~~~~~~~~~~~~~~~~~~~ - Default: ``False`` SECURITY WARNING: Enabling this setting can introduce security vulnerabilities. Only enable @@ -96,7 +85,6 @@ deployments for development previews and user acceptance testing. ALLOWED_SCHEMES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Default: ``["https"]`` A list of schemes that the ``allowed_origins`` field will be validated against. @@ -105,13 +93,6 @@ Adding ``"http"`` to the list is considered to be safe only for local developmen Note that `OAUTHLIB_INSECURE_TRANSPORT `_ environment variable should be also set to allow HTTP origins. - -APPLICATION_MODEL -~~~~~~~~~~~~~~~~~ -The import string of the class (model) representing your applications. Overwrite -this value if you wrote your own implementation (subclass of -``oauth2_provider.models.Application``). - AUTHORIZATION_CODE_EXPIRE_SECONDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``60`` @@ -214,12 +195,6 @@ period the application, the app then has only a consumed refresh token and the only recourse is to have the user re-authenticate. A suggested value, if this is enabled, is 2 minutes. -REFRESH_TOKEN_MODEL -~~~~~~~~~~~~~~~~~~~ -The import string of the class (model) representing your refresh tokens. Overwrite -this value if you wrote your own implementation (subclass of -``oauth2_provider.models.RefreshToken``). - REFRESH_TOKEN_REUSE_PROTECTION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When this is set to ``True`` (default ``False``), and ``ROTATE_REFRESH_TOKEN`` is used, the server will check @@ -257,7 +232,7 @@ Defaults to ``oauth2_provider.scopes.SettingsScopes``, which reads scopes throug SCOPES ~~~~~~ -.. note:: (0.12.0+) Only used if ``ACCESS_TOKEN_GENERATOR`` is set to the SettingsScopes default. +.. note:: (0.12.0+) Only used if ``SCOPES_BACKEND_CLASS`` is set to the SettingsScopes default. A dictionary mapping each scope name to its human description. @@ -265,7 +240,7 @@ A dictionary mapping each scope name to its human description. DEFAULT_SCOPES ~~~~~~~~~~~~~~ -.. note:: (0.12.0+) Only used if ``ACCESS_TOKEN_GENERATOR`` is set to the SettingsScopes default. +.. note:: (0.12.0+) Only used if ``SCOPES_BACKEND_CLASS`` is set to the SettingsScopes default. A list of scopes that should be returned by default. This is a subset of the keys of the ``SCOPES`` setting. @@ -277,13 +252,13 @@ By default this is set to ``'__all__'`` meaning that the whole set of ``SCOPES`` READ_SCOPE ~~~~~~~~~~ -.. note:: (0.12.0+) Only used if ``ACCESS_TOKEN_GENERATOR`` is set to the SettingsScopes default. +.. note:: (0.12.0+) Only used if ``SCOPES_BACKEND_CLASS`` is set to the SettingsScopes default. The name of the *read* scope. WRITE_SCOPE ~~~~~~~~~~~ -.. note:: (0.12.0+) Only used if ``ACCESS_TOKEN_GENERATOR`` is set to the SettingsScopes default. +.. note:: (0.12.0+) Only used if ``SCOPES_BACKEND_CLASS`` is set to the SettingsScopes default. The name of the *write* scope. @@ -340,7 +315,6 @@ Default: ``False`` Whether or not :doc:`oidc` support is enabled. - OIDC_RSA_PRIVATE_KEY ~~~~~~~~~~~~~~~~~~~~ Default: ``""`` @@ -470,11 +444,47 @@ Time of sleep in seconds used by ``cleartokens`` management command between batc Set this to a non-zero value (e.g. ``0.1``) to add a pause between batch sizes to reduce system load when clearing large batches of expired tokens. +List of non-namespaced settings +------------------------------- +.. note:: + These settings must be set as top-level Django settings (outside of ``OAUTH2_PROVIDER``), + because of the way Django currently implements swappable models. + See `issue #90 `_ for details. + + +OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The import string of the class (model) representing your access tokens. +Overwrite this value if you wrote your own implementation (subclass of +``oauth2_provider.models.AccessToken``). + +OAUTH2_PROVIDER_APPLICATION_MODEL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The import string of the class (model) representing your applications. +Overwrite this value if you wrote your own implementation (subclass of +``oauth2_provider.models.Application``). + +OAUTH2_PROVIDER_ID_TOKEN_MODEL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The import string of the class (model) representing your OpenID Connect ID Token. +Overwrite this value if you wrote your own implementation (subclass of +``oauth2_provider.models.IDToken``). + +OAUTH2_PROVIDER_GRANT_MODEL +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The import string of the class (model) representing your OAuth2 grants. +Overwrite this value if you wrote your own implementation (subclass of +``oauth2_provider.models.Grant``). + +OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The import string of the class (model) representing your refresh tokens. +Overwrite this value if you wrote your own implementation (subclass of +``oauth2_provider.models.RefreshToken``). Settings imported from Django project ------------------------------------- USE_TZ ~~~~~~ - Used to determine whether or not to make token expire dates timezone aware. From 6507e66c2d76a6acef860a4bd40390e7d054c243 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:26:45 -0400 Subject: [PATCH 087/115] [pre-commit.ci] pre-commit autoupdate (#1569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.5 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.5...v0.11.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e64c2528f..485a3091a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.5 + rev: v0.11.6 hooks: - id: ruff args: [ --fix ] From db4c6c75c19596e9c70be9f6b4c8f68ef21c9a43 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:43:15 -0400 Subject: [PATCH 088/115] [pre-commit.ci] pre-commit autoupdate (#1570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 485a3091a..7cb239c7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.6 + rev: v0.11.7 hooks: - id: ruff args: [ --fix ] From 64b1681ad4c6e05a41139b274e625fff38afab20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 14:29:38 -0400 Subject: [PATCH 089/115] [pre-commit.ci] pre-commit autoupdate (#1572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7cb239c7e..c24b5a443 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.7 + rev: v0.11.8 hooks: - id: ruff args: [ --fix ] From 877e62542e44c1d0a2ddad8a09bb7a8d7b41c44c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 13:48:50 -0400 Subject: [PATCH 090/115] [pre-commit.ci] pre-commit autoupdate (#1574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c24b5a443..11f11a935 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.8 + rev: v0.11.9 hooks: - id: ruff args: [ --fix ] From 1d5bfe776fd3e18a94715aaa6fd2940ae041797b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 14:08:12 -0400 Subject: [PATCH 091/115] [pre-commit.ci] pre-commit autoupdate (#1575) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11f11a935..9aa44ada9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.9 + rev: v0.11.10 hooks: - id: ruff args: [ --fix ] From 6eb18c3cd31a14aed290e276f71c84a44f085d9c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 14:39:41 -0400 Subject: [PATCH 092/115] [pre-commit.ci] pre-commit autoupdate (#1577) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9aa44ada9..d36da279e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.10 + rev: v0.11.11 hooks: - id: ruff args: [ --fix ] From 8d3e7a907c5c82c66953b74a015bbc98aa4e636c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:56:42 -0400 Subject: [PATCH 093/115] [pre-commit.ci] pre-commit autoupdate (#1578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.11 → v0.11.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.11...v0.11.12) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d36da279e..8c244f88b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.11.12 hooks: - id: ruff args: [ --fix ] From 2f2b7f0b5172a0144e390a5555bf7b2fe23a384e Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Thu, 5 Jun 2025 23:10:34 +0500 Subject: [PATCH 094/115] Remove unnecessary select for removing an auth code grant (#1568) --- AUTHORS | 1 + oauth2_provider/oauth2_validators.py | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index e2da60020..e5357ae7c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -123,3 +123,4 @@ Yaroslav Halchenko Yuri Savin Miriam Forner Alex Kerkum +q0w diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index b20d0dd6c..db459a446 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -322,10 +322,8 @@ def invalidate_authorization_code(self, client_id, code, request, *args, **kwarg :raises: InvalidGrantError if the grant does not exist. """ - try: - grant = Grant.objects.get(code=code, application=request.client) - grant.delete() - except Grant.DoesNotExist: + deleted_grant_count, _ = Grant.objects.filter(code=code, application=request.client).delete() + if not deleted_grant_count: raise errors.InvalidGrantError(request=request) def validate_client_id(self, client_id, request, *args, **kwargs): From 362fecb87f439069cc967f47ebbd3ebde614abbe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:33:13 -0400 Subject: [PATCH 095/115] [pre-commit.ci] pre-commit autoupdate (#1580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.12 → v0.11.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.12...v0.11.13) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c244f88b..34a93c193 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.12 + rev: v0.11.13 hooks: - id: ruff args: [ --fix ] From d862f531a2ab96fb3cf05f6ffbf9baf8192c1459 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:43:56 -0400 Subject: [PATCH 096/115] [pre-commit.ci] pre-commit autoupdate (#1581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.13 → v0.12.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34a93c193..e117b66b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.1 hooks: - id: ruff args: [ --fix ] From bd6336993c2339876907d54fb6556cc79bfbe377 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Sun, 20 Jul 2025 13:52:50 -0400 Subject: [PATCH 097/115] fix: codecov uploads failing (#1585) --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3073dc7a..0b453d269 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,8 @@ jobs: test-package: name: Test Package (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) runs-on: ubuntu-latest + permissions: + id-token: write # Required for Codecov OIDC token strategy: fail-fast: false matrix: @@ -60,9 +62,10 @@ jobs: DJANGO: ${{ matrix.django-version }} - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: name: Python ${{ matrix.python-version }} + use_oidc: true test-demo-rp: name: Test Demo Relying Party From 3fad7654182d7eddf076d7070e2af4bfe95341e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 14:11:06 -0400 Subject: [PATCH 098/115] Bump vite from 5.4.14 to 5.4.19 in /tests/app/rp (#1587) --- tests/app/rp/package-lock.json | 8 ++++---- tests/app/rp/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index 6f33dbbec..c8186b56d 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -20,7 +20,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.14" + "vite": "^5.4.19" } }, "node_modules/@ampproject/remapping": { @@ -2290,9 +2290,9 @@ } }, "node_modules/vite": { - "version": "5.4.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", - "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 2e1a3afb6..603114a1a 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -21,7 +21,7 @@ "svelte-check": "^3.8.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.4.14" + "vite": "^5.4.19" }, "type": "module", "dependencies": { From 121abd4fb8398401907860005a8be8bdec0149f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:35:33 -0400 Subject: [PATCH 099/115] [pre-commit.ci] pre-commit autoupdate (#1584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.1 → v0.12.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.1...v0.12.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e117b66b4..f4eb471cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.1 + rev: v0.12.7 hooks: - id: ruff args: [ --fix ] From 1c2d99673169d0962c215a835fed392c3e2fdbf0 Mon Sep 17 00:00:00 2001 From: Dawid Wolski <130552247+dawidwolski-identt@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:08:38 +0200 Subject: [PATCH 100/115] Fix: AttributeError in IntrospectTokenView when token not provided (#1562) When token is not provided explicitly respond with a 400 status and properly structured JSON error. Before this a 500 was being returned for an unhandled exception. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Darrel O'Pry --- CHANGELOG.md | 1 + oauth2_provider/views/introspect.py | 5 +++++ tests/test_introspection_view.py | 14 ++++++++++++++ tox.ini | 11 ++++++----- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4770459..514c45ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1512 client_secret not marked sensitive * #1521 Fix 0012 migration loading access token table into memory * #1584 Fix IDP container in docker compose environment could not find templates and static files. +* #1562 Fix: Handle AttributeError in IntrospectTokenView diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 5474c3a7e..5b9810c82 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -26,6 +26,11 @@ class IntrospectTokenView(ClientProtectedScopedResourceView): @staticmethod def get_token_response(token_value=None): + if token_value is None: + return JsonResponse( + {"error": "invalid_request", "error_description": "Token parameter is missing."}, + status=400, + ) try: token_checksum = hashlib.sha256(token_value.encode("utf-8")).hexdigest() token = ( diff --git a/tests/test_introspection_view.py b/tests/test_introspection_view.py index 3db23bbcd..ad7d8983d 100644 --- a/tests/test_introspection_view.py +++ b/tests/test_introspection_view.py @@ -279,6 +279,20 @@ def test_view_post_notexisting_token(self): }, ) + def test_view_post_no_token(self): + """ + Test that when you pass no token HTTP 400 is returned + """ + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token, + } + response = self.client.post(reverse("oauth2_provider:introspect"), **auth_headers) + + self.assertEqual(response.status_code, 400) + content = response.json() + self.assertIsInstance(content, dict) + self.assertEqual(content["error"], "invalid_request") + def test_view_post_valid_client_creds_basic_auth(self): """Test HTTP basic auth working""" auth_headers = get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) diff --git a/tox.ini b/tox.ini index 303b0d51d..d5cf8d2dc 100644 --- a/tox.ini +++ b/tox.ini @@ -5,10 +5,10 @@ envlist = docs, lint, sphinxlint, - py{38,39,310,311,312}-dj42, - py{310,311,312}-dj50, - py{310,311,312}-dj51, - py{310,311,312}-djmain, + py{38,39,310,311,312,313}-dj42, + py{310,311,312,313}-dj50, + py{310,311,312,313}-dj51, + py{310,311,312,313}-djmain, py39-multi-db-dj-42 [gh-actions] @@ -18,6 +18,7 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [gh-actions:env] DJANGO = @@ -54,7 +55,7 @@ deps = passenv = PYTEST_ADDOPTS -[testenv:py{310,311,312}-djmain] +[testenv:py{310,311,312,313}-djmain] ignore_errors = true ignore_outcome = true From 8f132e7a26f8fb47ea0e3f8bc86af3fde2440a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihad=20G=C3=9CNDO=C4=9EDU?= Date: Tue, 12 Aug 2025 20:00:11 +0300 Subject: [PATCH 101/115] feat: add Turkish translations for OAuth2 provider messages (#1586) * Add Turkish translations for OAuth2 provider messages --- AUTHORS | 1 + CHANGELOG.md | 1 + .../locale/tr/LC_MESSAGES/django.po | 215 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 oauth2_provider/locale/tr/LC_MESSAGES/django.po diff --git a/AUTHORS b/AUTHORS index e5357ae7c..7d24d3afd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,6 +35,7 @@ Bart Merenda Bas van Oostveen Brian Helba Carl Schwan +Cihad GUNDOGDU Daniel Golding Daniel 'Vector' Kerr Darrel O'Pry diff --git a/CHANGELOG.md b/CHANGELOG.md index 514c45ec6..a8bc0fa32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added * #1506 Support for Wildcard Origin and Redirect URIs +* #1586 Turkish language support added diff --git a/oauth2_provider/locale/pt_BR/LC_MESSAGES/django.po b/oauth2_provider/locale/pt_BR/LC_MESSAGES/django.po index 48d673e33..e852622e3 100644 --- a/oauth2_provider/locale/pt_BR/LC_MESSAGES/django.po +++ b/oauth2_provider/locale/pt_BR/LC_MESSAGES/django.po @@ -9,10 +9,10 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-12-30 09:50-0300\n" -"PO-Revision-Date: 2021-12-30 09:50-0300\n" -"Last-Translator: Eduardo Oliveira \n" +"PO-Revision-Date: 2025-07-01 12:35-0300\n" +"Last-Translator: Thales Barbosa Bento \n" "Language-Team: LANGUAGE \n" -"Language: \n" +"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -101,7 +101,7 @@ msgstr "Tem certeza que deseja remover a aplicação?" msgid "Cancel" msgstr "Cancelar" -#: templates/oauth2_provider/application_confirm_delete.html:13 +#: templates/oauth2_provider/application_confirm_delete.html:13F #: templates/oauth2_provider/application_detail.html:38 #: templates/oauth2_provider/authorized-token-delete.html:7 msgid "Delete" @@ -113,7 +113,7 @@ msgstr "ID do Cliente" #: templates/oauth2_provider/application_detail.html:15 msgid "Client secret" -msgstr "Palavra-Chave Secreta do Cliente" +msgstr "Chave Secreta do Cliente" #: templates/oauth2_provider/application_detail.html:20 msgid "Client type" @@ -200,3 +200,258 @@ msgstr "Revogar" #: templates/oauth2_provider/authorized-tokens.html:19 msgid "There are no authorized tokens yet." msgstr "Não existem tokens autorizados ainda." + +msgid "Allowed origins list to enable CORS, space separated" +msgstr "Lista de origens permitidas para habilitar CORS, separadas por espaços" + +msgid "Access tokens" +msgstr "Tokens de acesso" + +msgid "Applications" +msgstr "Aplicações" + +msgid "Grants" +msgstr "Concessões" + +msgid "Id tokens" +msgstr "Tokens de ID" + +msgid "Refresh tokens" +msgstr "Tokens de atualização" + +msgid "Access token" +msgstr "Token de acesso" + +msgid "Application" +msgstr "Aplicação" + +msgid "Grant" +msgstr "Concessão" + +msgid "Id token" +msgstr "Token de ID" + +msgid "Refresh token" +msgstr "Token de atualização" + +msgid "Skip authorization" +msgstr "Pular autorização" + +msgid "Algorithm" +msgstr "Algoritmo" + +msgid "Created" +msgstr "Criado" + +msgid "Updated" +msgstr "Atualizado" + +msgid "Scope" +msgstr "Escopo" + +msgid "Scopes" +msgstr "Escopos" + +msgid "Expires" +msgstr "Expira" + +msgid "Token" +msgstr "Token" + +msgid "User" +msgstr "Usuário" + +msgid "Code" +msgstr "Código" + +msgid "Redirect URI" +msgstr "URI de Redirecionamento" + +msgid "State" +msgstr "Estado" + +msgid "Nonce" +msgstr "Nonce (Number Used Once)" + +msgid "Code Challenge" +msgstr "Desafio do Código" + +msgid "Code Challenge Method" +msgstr "Método do Desafio do Código" + +msgid "PKCE required" +msgstr "PKCE obrigatório" + +msgid "Post logout redirect URI" +msgstr "URI de redirecionamento pós-logout" + +msgid "Hash client secret" +msgstr "Hash da chave secreta do cliente" + +msgid "The client secret will be hashed" +msgstr "A chave secreta do cliente será transformada em hash" + +msgid "Skip authorization completely" +msgstr "Pular autorização completamente" + +msgid "Post logout redirect URIs" +msgstr "URIs de redirecionamento pós-logout" + +msgid "Allowed Post Logout Redirect URIs list, space separated" +msgstr "Lista de URIs de redirecionamento pós-logout permitidas, separadas por espaços" + +msgid "Authorization Grant" +msgstr "Concessão de Autorização" + +msgid "Authorization Grants" +msgstr "Concessões de Autorização" + +msgid "Invalid client or client credentials" +msgstr "Cliente inválido ou credenciais do cliente inválidas" + +msgid "Invalid authorization code" +msgstr "Código de autorização inválido" + +msgid "Invalid redirect URI" +msgstr "URI de redirecionamento inválida" + +msgid "Invalid grant type" +msgstr "Tipo de concessão inválido" + +msgid "Invalid scope" +msgstr "Escopo inválido" + +msgid "Invalid request" +msgstr "Solicitação inválida" + +msgid "Unsupported grant type" +msgstr "Tipo de concessão não suportado" + +msgid "Unsupported response type" +msgstr "Tipo de resposta não suportado" + +msgid "Authorization server error" +msgstr "Erro do servidor de autorização" + +msgid "Temporarily unavailable" +msgstr "Temporariamente indisponível" + +msgid "Access denied" +msgstr "Acesso negado" + +msgid "Invalid client" +msgstr "Cliente inválido" + +msgid "The client identifier provided is invalid" +msgstr "O identificador do cliente fornecido é inválido" + +msgid "The client authentication failed" +msgstr "A autenticação do cliente falhou" + +msgid "The authorization grant is invalid, expired, or revoked" +msgstr "A concessão de autorização é inválida, expirada ou revogada" + +msgid "The authenticated client is not authorized to use this authorization grant type" +msgstr "O cliente autenticado não está autorizado a usar este tipo de concessão de autorização" + +msgid "The request is missing a required parameter" +msgstr "A solicitação está perdendo um parâmetro obrigatório" + +msgid "The authorization server encountered an unexpected condition" +msgstr "O servidor de autorização encontrou uma condição inesperada" + +msgid "The authorization server is currently unable to handle the request" +msgstr "O servidor de autorização atualmente não consegue processar a solicitação" + +msgid "The resource owner or authorization server denied the request" +msgstr "O proprietário do recurso ou servidor de autorização negou a solicitação" + +msgid "The authorization server does not support obtaining authorization codes" +msgstr "O servidor de autorização não suporta obter códigos de autorização" + +msgid "The authorization server does not support the revocation of access tokens" +msgstr "O servidor de autorização não suporta a revogação de tokens de acesso" + +msgid "The authorization server does not support the revocation of refresh tokens" +msgstr "O servidor de autorização não suporta a revogação de tokens de atualização" + +msgid "The authorization server does not support the use of the transformation parameter" +msgstr "O servidor de autorização não suporta o uso do parâmetro de transformação" + +msgid "The target resource is invalid, missing, malformed, or not supported" +msgstr "O recurso de destino é inválido, ausente, malformado ou não suportado" + +msgid "Rotate refresh token" +msgstr "Rotacionar token de atualização" + +msgid "Reuse refresh token" +msgstr "Reutilizar token de atualização" + +msgid "Application name" +msgstr "Nome da aplicação" + +msgid "Application description" +msgstr "Descrição da aplicação" + +msgid "Application logo" +msgstr "Logo da aplicação" + +msgid "Application website" +msgstr "Website da aplicação" + +msgid "Terms of service" +msgstr "Termos de serviço" + +msgid "Privacy policy" +msgstr "Política de privacidade" + +msgid "Support email" +msgstr "Email de suporte" + +msgid "Support URL" +msgstr "URL de suporte" + +msgid "Redirect URIs" +msgstr "URIs de Redirecionamento" + +msgid "Source refresh token" +msgstr "Token de atualização de origem" + +msgid "Token checksum" +msgstr "Checksum do token" + +msgid "Token family" +msgstr "Família do token" + +msgid "Code challenge" +msgstr "Desafio do código" + +msgid "Code challenge method" +msgstr "Método do desafio do código" + +msgid "Claims" +msgstr "Reivindicações" + +msgid "JWT Token ID" +msgstr "ID do Token JWT" + +msgid "Allowed origins" +msgstr "Origens permitidas" + +msgid "Client ID" +msgstr "ID do Cliente" + +msgid "Revoked" +msgstr "Revogado" + +msgid "Authorization grant type" +msgstr "Tipo de concessão de autorização" + +msgid "Hashed on Save. Copy it now if this is a new secret." +msgstr "Hashed em Salvar. Copie agora se este for um novo segredo." + +msgid "allowed origin URI Validation error" +msgstr "erro de validação de URI de origem permitido. invalid_scheme" + +msgid "Date" +msgstr "Data" From 52b0b2f103c647280ed7711fda0ba674913addc0 Mon Sep 17 00:00:00 2001 From: David Reguera <33068707+nablabits@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:47:06 +0200 Subject: [PATCH 103/115] chore(docs): Move around label targets. (#1576) The ...createapplication anchor seems to have ended up in the wrong spot. This moves it to just before the heading. --- docs/management_commands.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/management_commands.rst b/docs/management_commands.rst index 83770041e..0a3f1bda0 100644 --- a/docs/management_commands.rst +++ b/docs/management_commands.rst @@ -5,8 +5,6 @@ Django OAuth Toolkit exposes some useful management commands that can be run via or :doc:`Celery `. .. _cleartokens: -.. _createapplication: - cleartokens ~~~~~~~~~~~ @@ -27,7 +25,7 @@ The ``cleartokens`` management command will also delete expired access and ID to Note: Refresh tokens need to expire before AccessTokens can be removed from the database. Using ``cleartokens`` without ``REFRESH_TOKEN_EXPIRE_SECONDS`` has limited effect. - +.. _createapplication: createapplication ~~~~~~~~~~~~~~~~~ From 0a5ffdbca596d1b21e7f98ea56ead2cec76192c7 Mon Sep 17 00:00:00 2001 From: Subham Date: Thu, 14 Aug 2025 04:55:52 +0530 Subject: [PATCH 104/115] chore: update deprecated jwcrypto key_type to kty in tests (#1590) key_type was deprecated in latchset/jwcrypto@0edf66d, https://github.com/latchset/jwcrypto/releases/tag/v0.9.0 --- tests/test_authorization_code.py | 2 +- tests/test_models.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index f162e211a..660e5e5d4 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -1867,7 +1867,7 @@ def test_id_token(self): # Check decoding JWT using HS256 key = self.application.jwk_key - assert key.key_type == "oct" + assert key.kty == "oct" jwt_token = jwt.JWT(key=key, jwt=content["id_token"]) claims = json.loads(jwt_token.claims) assert claims["sub"] == "1" diff --git a/tests/test_models.py b/tests/test_models.py index 2c7ff8114..eb01aac8f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -563,7 +563,7 @@ def test_clear_expired_id_tokens(oauth2_settings, oidc_tokens, rf): def test_application_key(oauth2_settings, application): # RS256 key key = application.jwk_key - assert key.key_type == "RSA" + assert key.kty == "RSA" # RS256 key, but not configured oauth2_settings.OIDC_RSA_PRIVATE_KEY = None @@ -574,7 +574,7 @@ def test_application_key(oauth2_settings, application): # HS256 key application.algorithm = Application.HS256_ALGORITHM key = application.jwk_key - assert key.key_type == "oct" + assert key.kty == "oct" # No algorithm application.algorithm = Application.NO_ALGORITHM From 762cf8d1d35f72071626de788aa8948111f6cdcc Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Fri, 3 Oct 2025 11:21:06 -0400 Subject: [PATCH 105/115] chore: update from jazzband to django-oauth (#1592) --- .github/workflows/release.yml | 11 ++- CHANGELOG.md | 22 ++--- CODE_OF_CONDUCT.md | 150 ++++++++++++++++++++++--------- CONTRIBUTING.md | 8 +- Dockerfile | 4 +- README.rst | 47 ++++------ docs/advanced_topics.rst | 2 +- docs/contributing.rst | 24 +++-- docs/index.rst | 2 +- docs/settings.rst | 2 +- pyproject.toml | 2 +- tests/test_authorization_code.py | 2 +- tests/test_models.py | 2 +- 13 files changed, 163 insertions(+), 115 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64302e819..0c4329265 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: build: - if: github.repository == 'jazzband/django-oauth-toolkit' + if: github.repository == 'django-oauth/django-oauth-toolkit' runs-on: ubuntu-latest steps: @@ -29,10 +29,9 @@ jobs: python -m build twine check dist/* - - name: Upload packages to Jazzband + - name: Upload packages to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: - user: jazzband - password: ${{ secrets.JAZZBAND_RELEASE_KEY }} - repository_url: https://jazzband.co/projects/django-oauth-toolkit/upload + user: __token__ + password: ${{ secrets.PYPI_PUBLISH_TOKEN }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 32dd1734c..59eb50c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1506 Support for Wildcard Origin and Redirect URIs * #1586 Turkish language support added - @@ -241,7 +243,7 @@ This is a major release with **BREAKING** changes. Please make sure to review th ## [1.6.1] 2021-12-23 ### Changed -* Note: Only Django 4.0.1+ is supported due to a regression in Django 4.0.0. [Explanation](https://github.com/jazzband/django-oauth-toolkit/pull/1046#issuecomment-998015272) +* Note: Only Django 4.0.1+ is supported due to a regression in Django 4.0.0. [Explanation](https://github.com/django-oauth/django-oauth-toolkit/pull/1046#issuecomment-998015272) ### Fixed * Miscellaneous 1.6.0 packaging issues. @@ -332,7 +334,7 @@ This is a major release with **BREAKING** changes. Please make sure to review th ### Fixed * #812: Reverts #643 pass wrong request object to authenticate function. -* Fix concurrency issue with refresh token requests (#[810](https://github.com/jazzband/django-oauth-toolkit/pull/810)) +* Fix concurrency issue with refresh token requests (#[810](https://github.com/django-oauth/django-oauth-toolkit/pull/810)) * #817: Reverts #734 tutorial documentation error. @@ -371,16 +373,16 @@ This is a major release with **BREAKING** changes. Please make sure to review th ### Fixed * Fix a race condition in creation of AccessToken with external oauth2 server. -* Fix several concurrency issues. (#[638](https://github.com/jazzband/django-oauth-toolkit/issues/638)) -* Fix to pass `request` to `django.contrib.auth.authenticate()` (#[636](https://github.com/jazzband/django-oauth-toolkit/issues/636)) +* Fix several concurrency issues. (#[638](https://github.com/django-oauth/django-oauth-toolkit/issues/638)) +* Fix to pass `request` to `django.contrib.auth.authenticate()` (#[636](https://github.com/django-oauth/django-oauth-toolkit/issues/636)) * Fix missing `oauth2_error` property exception oauthlib_core.verify_request method raises exceptions in authenticate. - (#[633](https://github.com/jazzband/django-oauth-toolkit/issues/633)) + (#[633](https://github.com/django-oauth/django-oauth-toolkit/issues/633)) * Fix "django.db.utils.NotSupportedError: FOR UPDATE cannot be applied to the nullable side of an outer join" for postgresql. - (#[714](https://github.com/jazzband/django-oauth-toolkit/issues/714)) + (#[714](https://github.com/django-oauth/django-oauth-toolkit/issues/714)) * Fix to return a new refresh token during grace period rather than the recently-revoked one. - (#[702](https://github.com/jazzband/django-oauth-toolkit/issues/702)) + (#[702](https://github.com/django-oauth/django-oauth-toolkit/issues/702)) * Fix a bug in refresh token revocation. - (#[625](https://github.com/jazzband/django-oauth-toolkit/issues/625)) + (#[625](https://github.com/django-oauth/django-oauth-toolkit/issues/625)) ## 1.2.0 [2018-06-03] @@ -402,7 +404,7 @@ This is a major release with **BREAKING** changes. Please make sure to review th * **Critical**: Django OAuth Toolkit 1.1.0 contained a migration that would revoke all existing RefreshTokens (`0006_auto_20171214_2232`). This release corrects the migration. If you have already ran it in production, please see the following issue for more details: - https://github.com/jazzband/django-oauth-toolkit/issues/589 + https://github.com/django-oauth/django-oauth-toolkit/issues/589 ## 1.1.0 [2018-04-13] diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e0d5efab5..e5ee8d275 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,46 +1,108 @@ # Code of Conduct -As contributors and maintainers of the Jazzband projects, and in the interest of -fostering an open and welcoming community, we pledge to respect all people who -contribute through reporting issues, posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. - -We are committed to making participation in the Jazzband a harassment-free experience -for everyone, regardless of the level of experience, gender, gender identity and -expression, sexual orientation, disability, personal appearance, body size, race, -ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery -- Personal attacks -- Trolling or insulting/derogatory comments -- Public or private harassment -- Publishing other's private information, such as physical or electronic addresses, - without explicit permission -- Other unethical or unprofessional conduct - -The Jazzband roadies have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are not -aligned to this Code of Conduct, or to ban temporarily or permanently any contributor -for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -By adopting this Code of Conduct, the roadies commit themselves to fairly and -consistently applying these principles to every aspect of managing the jazzband -projects. Roadies who do not follow or enforce the Code of Conduct may be permanently -removed from the Jazzband roadies. - -This code of conduct applies both within project spaces and in public spaces when an -individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by -contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and -investigated and will result in a response that is deemed necessary and appropriate to -the circumstances. Roadies are obligated to maintain confidentiality with regard to the -reporter of an incident. - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version -1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] - -[homepage]: https://contributor-covenant.org -[version]: https://contributor-covenant.org/version/1/3/0/ +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Maintainers are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[django-oauth-coc@googlegroups.com](mailto:django-oauth-coc@googlegroups.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Warning + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 2. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 3. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Django Commons Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49518f460..b89d471e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,10 @@ -[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) +# Contribute to Django OAuth Toolkit -This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines). +Thanks for your interest, we love contributions! There are many ways to participate. We are always in need of help with code review, bug fixes, feature development, documentation, and community development. -# Contribute to Django OAuth Toolkit +By contributing you agree to abide by the [Code of Conduct](./CODE_OF_CONDUCT.md) -Thanks for your interest, we love contributions! +We are striving to make a free and open source standards compliant OAuth2/OIDC Identity Provider that adheres to best practices out of the box. Let that goal be your guide as you make decisions related to the project. Please [follow these guidelines](https://django-oauth-toolkit.readthedocs.io/en/latest/contributing.html) when submitting pull requests. diff --git a/Dockerfile b/Dockerfile index 4565df5ff..8828cead5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,8 +37,8 @@ FROM python:3.11.6-slim # allow embed sha1 at build time as release. ARG GIT_SHA1 -LABEL org.opencontainers.image.authors="https://jazzband.co/projects/django-oauth-toolkit" -LABEL org.opencontainers.image.source="https://github.com/jazzband/django-oauth-toolkit" +LABEL org.opencontainers.image.authors="https://github.com/django-oauth/django-oauth-toolkit/blob/master/AUTHORS" +LABEL org.opencontainers.image.source="https://github.com/django-oauth/django-oauth-toolkit" LABEL org.opencontainers.image.revision=${GIT_SHA1} diff --git a/README.rst b/README.rst index e8b49d2a6..e58098806 100644 --- a/README.rst +++ b/README.rst @@ -1,21 +1,17 @@ Django OAuth Toolkit ==================== -.. image:: https://jazzband.co/static/img/badge.svg - :target: https://jazzband.co/ - :alt: Jazzband - *OAuth2 goodies for the Djangonauts!* .. image:: https://badge.fury.io/py/django-oauth-toolkit.svg :target: http://badge.fury.io/py/django-oauth-toolkit -.. image:: https://github.com/jazzband/django-oauth-toolkit/workflows/Test/badge.svg - :target: https://github.com/jazzband/django-oauth-toolkit/actions +.. image:: https://github.com/django-oauth/django-oauth-toolkit/workflows/Test/badge.svg + :target: https://github.com/django-oauth/django-oauth-toolkit/actions :alt: GitHub Actions -.. image:: https://codecov.io/gh/jazzband/django-oauth-toolkit/branch/master/graph/badge.svg - :target: https://codecov.io/gh/jazzband/django-oauth-toolkit +.. image:: https://codecov.io/gh/django-oauth/django-oauth-toolkit/branch/master/graph/badge.svg + :target: https://codecov.io/gh/django-oauth/django-oauth-toolkit :alt: Coverage .. image:: https://img.shields.io/pypi/pyversions/django-oauth-toolkit.svg @@ -38,7 +34,7 @@ capabilities to your Django projects. Django OAuth Toolkit makes extensive use o Reporting security issues ------------------------- -Please report any security issues to the JazzBand security team at . Do not file an issue on the tracker. +Please report any security issues to the Django OAuth security team at . Do not file an issue on the tracker. Requirements ------------ @@ -78,7 +74,7 @@ If you need an OAuth2 provider you'll want to add the following to your ``urls.p Changelog --------- -See `CHANGELOG.md `_. +See `CHANGELOG.md `_. Documentation @@ -99,9 +95,8 @@ We need help maintaining and enhancing django-oauth-toolkit (DOT). Join the team ~~~~~~~~~~~~~ -Please consider joining `Jazzband `__ (If not -already a member) and the `DOT project -team `__. +There are no barriers to participation. Anyone can open an issue, pr, or review a pull request. Please +dive in! How you can help ~~~~~~~~~~~~~~~~ @@ -109,15 +104,15 @@ How you can help See our `contributing `__ info and the open -`issues `__ and -`PRs `__, +`issues `__ and +`PRs `__, especially those labeled -`help-wanted `__. +`help-wanted `__. Discussions ~~~~~~~~~~~ Have questions or want to discuss the project? -See `the discussions `__. +See `the discussions `__. Submit PRs and Perform Reviews @@ -127,18 +122,12 @@ PR submissions and reviews are always appreciated! Since we require an independent review of any PR before it can be merged, having your second set of eyes looking at PRs is extremely valuable. -Please don’t merge PRs -~~~~~~~~~~~~~~~~~~~~~~ - -Please be aware that we don’t want *every* Jazzband member to merge PRs -but just a handful of project team members so that we can maintain a -modicum of control over what goes into a release of this security oriented code base. Only `project -leads `__ are able to -publish releases to Pypi and it becomes difficult when creating a new -release for the leads to deal with “unexpected” merged PRs. -Become a Project Lead +Become a Maintainer ~~~~~~~~~~~~~~~~~~~~~ -If you are interested in stepping up to be a Project Lead, please take a look at -the `discussion about this `__. +If you are interested in stepping up to be a Maintainer, please open an issue. For maintainers we're +looking for a positive attitude, attentiveness to the specifications, strong coding and +communication skills, and a willingness to work with others. Maintainers are responsible for +merging pull requests, managing issues, creating releases, and ensuring the overall health of the +project. diff --git a/docs/advanced_topics.rst b/docs/advanced_topics.rst index 204e3f860..a99559e93 100644 --- a/docs/advanced_topics.rst +++ b/docs/advanced_topics.rst @@ -63,7 +63,7 @@ That's all, now Django OAuth Toolkit will use your model wherever an Application .. note:: ``OAUTH2_PROVIDER_APPLICATION_MODEL`` is the only setting variable that is not namespaced, this is because of the way Django currently implements swappable models. - See `issue #90 `_ for details. + See `issue #90 `_ for details. Configuring multiple databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/contributing.rst b/docs/contributing.rst index 569f5eab2..1b491d70b 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -2,17 +2,13 @@ Contributing ============ -.. image:: https://jazzband.co/static/img/jazzband.svg - :target: https://jazzband.co/ - :alt: Jazzband - -This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. +By contributing you agree to abide by the `Code of Conduct `_ and follow the `guidelines `_. Setup ===== -Fork ``django-oauth-toolkit`` repository on `GitHub `_ and follow these steps: +Fork ``django-oauth-toolkit`` repository on `GitHub `_ and follow these steps: * Create a virtualenv and activate it * Clone your repository locally @@ -21,7 +17,7 @@ Issues ====== You can find the list of bugs, enhancements and feature requests on the -`issue tracker `_. If you want to fix an issue, pick up one and +`issue tracker `_. If you want to fix an issue, pick up one and add a comment stating you're working on it. Code Style @@ -161,7 +157,7 @@ When you begin your PR, you'll be asked to provide the following: * ``Fixed`` for any bug fixes. * ``Security`` in case of vulnerabilities. (Please report any security issues to the - JazzBand security team ````. Do not file an issue on the tracker + security team ````. Do not file an issue on the tracker or submit a PR until directed to do so.) * Make sure your name is in :file:`AUTHORS`. We want to give credit to all contributors! @@ -169,7 +165,7 @@ When you begin your PR, you'll be asked to provide the following: If your PR is not yet ready to be merged mark it as a Work-in-Progress By prepending ``WIP:`` to the PR title so that it doesn't get inadvertently approved and merged. -Make sure to request a review by assigning Reviewer ``jazzband/django-oauth-toolkit``. +Make sure to request a review by assigning Reviewer ``django-oauth/django-oauth-toolkit``. This will assign the review to the project team and a member will review it. In the meantime you can continue to add commits to your topic branch (and push them up to GitHub) either if you see something that needs changing, or in response to a reviewer's comments. If a reviewer asks for changes, you do not need to close the pull and reissue it @@ -184,7 +180,7 @@ outdated code and your changes diverge too far from master, the pull request has To pull in upstream changes:: - git remote add upstream https://github.com/jazzband/django-oauth-toolkit.git + git remote add upstream https://github.com/django-oauth/django-oauth-toolkit.git git fetch upstream Then merge the changes that you fetched:: @@ -316,7 +312,7 @@ Reviewing and Merging PRs ------------------------- - Make sure the PR description includes the `pull request template - `_ + `_ - Confirm that all required checklist items from the PR template are both indicated as done in the PR description and are actually done. - Perform a careful review and ask for any needed changes. @@ -351,11 +347,11 @@ password: password Publishing a Release -------------------- -Only Project Leads can `publish a release `_ to pypi.org +Only maintainers can publish a release to pypi.org and rtfd.io. This checklist is a reminder of the required steps. - When planning a new release, create a `milestone - `_ + `_ and assign issues, PRs, etc. to that milestone. - Review all commits since the last release and confirm that they are properly documented in the CHANGELOG. Reword entries as appropriate with links to docs @@ -366,7 +362,7 @@ and rtfd.io. This checklist is a reminder of the required steps. - :file:`oauth2_provider/__init__.py` to set ``__version__ = "..."`` - Once the final PR is merged, create and push a tag for the release. You'll shortly - get a notification from Jazzband of the availability of two pypi packages (source tgz + get a notification of the availability of two pypi packages (source tgz and wheel). Download these locally before releasing them. - Do a ``tox -e build`` and extract the downloaded and built wheel zip and tgz files into temp directories and do a ``diff -r`` to make sure they have the same content. diff --git a/docs/index.rst b/docs/index.rst index 07ed24314..8519827ed 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ See our :doc:`Changelog ` for information on updates. Support ------- -If you need help please submit a `question `_. +If you need help please submit a `question `_. Requirements ------------ diff --git a/docs/settings.rst b/docs/settings.rst index 985ca5d2c..9c71bb2a8 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -449,7 +449,7 @@ List of non-namespaced settings .. note:: These settings must be set as top-level Django settings (outside of ``OAUTH2_PROVIDER``), because of the way Django currently implements swappable models. - See `issue #90 `_ for details. + See `issue #90 `_ for details. OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL diff --git a/pyproject.toml b/pyproject.toml index 401d33cab..2bb4a83b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dev = [ [project.urls] Homepage = "https://django-oauth-toolkit.readthedocs.io/" -Repository = "https://github.com/jazzband/django-oauth-toolkit" +Repository = "https://github.com/django-oauth/django-oauth-toolkit" [tool.setuptools.dynamic] version = {attr = "oauth2_provider.__version__"} diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index 660e5e5d4..360fac957 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -505,7 +505,7 @@ def test_code_post_auth_failing_redirection_uri_with_querystring(self): """ Test that in case of error the querystring of the redirection uri is preserved - See https://github.com/jazzband/django-oauth-toolkit/issues/238 + See https://github.com/django-oauth/django-oauth-toolkit/issues/238 """ self.client.login(username="test_user", password="123456") diff --git a/tests/test_models.py b/tests/test_models.py index eb01aac8f..8c0048066 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -168,7 +168,7 @@ def test_custom_application_model(self): If a custom application model is installed, it should be present in the related objects and not the swapped out one. - See issue #90 (https://github.com/jazzband/django-oauth-toolkit/issues/90) + See issue #90 (https://github.com/django-oauth/django-oauth-toolkit/issues/90) """ related_object_names = [ f.name From 842a4d5b0f2ab7019b42a0cd3ae913008e7fce6a Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Fri, 3 Oct 2025 12:11:08 -0400 Subject: [PATCH 106/115] Release 3.1 (#1600) * chore: prepare 3.1 release * chore: full release --- CHANGELOG.md | 18 ++++++++++++++++-- oauth2_provider/__init__.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59eb50c61..1f17685ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [unreleased] + + +## [3.1.0] + + +**NOTE**: This is the first release under the new [django-oauth](https://github.com/django-oauth) organization. The project moved in order to be more independent and to bypass quota limits on parallel CI jobs we were encountering in Jazzband. The project will emulateDjango Commons going forward in it's operation. We're always on the look for willing maintainers and contributors. Feel free to start participating any time. PR's are always welcome. + ### Added -* #1506 Support for Wildcard Origin and Redirect URIs +* #1506 Support for Wildcard Origin and Redirect URIs - Adds a new setting [ALLOW_URL_WILDCARDS](https://django-oauth-toolkit.readthedocs.io/en/latest/settings.html#allow-uri-wildcards). This feature is useful for working with CI service such as cloudflare, netlify, and vercel that offer branch +deployments for development previews and user acceptance testing. * #1586 Turkish language support added ### Changed diff --git a/oauth2_provider/__init__.py b/oauth2_provider/__init__.py index 055276878..f5f41e567 100644 --- a/oauth2_provider/__init__.py +++ b/oauth2_provider/__init__.py @@ -1 +1 @@ -__version__ = "3.0.1" +__version__ = "3.1.0" From 0164aec7fb7ee7f8f755d4f992252133c4d72ab7 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Sun, 2 Nov 2025 01:09:20 -0400 Subject: [PATCH 107/115] chore: update rp and idp styling (#1607) - rp improve styling for readability - rp updated to latest @dopry/svelte-oidc to get EventLog - idp add home page with links to admin and rp - add tasks to pr template asking contributors to update the idp and rp with new features --- .github/pull_request_template.md | 2 + .github/workflows/test.yml | 8 +- tests/app/idp/idp/urls.py | 2 + tests/app/idp/templates/home/index.html | 19 + tests/app/rp/package-lock.json | 1924 ++---- tests/app/rp/package.json | 24 +- tests/app/rp/src/app.html | 73 +- tests/app/rp/src/routes/+page.svelte | 67 +- tests/app/rp/static/materialize.min.css | 7732 +++++++++++++++++++++++ 9 files changed, 8556 insertions(+), 1295 deletions(-) create mode 100644 tests/app/idp/templates/home/index.html create mode 100644 tests/app/rp/static/materialize.min.css diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9e41b33cf..cf8622d1a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,3 +14,5 @@ Fixes # - [ ] documentation updated - [ ] `CHANGELOG.md` updated (only for user relevant changes) - [ ] author name in `AUTHORS` +- [ ] tests/app/idp updated to demonstrate new features +- [ ] tests/app/rp updated to demonstrate new features diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b453d269..b4cdaa8fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,10 @@ name: Test on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test-package: name: Test Package (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) @@ -74,8 +78,8 @@ jobs: fail-fast: false matrix: node-version: - - "18.x" - - "20.x" + - "22.x" + - "24.x" steps: - name: Checkout uses: actions/checkout@v4 diff --git a/tests/app/idp/idp/urls.py b/tests/app/idp/idp/urls.py index 90e8abd48..8be65c35b 100644 --- a/tests/app/idp/idp/urls.py +++ b/tests/app/idp/idp/urls.py @@ -17,9 +17,11 @@ from django.contrib import admin from django.urls import include, path +from django.views.generic import TemplateView urlpatterns = [ + path('', TemplateView.as_view(template_name='home/index.html'), name='home'), # Maps the root URL to your home_view path("admin/", admin.site.urls), path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")), path("accounts/", include("django.contrib.auth.urls")), diff --git a/tests/app/idp/templates/home/index.html b/tests/app/idp/templates/home/index.html new file mode 100644 index 000000000..5176db721 --- /dev/null +++ b/tests/app/idp/templates/home/index.html @@ -0,0 +1,19 @@ + + + + + + + Identity Provider Home + + + +

Welcome to the Identity Provider (IdP)

+

This is the home page of the Identity Provider used for testing OAuth2 flows.

+

Please ensure that the test relying party is running to proceed with authentication tests.

+ + + \ No newline at end of file diff --git a/tests/app/rp/package-lock.json b/tests/app/rp/package-lock.json index c8186b56d..2e63efd62 100644 --- a/tests/app/rp/package-lock.json +++ b/tests/app/rp/package-lock.json @@ -8,46 +8,35 @@ "name": "rp", "version": "0.0.1", "dependencies": { - "@dopry/svelte-oidc": "^1.1.0" + "@dopry/svelte-oidc": "^1.2.0", + "jose": "^6.1.0" }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.1.1", - "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.20.6", - "prettier": "^3.3.2", - "prettier-plugin-svelte": "^3.2.4", - "svelte": "^4.2.19", - "svelte-check": "^3.8.0", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.4.19" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/adapter-node": "^5.4.0", + "@sveltejs/kit": "^2.48.4", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^5.43.2", + "svelte-check": "^4.3.3", + "tslib": "^2.8.1", + "typescript": "^5.9.3", + "vite": "^7.1.12" } }, "node_modules/@dopry/svelte-oidc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@dopry/svelte-oidc/-/svelte-oidc-1.1.0.tgz", - "integrity": "sha512-FfXm/f2vRNxFsYxKs8hal1Huf94dqKrRIppDzjDIH9cNy683b9sN9NUY0mZtrHc1yJL+jyfNNsB+bY9/9fCErA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dopry/svelte-oidc/-/svelte-oidc-1.2.0.tgz", + "integrity": "sha512-iQKkgxjua264dgkm9u2vxMRwN4CKQywOaAQlszFVxcricAjWb5hCmcxD6qeMvprhmE5gB+Bk7JXpuUAq21O72A==", "dependencies": { - "oidc-client": "1.11.5" + "oidc-client-ts": "^3.3.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -57,13 +46,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -73,13 +62,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -89,13 +78,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -105,13 +94,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -121,13 +110,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -137,13 +126,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -153,13 +142,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -169,13 +158,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -185,13 +174,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -201,13 +190,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -217,13 +206,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -233,13 +222,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -249,13 +238,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -265,13 +254,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -281,13 +270,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -297,13 +286,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -313,13 +302,29 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -329,13 +334,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -345,13 +366,29 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -361,13 +398,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -377,13 +414,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -393,13 +430,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -409,21 +446,27 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -435,19 +478,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -460,41 +494,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.28", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", @@ -502,20 +501,21 @@ "dev": true }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", - "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "version": "28.0.9", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz", + "integrity": "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", - "glob": "^8.0.3", + "fdir": "^6.2.0", "is-reference": "1.2.1", - "magic-string": "^0.30.3" + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0 || 14 >= 14.17" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" @@ -526,62 +526,6 @@ } } }, - "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -603,15 +547,14 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", "is-module": "^1.0.0", "resolve": "^1.22.1" }, @@ -649,16 +592,22 @@ } } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", "cpu": [ "arm" ], @@ -669,9 +618,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", "cpu": [ "arm64" ], @@ -682,9 +631,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", "cpu": [ "arm64" ], @@ -695,9 +644,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", "cpu": [ "x64" ], @@ -707,10 +656,36 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", "cpu": [ "arm" ], @@ -721,9 +696,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", "cpu": [ "arm" ], @@ -734,9 +709,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", "cpu": [ "arm64" ], @@ -747,9 +722,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", "cpu": [ "arm64" ], @@ -759,10 +734,23 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", "cpu": [ "ppc64" ], @@ -773,9 +761,22 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", "cpu": [ "riscv64" ], @@ -786,9 +787,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", "cpu": [ "s390x" ], @@ -799,9 +800,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", "cpu": [ "x64" ], @@ -812,9 +813,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", "cpu": [ "x64" ], @@ -824,10 +825,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", "cpu": [ "arm64" ], @@ -838,9 +852,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", "cpu": [ "ia32" ], @@ -850,10 +864,23 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", "cpu": [ "x64" ], @@ -863,27 +890,39 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", + "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8.9.0" + } + }, "node_modules/@sveltejs/adapter-auto": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.1.tgz", - "integrity": "sha512-6LeZft2Fo/4HfmLBi5CucMYmgRxgcETweQl/yQoZo/895K3S9YWYN4Sfm/IhwlIpbJp3QNvhKmwCHbsqQNYQpw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz", + "integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==", "dev": true, - "dependencies": { - "import-meta-resolve": "^4.0.0" - }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "node_modules/@sveltejs/adapter-node": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.0.1.tgz", - "integrity": "sha512-eYdmxdUWMW+dad1JfMsWBPY2vjXz9eE+52A2AQnXPScPJlIxIVk5mmbaEEzrZivLfO2wEcLTZ5vdC03W69x+iA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.4.0.tgz", + "integrity": "sha512-NMsrwGVPEn+J73zH83Uhss/hYYZN6zT3u31R3IHAn3MiKC3h8fjmIAhLfTSOeNHr5wPYfjjMg8E+1gyFgyrEcQ==", "dev": true, "dependencies": { - "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.9.5" }, "peerDependencies": { @@ -891,17 +930,18 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.20.6", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.6.tgz", - "integrity": "sha512-ImUkSQ//Xf4N9r0HHAe5vRA7RyQ7U1Ue1YUT235Ig+IiIqbsixEulHTHrP5LtBiC8xOkJoPZQ1VZ/nWHNOaGGw==", + "version": "2.48.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.48.4.tgz", + "integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==", "dev": true, - "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.1.0", + "devalue": "^5.3.2", "esm-env": "^1.2.2", - "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", @@ -916,50 +956,52 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3 || ^6.0.0" + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", - "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", + "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, - "peer": true, "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", - "debug": "^4.3.4", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.5" + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" } }, - "node_modules/@sveltejs/vite-plugin-svelte/node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", - "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", "dev": true, - "peer": true, "dependencies": { - "debug": "^4.3.4" + "debug": "^4.4.1" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@types/cookie": { @@ -969,15 +1011,9 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/pug": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", - "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "node_modules/@types/resolve": { @@ -987,9 +1023,10 @@ "dev": true }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -997,187 +1034,54 @@ "node": ">=0.4.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, "engines": { - "node": ">= 8" + "node": ">= 0.4" } }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, - "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "dependencies": { - "fill-range": "^7.1.1" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, - "node_modules/code-red/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -1187,42 +1091,13 @@ "node": ">= 0.6" } }, - "node_modules/core-js": { - "version": "3.30.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz", - "integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "peer": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1242,72 +1117,51 @@ "node": ">=0.10.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "dev": true - }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.2.tgz", + "integrity": "sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==", "dev": true }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/esm-env": { @@ -1317,58 +1171,38 @@ "dev": true, "license": "MIT" }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "node_modules/esrap": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz", + "integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1392,45 +1226,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1443,133 +1238,52 @@ "node": ">= 0.4" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "dependencies": { - "builtin-modules": "^3.3.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, "dependencies": { "@types/estree": "*" } }, + "node_modules/jose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1586,85 +1300,12 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/mri": { @@ -1686,16 +1327,15 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "peer": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -1710,55 +1350,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/oidc-client": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.11.5.tgz", - "integrity": "sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==", - "dependencies": { - "acorn": "^7.4.1", - "base64-js": "^1.5.1", - "core-js": "^3.8.3", - "crypto-js": "^4.0.0", - "serialize-javascript": "^4.0.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, + "node_modules/oidc-client-ts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.3.0.tgz", + "integrity": "sha512-t13S540ZwFOEZKLYHJwSfITugupW4uYLwuQSSXyKH/wHwZ+7FvgHE7gnNJh1YQIZ1Yd1hKSRjqeXGSUtS0r9JA==", "dependencies": { - "callsites": "^3.0.0" + "jwt-decode": "^4.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, "node_modules/path-parse": { @@ -1767,39 +1367,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -1816,8 +1405,8 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -1825,9 +1414,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1840,111 +1429,55 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.4.tgz", - "integrity": "sha512-tZv+ADfeOWFNQkXkRh6zUXE16w3Vla8x2Ug0B/EnSmjR4EnwdwZbGgL/liSwR1kcEALU5mAAyua98HBxheCxgg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", + "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" + "node": ">= 0.4" }, - "bin": { - "rimraf": "bin.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -1954,48 +1487,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -2008,45 +1524,6 @@ "node": ">=6" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", - "dev": true, - "dependencies": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -2067,21 +1544,6 @@ "node": ">=18" } }, - "node_modules/sorcery": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz", - "integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.14", - "buffer-crc32": "^1.0.0", - "minimist": "^1.2.0", - "sander": "^0.5.0" - }, - "bin": { - "sorcery": "bin/sorcery" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2091,18 +1553,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2116,149 +1566,76 @@ } }, "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/estree": "^1.0.1", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", + "version": "5.43.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.2.tgz", + "integrity": "sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA==", + "dev": true, + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^2.1.0", + "is-reference": "^3.0.3", "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/svelte-check": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.0.tgz", - "integrity": "sha512-7Nxn+3X97oIvMzYJ7t27w00qUf1Y52irE2RU2dQAd5PyvfGp4E7NLhFKVhb6PV2fx7dCRMpNKDIuazmGthjpSQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", + "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "chokidar": "^3.4.1", - "fast-glob": "^3.2.7", - "import-fresh": "^3.2.1", + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^5.1.3", - "typescript": "^5.0.3" + "sade": "^1.7.4" }, "bin": { "svelte-check": "bin/svelte-check" }, - "peerDependencies": { - "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" - } - }, - "node_modules/svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", - "dev": true, - "peer": true, "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" + "node": ">= 18.0.0" }, "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" } }, - "node_modules/svelte-preprocess": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", - "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==", + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@types/pug": "^2.0.6", - "detect-indent": "^6.1.0", - "magic-string": "^0.30.5", - "sorcery": "^0.11.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">= 16.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": "^0.55.0", - "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", - "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } + "@types/estree": "^1.0.6" } }, - "node_modules/svelte/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "dependencies": { - "is-number": "^7.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/totalist": { @@ -2271,40 +1648,42 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, - "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2313,19 +1692,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -2346,17 +1731,22 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vitefu": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", - "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dev": true, - "peer": true, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { @@ -2364,10 +1754,10 @@ } } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "dev": true } } diff --git a/tests/app/rp/package.json b/tests/app/rp/package.json index 603114a1a..8662ba38a 100644 --- a/tests/app/rp/package.json +++ b/tests/app/rp/package.json @@ -12,19 +12,21 @@ "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.1.1", - "@sveltejs/adapter-node": "^5.0.1", - "@sveltejs/kit": "^2.20.6", - "prettier": "^3.3.2", - "prettier-plugin-svelte": "^3.2.4", - "svelte": "^4.2.19", - "svelte-check": "^3.8.0", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.4.19" + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/adapter-node": "^5.4.0", + "@sveltejs/kit": "^2.48.4", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^5.43.2", + "svelte-check": "^4.3.3", + "tslib": "^2.8.1", + "typescript": "^5.9.3", + "vite": "^7.1.12" }, "type": "module", "dependencies": { - "@dopry/svelte-oidc": "^1.1.0" + "@dopry/svelte-oidc": "^1.2.0", + "jose": "^6.1.0" } } diff --git a/tests/app/rp/src/app.html b/tests/app/rp/src/app.html index 77ec85d79..7a8d95035 100644 --- a/tests/app/rp/src/app.html +++ b/tests/app/rp/src/app.html @@ -3,10 +3,79 @@ - + + + Django OAuth Toolkit RP Demo + %sveltekit.head% + -
%sveltekit.body%
+
+

Django OAuth Toolkit Test RP

+ +
%sveltekit.body%
+
diff --git a/tests/app/rp/src/routes/+page.svelte b/tests/app/rp/src/routes/+page.svelte index 1df1a226b..9641425b7 100644 --- a/tests/app/rp/src/routes/+page.svelte +++ b/tests/app/rp/src/routes/+page.svelte @@ -1,17 +1,16 @@ + + + + + +
+ +
+ + diff --git a/tests/app/rp/src/routes/device/+page.svelte b/tests/app/rp/src/routes/device/+page.svelte new file mode 100644 index 000000000..cfa9555e7 --- /dev/null +++ b/tests/app/rp/src/routes/device/+page.svelte @@ -0,0 +1,490 @@ + + + + Device Authorization Flow Test + + +
+

Test the OAuth 2.0 Device Authorization Grant

+

+ This page demonstrates the Device Authorization Flow (RFC 8628), which is used by devices + with limited input capabilities (like smart TVs, IoT devices, etc.) to obtain OAuth tokens. + Do not use device-authorization in a browser, this is just an illustrative example to + streamline manual testing for maintainers. It shows how you'd need to implement the flow on + your device. Have a look at this full user journey test for an implementation in Python. +

+
+ +{#if status === 'idle'} +
+

Step 1: Initiate Authorization

+

Click the button below to start the device authorization flow.

+ +
+{/if} + +{#if status === 'authorizing'} +
+

Initiating...

+

Contacting the authorization server...

+
+
+{/if} + +{#if status === 'polling'} +
+

Step 2: Authorize the Device

+

+ Open the verification URL below in a new tab, enter the user code, and approve the + authorization. +

+ +
+
+ User Code: + {userCode} +
+
+ Verification URL: + + {verificationUri} + +
+
+ Expires in: + {expiresIn} seconds +
+
+ + + +
+
+

Polling for authorization... (checking every {interval} seconds)

+
+ + +
+{/if} + +{#if status === 'complete'} +
+

✓ Authorization Complete!

+

Successfully obtained an access token.

+ +
+
+ Token Type: + {tokenType} +
+
+ Expires In: + {expiresInToken} seconds +
+ {#if scope} +
+ Scope: + {scope} +
+ {/if} +
+ Access Token: + +
+ {#if refreshToken} +
+ Refresh Token: + +
+ {/if} +
+ + +
+{/if} + +{#if status === 'error'} +
+

Error

+

{errorMessage}

+ +
+{/if} + +
+

How it works

+
    +
  1. + Device requests authorization: The device sends a request to the authorization + server with its client ID. +
  2. +
  3. + Server returns codes: The server responds with a device code, user code, + and verification URI. +
  4. +
  5. + User authorizes: The user visits the verification URI on another device + (like a phone or computer), enters the user code, and approves the authorization. +
  6. +
  7. + Device polls for token: Meanwhile, the device polls the token endpoint using + the device code until the user completes authorization. +
  8. +
  9. + Token granted: Once the user approves, the polling request returns the access + token. +
  10. +
+
+ + diff --git a/tests/test_device.py b/tests/test_device.py new file mode 100644 index 000000000..727c81002 --- /dev/null +++ b/tests/test_device.py @@ -0,0 +1,769 @@ +from datetime import datetime, timedelta, timezone +from unittest import mock +from urllib.parse import urlencode + +import django.http.response +import pytest +from django import http +from django.conf import settings +from django.contrib.auth import get_user_model +from django.test import RequestFactory +from django.urls import reverse + +import oauth2_provider.models +from oauth2_provider.models import ( + get_access_token_model, + get_application_model, + get_device_grant_model, + get_refresh_token_model, +) +from oauth2_provider.utils import set_oauthlib_user_to_device_request_user + +from . import presets +from .common_testing import OAuth2ProviderTestCase as TestCase + + +Application = get_application_model() +AccessToken = get_access_token_model() +RefreshToken = get_refresh_token_model() +UserModel = get_user_model() +DeviceModel: oauth2_provider.models.DeviceGrant = get_device_grant_model() + + +@pytest.mark.usefixtures("oauth2_settings") +@pytest.mark.oauth2_settings(presets.DEFAULT_SCOPES_RW) +class DeviceFlowBaseTestCase(TestCase): + factory = RequestFactory() + + @classmethod + def setUpTestData(cls): + cls.test_user = UserModel.objects.create_user("test_user", "test@example.com", "123456") + cls.dev_user = UserModel.objects.create_user("dev_user", "dev@example.com", "123456") + + cls.application = Application.objects.create( + name="test_client_credentials_app", + user=cls.dev_user, + client_type=Application.CLIENT_PUBLIC, + authorization_grant_type=Application.GRANT_DEVICE_CODE, + client_secret="abcdefghijklmnopqrstuvwxyz1234567890", + ) + + def tearDown(self): + DeviceModel.objects.all().delete() + return super().tearDown() + + +class TestDeviceFlow(DeviceFlowBaseTestCase): + """ + The first 2 tests test the device flow in order + how the device flow works + """ + + @mock.patch( + "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token", + lambda: "abc", + ) + def test_device_flow_authorization_initiation(self): + """ + Tests the initial stage of the flow when the device sends its device authorization + request to the authorization server. + + Device Authorization Request(https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) + + This request shape: + POST /device_authorization HTTP/1.1 + Host: server.example.com + Content-Type: application/x-www-form-urlencoded + + client_id=1406020730&scope=example_scope + + Should respond with this response shape: + Device Authorization Response (https://datatracker.ietf.org/doc/html/rfc8628#section-3.2) + { + "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + "user_code": "WDJB-MJHT", + "verification_uri": "https://example.com/device", + "expires_in": 1800, + "interval": 5 + } + """ + + self.oauth2_settings.OAUTH_DEVICE_VERIFICATION_URI = "example.com/device" + self.oauth2_settings.OAUTH_DEVICE_USER_CODE_GENERATOR = lambda: "xyz" + + request_data: dict[str, str] = { + "client_id": self.application.client_id, + } + request_as_x_www_form_urlencoded: str = urlencode(request_data) + + response: django.http.response.JsonResponse = self.client.post( + reverse("oauth2_provider:device-authorization"), + data=request_as_x_www_form_urlencoded, + content_type="application/x-www-form-urlencoded", + ) + + assert response.status_code == 200 + + # let's make sure the device was created in the db + assert DeviceModel.objects.get(device_code="abc").status == DeviceModel.AUTHORIZATION_PENDING + + assert response.json() == { + "verification_uri": "example.com/device", + "expires_in": 1800, + "user_code": "xyz", + "device_code": "abc", + "interval": 5, + } + + @mock.patch( + "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token", + lambda: "abc", + ) + def test_device_flow_authorization_user_code_confirm_and_access_token(self): + """ + This is a full user journey test. + + The device initiates the flow by calling the /device-authorization endpoint and starts + polling the /authorize endpoint getting back error until the user approves in the + browser. + + In the meantime, the user visits the /device endpoint in their browsers to submit the + user code and approve, after which the /authorize returns the tokens to the device. + """ + + # ----------------------- + # 0: Setup device flow, where the device sends an authorization request and + # starts polling. The polling will fail because the user has not approved yet + # ----------------------- + self.oauth2_settings.OAUTH_DEVICE_VERIFICATION_URI = "example.com/device" + self.oauth2_settings.OAUTH_DEVICE_USER_CODE_GENERATOR = lambda: "xyz" + self.oauth2_settings.OAUTH_PRE_TOKEN_VALIDATION = [set_oauthlib_user_to_device_request_user] + + request_data: dict[str, str] = { + "client_id": self.application.client_id, + } + request_as_x_www_form_urlencoded: str = urlencode(request_data) + + device_authorization_response: http.response.JsonResponse = self.client.post( + reverse("oauth2_provider:device-authorization"), + data=request_as_x_www_form_urlencoded, + content_type="application/x-www-form-urlencoded", + ) + + assert device_authorization_response.__getitem__("content-type") == "application/json" + device = DeviceModel.objects.get(device_code="abc") + self.assertJSONEqual( + raw=device_authorization_response.content, + expected_data={ + "verification_uri": "example.com/device", + "expires_in": 1800, + "user_code": device.user_code, + "device_code": device.device_code, + "interval": 5, + }, + ) + + # Device polls /token and gets back error because the user hasn't approved yet + token_payload = { + "device_code": device.device_code, + "client_id": self.application.client_id, + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + token_response: http.response.JsonResponse = self.client.post( + "/o/token/", + data=urlencode(token_payload), + content_type="application/x-www-form-urlencoded", + ) + # TokenView should always respond with application/json as it's meant to be + # consumed by devices. + assert token_response.__getitem__("content-type") == "application/json" + assert token_response.status_code == 400 + self.assertJSONEqual(raw=token_response.content, expected_data={"error": "authorization_pending"}) + + # /device and /device_confirm require a user to be logged in + # to access it + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + # -------------------------------------------------------------------------------- + # 1. User visits the /device endpoint in their browsers and submits the user code + # submits wrong code then right code + # -------------------------------------------------------------------------------- + + # 1. User visits the /device endpoint in their browsers and submits the user code + # (GET Request to load it) + get_response = self.client.get(reverse("oauth2_provider:device")) + assert get_response.status_code == 200 + assert "form" in get_response.context # Ensure the form is rendered in the context + + # 1.1.0 User visits the /device endpoint in their browsers and submits wrong user code + self.assertContains( + self.client.post(reverse("oauth2_provider:device"), data={"user_code": "invalid_code"}), + status_code=200, + text="Incorrect user code", + count=1, + ) + + # Note: the device not being in the expected test covered in the other tests + + # 1.1.1: user submits valid user code + device_confirm_url = reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "xyz", "client_id": self.application.client_id}, + ) + + self.assertRedirects( + response=self.client.post( + reverse("oauth2_provider:device"), + data={"user_code": "xyz"}, + ), + expected_url=device_confirm_url, + ) + + # -------------------------------------------------------------------------------- + # 2: We redirect to the accept/deny form (the user is still in their browser) + # and approves + # -------------------------------------------------------------------------------- + device_grant_status_url = reverse( + "oauth2_provider:device-grant-status", + kwargs={"user_code": "xyz", "client_id": self.application.client_id}, + ) + + self.assertRedirects( + response=self.client.post(device_confirm_url, data={"action": "accept"}), + expected_url=device_grant_status_url, + ) + + # -------------------------------------------------------------------------------- + # 3: We redirect to the device grant status page (the user is still in their browser) + # -------------------------------------------------------------------------------- + self.assertContains( + response=self.client.get(device_grant_status_url), + text="Device Authorized", + count=1, + ) + + device = DeviceModel.objects.get(device_code="abc") + assert device.status == device.AUTHORIZED + + # ------------------------- + # 4: Device polls /token successfully + # ------------------------- + token_payload = { + "device_code": device.device_code, + "client_id": self.application.client_id, + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + token_response = self.client.post( + "/o/token/", + data=urlencode(token_payload), + content_type="application/x-www-form-urlencoded", + ) + # TokenView should always respond with application/json as it's meant to be + # consumed by devices. + assert token_response.__getitem__("content-type") == "application/json" + assert token_response.status_code == 200 + + token_data = token_response.json() + assert token_data == { + "access_token": mock.ANY, + "expires_in": 36000, + "token_type": "Bearer", + "scope": "read write", + "refresh_token": mock.ANY, + } + + # ensure the access token and refresh token have the same user as the device that just authenticated + access_token: oauth2_provider.models.AccessToken = AccessToken.objects.get( + token=token_data["access_token"] + ) + assert access_token.user == device.user + + refresh_token: oauth2_provider.models.RefreshToken = RefreshToken.objects.get( + token=token_data["refresh_token"] + ) + assert refresh_token.user == device.user + + def test_user_denies_access(self): + """ + This test asserts the when the user denies access, the state of the grant is saved + and the user is redirected to the page where they can see the "denied" state. + + The /token View returning the appropriate message for the "denied" state is covered + in test_token_view_returns_error_if_device_in_invalid_state. + """ + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(days=1), + status=DeviceModel.AUTHORIZATION_PENDING, + ) + device.save() + + device_confirm_url = reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "user_code", "client_id": "client_id"}, + ) + + device_grant_status_url = reverse( + "oauth2_provider:device-grant-status", + kwargs={"user_code": "user_code", "client_id": "client_id"}, + ) + + self.assertRedirects( + response=self.client.post(device_confirm_url, data={"action": "deny"}), + expected_url=device_grant_status_url, + ) + + device.refresh_from_db() + assert device.status == device.DENIED + + def test_device_confirm_view_returns_400_on_incorrect_action(self): + """ + This test asserts that the confirm view returns 400 if action is not + "accept" or "deny". + """ + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(days=1), + status=DeviceModel.AUTHORIZATION_PENDING, + ) + device.save() + + device_confirm_url = reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "user_code", "client_id": "client_id"}, + ) + response = self.client.post(device_confirm_url, data={"action": "inccorect_action"}) + + assert response.status_code == 400 + + def test_device_flow_authorization_device_invalid_state_returns_form_error(self): + """ + This test asserts that only devices in the expected state (authorization-pending) + can be approved/denied by the user. + """ + + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(days=1), + ) + device.save() + + # This simulates pytest.mark.parameterize, which unfortunately does not work with unittest + # and consequently with Django TestCase. + for invalid_state in ["authorized", "denied", "LOL_status"]: + # Set the device into an incorrect state. + device.status = invalid_state + device.save(update_fields=["status"]) + + self.assertContains( + response=self.client.post( + reverse("oauth2_provider:device"), + data={"user_code": "user_code"}, + ), + status_code=200, + text="User code has already been used", + count=1, + ) + + def test_device_flow_authorization_device_expired_returns_form_error(self): + """ + This test asserts that only devices in the expected state (authorization-pending) + can be approved/denied by the user. + """ + + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(seconds=-1), # <- essentially expired + ) + device.save() + + self.assertContains( + response=self.client.post( + reverse("oauth2_provider:device"), + data={"user_code": "user_code"}, + ), + status_code=200, + text="Expired user code", + count=1, + ) + + def test_token_view_returns_error_if_device_in_invalid_state(self): + """ + This test asserts that the token view returns the appropriate errors as specified + in https://datatracker.ietf.org/doc/html/rfc8628#section-3.5, in case the device + has not yet been approved by the user. + """ + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(seconds=60), + ) + device.save() + + token_payload = { + "device_code": "device_code", + "client_id": "client_id", + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + testcases = [ + ("authorization-pending", '{"error": "authorization_pending"}', 400), + ("expired", '{"error": "expired_token"}', 400), + ("denied", '{"error": "access_denied"}', 400), + ("LOL_status", '{"error": "internal_error"}', 500), + ] + for invalid_state, expected_error_message, expected_error_code in testcases: + device.status = invalid_state + device.save(update_fields=["status"]) + + response = self.client.post( + "/o/token/", + data=urlencode(token_payload), + content_type="application/x-www-form-urlencoded", + ) + self.assertContains( + response=response, + status_code=expected_error_code, + text=expected_error_message, + count=1, + ) + # TokenView should always respond with application/json as it's meant to be + # consumed by devices. + self.assertEqual(response.__getitem__("content-type"), "application/json") + + def test_token_view_returns_404_error_if_device_not_found(self): + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(seconds=60), + ) + device.save() + + token_payload = { + "device_code": "another_device_code", + "client_id": "client_id", + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + response = self.client.post( + "/o/token/", + data=urlencode(token_payload), + content_type="application/x-www-form-urlencoded", + ) + self.assertContains( + response=response, + status_code=404, + text="device_not_found", + count=1, + ) + # TokenView should always respond with application/json as it's meant to be + # consumed by devices. + self.assertEqual(response.__getitem__("content-type"), "application/json") + + def test_token_view_status_equals_what_oauthlib_token_response_method_returns(self): + """ + Tests the use case where oauthlib create_token_response returns a status different + than 200. + """ + + class MockOauthlibCoreClass: + def create_token_response(self, _): + return "url", {"headers_are_ignored": True}, '{"Key": "Value"}', 299 + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now() + timedelta(seconds=60), + status="authorized", + ) + device.save() + + token_payload = { + "device_code": "device_code", + "client_id": "client_id", + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + with mock.patch( + "oauth2_provider.views.mixins.OAuthLibMixin.get_oauthlib_core", MockOauthlibCoreClass + ): + response = self.client.post( + "/o/token/", + data=urlencode(token_payload), + content_type="application/x-www-form-urlencoded", + ) + + self.assertEqual(response["content-type"], "application/json") + self.assertContains( + response=response, + status_code=299, + text='{"Key": "Value"}', + count=1, + ) + assert not response.has_header("headers_are_ignored") + + @mock.patch( + "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token", + lambda: "abc", + ) + def test_device_polling_interval_can_be_changed(self): + """ + Tests the device polling rate(interval) can be changed to something other than the default + of 5 seconds. + """ + + self.oauth2_settings.OAUTH_DEVICE_VERIFICATION_URI = "example.com/device" + self.oauth2_settings.OAUTH_DEVICE_USER_CODE_GENERATOR = lambda: "xyz" + + self.oauth2_settings.DEVICE_FLOW_INTERVAL = 10 + + request_data: dict[str, str] = { + "client_id": self.application.client_id, + } + request_as_x_www_form_urlencoded: str = urlencode(request_data) + + response: django.http.response.JsonResponse = self.client.post( + reverse("oauth2_provider:device-authorization"), + data=request_as_x_www_form_urlencoded, + content_type="application/x-www-form-urlencoded", + ) + + assert response.status_code == 200 + + assert response.json() == { + "verification_uri": "example.com/device", + "expires_in": 1800, + "user_code": "xyz", + "device_code": "abc", + "interval": 10, + } + + def test_incorrect_client_id_sent(self): + """ + Ensure the correct error is returned when an invalid client is sent + """ + request_data: dict[str, str] = { + "client_id": "client_id_that_does_not_exist", + } + request_as_x_www_form_urlencoded: str = urlencode(request_data) + + response: django.http.response.JsonResponse = self.client.post( + reverse("oauth2_provider:device-authorization"), + data=request_as_x_www_form_urlencoded, + content_type="application/x-www-form-urlencoded", + ) + + assert response.status_code == 400 + + assert response.json() == { + "error": "invalid_request", + "error_description": "Invalid client_id parameter value.", + } + + def test_missing_client_id(self): + """ + Ensure the correct error is returned when the client id is missing. + """ + request_data: dict[str, str] = { + "not_client_id": "client_id_that_does_not_exist", + } + request_as_x_www_form_urlencoded: str = urlencode(request_data) + + response: django.http.response.JsonResponse = self.client.post( + reverse("oauth2_provider:device-authorization"), + data=request_as_x_www_form_urlencoded, + content_type="application/x-www-form-urlencoded", + ) + + assert response.status_code == 400 + + assert response.json() == { + "error": "invalid_request", + "error_description": "Missing client_id parameter.", + } + + def test_device_confirm_and_user_code_views_require_login(self): + URLs = [ + reverse("oauth2_provider:device-confirm", kwargs={"user_code": None, "client_id": "abc"}), + reverse("oauth2_provider:device-confirm", kwargs={"user_code": "abc", "client_id": "abc"}), + reverse("oauth2_provider:device"), + ] + + for url in URLs: + r = self.client.get(url) + assert r.status_code == 302 + assert r["Location"] == f"{settings.LOGIN_URL}?next={url}" + + r = self.client.post(url) + assert r.status_code == 302 + assert r["Location"] == f"{settings.LOGIN_URL}?next={url}" + + def test_device_confirm_view_GET_returns_404_when_device_does_not_exist(self): + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now(), + ) + device.save() + + self.assertContains( + response=self.client.get( + reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "not_user_code", "client_id": "not_client_id"}, + ) + ), + status_code=404, + text="The requested resource was not found on this server.", + ) + + # Asserts for valid user_code and client_id but invalid states + for invalid_state in ["authorized", "denied", "expired"]: + device.status = invalid_state + device.save(update_fields=["status"]) + + self.assertContains( + response=self.client.get( + reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "not_user_code", "client_id": "client_id"}, + ) + ), + status_code=404, + text="The requested resource was not found on this server.", + ) + + def test_device_confirm_view_POST_returns_404_when_device_does_not_exist(self): + UserModel.objects.create_user( + username="test_user_device_flow", + email="test_device@example.com", + password="password123", + ) + self.client.login(username="test_user_device_flow", password="password123") + + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now(), + ) + device.save() + + self.assertContains( + response=self.client.post( + reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "not_user_code", "client_id": "client_id"}, + ), + data={"action": "accept"}, + ), + status_code=404, + text="The requested resource was not found on this server.", + count=1, + ) + + # Asserts for valid user_code and client_id but invalid states + for invalid_state in ["authorized", "denied", "expired"]: + device.status = invalid_state + device.save(update_fields=["status"]) + + self.assertContains( + response=self.client.post( + reverse( + "oauth2_provider:device-confirm", + kwargs={"user_code": "user_code", "client_id": "client_id"}, + ), + data={"action": "accept"}, + ), + status_code=404, + text="The requested resource was not found on this server.", + count=1, + ) + + def test_device_is_expired_method_sets_status_to_expired_if_deadline_passed(self): + device = DeviceModel( + client_id="client_id", + device_code="device_code", + user_code="user_code", + scope="scope", + expires=datetime.now(tz=timezone.utc) + timedelta(seconds=-1), # <- essentially expired + ) + device.save() + + assert device.status == device.AUTHORIZATION_PENDING # default value + + # call is_expired() which should update the state + is_expired = device.is_expired() + + assert is_expired + assert device.status == device.EXPIRED + + # calling again is_expired() should return true and not change the state + is_expired = device.is_expired() + + assert is_expired + assert device.status == device.EXPIRED diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index 14c74506e..7e7e46de7 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -180,6 +180,12 @@ def test_authenticate_basic_auth_not_utf8(self): self.request.headers = {"HTTP_AUTHORIZATION": "Basic test"} self.assertFalse(self.validator._authenticate_basic_auth(self.request)) + def test_authenticate_basic_auth_public_app_with_device_code(self): + self.request.grant_type = "urn:ietf:params:oauth:grant-type:device_code" + self.request.headers = get_basic_auth_header("client_id", CLEARTEXT_SECRET) + self.application.client_type = Application.CLIENT_PUBLIC + self.assertTrue(self.validator._authenticate_basic_auth(self.request)) + def test_authenticate_check_secret(self): hashed = make_password(CLEARTEXT_SECRET) self.assertTrue(self.validator._check_secret(CLEARTEXT_SECRET, CLEARTEXT_SECRET)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 2c319b6ea..eef4b985c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,5 @@ +import pytest + from oauth2_provider import utils @@ -25,3 +27,24 @@ def test_jwk_from_pem_caches_jwk(): jwk3 = utils.jwk_from_pem(a_different_tiny_rsa_key) assert jwk3 is not jwk1 + + +def test_user_code_generator(): + # Default argument, 8 characters + user_code = utils.user_code_generator() + assert isinstance(user_code, str) + assert len(user_code) == 8 + + for character in user_code: + assert character >= "0" + assert character <= "V" + + another_user_code = utils.user_code_generator() + assert another_user_code != user_code + + shorter_user_code = utils.user_code_generator(user_code_length=1) + assert len(shorter_user_code) == 1 + + with pytest.raises(ValueError): + utils.user_code_generator(user_code_length=0) + utils.user_code_generator(user_code_length=-1) diff --git a/tox.ini b/tox.ini index 0a85f5fb8..29e93a2ae 100644 --- a/tox.ini +++ b/tox.ini @@ -46,7 +46,7 @@ deps = dj52: Django>=5.2,<6.0 djmain: https://github.com/django/django/archive/main.tar.gz djangorestframework - oauthlib>=3.2.2 + oauthlib>=3.3.0 jwcrypto coverage pytest @@ -79,7 +79,7 @@ commands = deps = Jinja2<3.1 sphinx<3 - oauthlib>=3.2.2 + oauthlib>=3.3.0 m2r>=0.2.1 mistune<2 sphinx-rtd-theme diff --git a/uv.lock b/uv.lock index 43764ff93..d5f28ba2c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.8, <3.14" +requires-python = ">=3.8, <=3.14" resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", @@ -260,6 +260,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, @@ -344,6 +366,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, { url = "https://files.pythonhosted.org/packages/0a/4e/3926a1c11f0433791985727965263f788af00db3482d89a7545ca5ecc921/charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", size = 198599, upload-time = "2025-10-14T04:41:53.213Z" }, { url = "https://files.pythonhosted.org/packages/ec/7c/b92d1d1dcffc34592e71ea19c882b6709e43d20fa498042dea8b815638d7/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", size = 143090, upload-time = "2025-10-14T04:41:54.385Z" }, { url = "https://files.pythonhosted.org/packages/84/ce/61a28d3bb77281eb24107b937a497f3c43089326d27832a63dcedaab0478/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", size = 139490, upload-time = "2025-10-14T04:41:55.551Z" }, @@ -547,6 +585,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, @@ -641,6 +705,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, ] @@ -676,6 +766,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, @@ -764,7 +869,7 @@ requires-dist = [ { name = "django", specifier = ">=4.2" }, { name = "jwcrypto", specifier = ">=1.5.0" }, { name = "m2r", marker = "extra == 'dev'" }, - { name = "oauthlib", specifier = ">=3.2.2" }, + { name = "oauthlib", specifier = ">=3.3.0" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-cov", marker = "extra == 'dev'" }, { name = "requests", specifier = ">=2.13.0" }, @@ -833,7 +938,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "django", specifier = ">=4.2,<=5.1" }, + { name = "django", specifier = ">=4.2,<=5.2" }, { name = "django-cors-headers", specifier = "==3.14.0" }, { name = "django-environ", specifier = "==0.11.2" }, { name = "django-oauth-toolkit", editable = "." }, @@ -1069,6 +1174,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, @@ -1634,6 +1761,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ]