diff --git a/.env b/.env index 447fd3c1b4..4a0e1ef2d4 100644 --- a/.env +++ b/.env @@ -6,6 +6,7 @@ PROWLER_UI_VERSION="stable" AUTH_URL=http://localhost:3000 API_BASE_URL=http://prowler-api:8080/api/v1 +NEXT_PUBLIC_API_BASE_URL=${API_BASE_URL} NEXT_PUBLIC_API_DOCS_URL=http://prowler-api:8080/api/v1/docs AUTH_TRUST_HOST=true UI_PORT=3000 @@ -130,7 +131,7 @@ SENTRY_ENVIRONMENT=local SENTRY_RELEASE=local #### Prowler release version #### -NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.6.0 +NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.7.5 # Social login credentials SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google" @@ -142,8 +143,7 @@ SOCIAL_GITHUB_OAUTH_CLIENT_ID="" SOCIAL_GITHUB_OAUTH_CLIENT_SECRET="" # Single Sign-On (SSO) -SAML_PUBLIC_CERT="" -SAML_PRIVATE_KEY="" +SAML_SSO_CALLBACK_URL="${AUTH_URL}/api/auth/callback/saml" # Lighthouse tracing LANGSMITH_TRACING=false diff --git a/.github/workflows/api-build-lint-push-containers.yml b/.github/workflows/api-build-lint-push-containers.yml index 2165eb7263..5fee1a2d8d 100644 --- a/.github/workflows/api-build-lint-push-containers.yml +++ b/.github/workflows/api-build-lint-push-containers.yml @@ -76,7 +76,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build and push container image (latest) # Comment the following line for testing diff --git a/.github/workflows/api-codeql.yml b/.github/workflows/api-codeql.yml index 620d9cbaed..2d38107960 100644 --- a/.github/workflows/api-codeql.yml +++ b/.github/workflows/api-codeql.yml @@ -48,12 +48,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/api-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index b64fbe6826..2929646088 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -211,7 +211,7 @@ jobs: files_ignore: ${{ env.IGNORE_FILES }} - name: Set up Docker Buildx if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build Container if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index 9892364e37..53e17b3e2d 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -11,7 +11,7 @@ jobs: with: fetch-depth: 0 - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@90694bf9af66e7536abc5824e7a87246dbf933cb # v3.88.35 + uses: trufflesecurity/trufflehog@6641d4ba5b684fffe195b9820345de1bf19f3181 # v3.89.2 with: path: ./ base: ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/sdk-build-lint-push-containers.yml b/.github/workflows/sdk-build-lint-push-containers.yml index 8182ef4c04..ec1d71a0cb 100644 --- a/.github/workflows/sdk-build-lint-push-containers.yml +++ b/.github/workflows/sdk-build-lint-push-containers.yml @@ -123,7 +123,7 @@ jobs: AWS_REGION: ${{ env.AWS_REGION }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build and push container image (latest) if: github.event_name == 'push' diff --git a/.github/workflows/sdk-codeql.yml b/.github/workflows/sdk-codeql.yml index 4420f5823d..10d5c93b45 100644 --- a/.github/workflows/sdk-codeql.yml +++ b/.github/workflows/sdk-codeql.yml @@ -56,12 +56,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/sdk-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ui-build-lint-push-containers.yml b/.github/workflows/ui-build-lint-push-containers.yml index 74cf23499f..bce6ba6a07 100644 --- a/.github/workflows/ui-build-lint-push-containers.yml +++ b/.github/workflows/ui-build-lint-push-containers.yml @@ -30,6 +30,7 @@ env: # Container Registries PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-ui + NEXT_PUBLIC_API_BASE_URL: http://prowler-api:8080/api/v1 jobs: repository-check: @@ -76,7 +77,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build and push container image (latest) # Comment the following line for testing @@ -86,6 +87,7 @@ jobs: context: ${{ env.WORKING_DIRECTORY }} build-args: | NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${{ env.SHORT_SHA }} + NEXT_PUBLIC_API_BASE_URL=${{ env.NEXT_PUBLIC_API_BASE_URL }} # Set push: false for testing push: true tags: | @@ -101,6 +103,7 @@ jobs: context: ${{ env.WORKING_DIRECTORY }} build-args: | NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${{ env.RELEASE_TAG }} + NEXT_PUBLIC_API_BASE_URL=${{ env.NEXT_PUBLIC_API_BASE_URL }} push: true tags: | ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }} diff --git a/.github/workflows/ui-codeql.yml b/.github/workflows/ui-codeql.yml index ef95253c5f..674bdb8310 100644 --- a/.github/workflows/ui-codeql.yml +++ b/.github/workflows/ui-codeql.yml @@ -48,12 +48,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/ui-codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ui-pull-request.yml b/.github/workflows/ui-pull-request.yml index 847c423780..e82b892f48 100644 --- a/.github/workflows/ui-pull-request.yml +++ b/.github/workflows/ui-pull-request.yml @@ -97,7 +97,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build Container uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: diff --git a/Dockerfile b/Dockerfile index 0e9fb89a03..f9aa97c58b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,8 @@ LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler" ARG POWERSHELL_VERSION=7.5.0 # hadolint ignore=DL3008 -RUN apt-get update && apt-get install -y --no-install-recommends wget libicu72 \ +RUN apt-get update && apt-get install -y --no-install-recommends \ + wget libicu72 libunwind8 libssl3 libcurl4 ca-certificates apt-transport-https gnupg \ && rm -rf /var/lib/apt/lists/* # Install PowerShell @@ -46,10 +47,6 @@ ENV PATH="${HOME}/.local/bin:${PATH}" RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir poetry -# By default poetry does not compile Python source files to bytecode during installation. -# This speeds up the installation process, but the first execution may take a little more -# time because Python then compiles source files to bytecode automatically. If you want to -# compile source files to bytecode during installation, you can use the --compile option RUN poetry install --compile && \ rm -rf ~/.cache/pip diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index abc9d64413..615691cad0 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,16 +2,20 @@ All notable changes to the **Prowler API** are documented in this file. -## [v1.9.0] (Prowler UNRELEASED) +## [v1.10.0] (Prowler UNRELEASED) + +--- + +## [v1.9.0] (Prowler v5.8.0) ### Added -- SSO with SAML support [(#7822)](https://github.com/prowler-cloud/prowler/pull/7822) - Support GCP Service Account key [(#7824)](https://github.com/prowler-cloud/prowler/pull/7824) - `GET /compliance-overviews` endpoints to retrieve compliance metadata and specific requirements statuses [(#7877)](https://github.com/prowler-cloud/prowler/pull/7877) - Lighthouse configuration support [(#7848)](https://github.com/prowler-cloud/prowler/pull/7848) ### Changed - Reworked `GET /compliance-overviews` to return proper requirement metrics [(#7877)](https://github.com/prowler-cloud/prowler/pull/7877) +- Optional `user` and `password` for M365 provider [(#7992)](https://github.com/prowler-cloud/prowler/pull/7992) ### Fixed - Scheduled scans are no longer deleted when their daily schedule run is disabled [(#8082)](https://github.com/prowler-cloud/prowler/pull/8082) diff --git a/api/poetry.lock b/api/poetry.lock index 452590b8c4..18622a248e 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "about-time" @@ -880,7 +880,6 @@ description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -950,6 +949,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {dev = "platform_python_implementation != \"PyPy\""} [package.dependencies] pycparser = "*" @@ -3854,26 +3854,26 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "6.30.2" +version = "6.31.1" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-6.30.2-cp310-abi3-win32.whl", hash = "sha256:b12ef7df7b9329886e66404bef5e9ce6a26b54069d7f7436a0853ccdeb91c103"}, - {file = "protobuf-6.30.2-cp310-abi3-win_amd64.whl", hash = "sha256:7653c99774f73fe6b9301b87da52af0e69783a2e371e8b599b3e9cb4da4b12b9"}, - {file = "protobuf-6.30.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:0eb523c550a66a09a0c20f86dd554afbf4d32b02af34ae53d93268c1f73bc65b"}, - {file = "protobuf-6.30.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:50f32cc9fd9cb09c783ebc275611b4f19dfdfb68d1ee55d2f0c7fa040df96815"}, - {file = "protobuf-6.30.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4f6c687ae8efae6cf6093389a596548214467778146b7245e886f35e1485315d"}, - {file = "protobuf-6.30.2-cp39-cp39-win32.whl", hash = "sha256:524afedc03b31b15586ca7f64d877a98b184f007180ce25183d1a5cb230ee72b"}, - {file = "protobuf-6.30.2-cp39-cp39-win_amd64.whl", hash = "sha256:acec579c39c88bd8fbbacab1b8052c793efe83a0a5bd99db4a31423a25c0a0e2"}, - {file = "protobuf-6.30.2-py3-none-any.whl", hash = "sha256:ae86b030e69a98e08c77beab574cbcb9fff6d031d57209f574a5aea1445f4b51"}, - {file = "protobuf-6.30.2.tar.gz", hash = "sha256:35c859ae076d8c56054c25b59e5e59638d86545ed6e2b6efac6be0b6ea3ba048"}, + {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, + {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, + {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, + {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, + {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, + {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, + {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, ] [[package]] name = "prowler" -version = "5.6.0" +version = "5.8.0" description = "Prowler is an Open Source security tool to perform AWS, GCP and Azure security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness. It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks." optional = false python-versions = ">3.9.1,<3.13" @@ -3921,6 +3921,7 @@ numpy = "2.0.2" pandas = "2.2.3" py-ocsf-models = "0.3.1" pydantic = "1.10.21" +pygithub = "2.5.0" python-dateutil = ">=2.9.0.post0,<3.0.0" pytz = "2025.1" schema = "0.7.7" @@ -3933,7 +3934,7 @@ tzlocal = "5.3.1" type = "git" url = "https://github.com/prowler-cloud/prowler.git" reference = "master" -resolved_reference = "9828824b737b8deda61f4a6646b54e0ad45033b9" +resolved_reference = "ea97de7f43a2063476b49f7697bb6c7b51137c11" [[package]] name = "psutil" @@ -4110,11 +4111,11 @@ description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {dev = "platform_python_implementation != \"PyPy\""} [[package]] name = "pycurl" @@ -4225,6 +4226,26 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pygithub" +version = "2.5.0" +description = "Use the full Github API v3" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyGithub-2.5.0-py3-none-any.whl", hash = "sha256:b0b635999a658ab8e08720bdd3318893ff20e2275f6446fcf35bf3f44f2c0fd2"}, + {file = "pygithub-2.5.0.tar.gz", hash = "sha256:e1613ac508a9be710920d26eb18b1905ebd9926aa49398e88151c1b526aad3cf"}, +] + +[package.dependencies] +Deprecated = "*" +pyjwt = {version = ">=2.4.0", extras = ["crypto"]} +pynacl = ">=1.4.0" +requests = ">=2.14.0" +typing-extensions = ">=4.0.0" +urllib3 = ">=1.26.0" + [[package]] name = "pygments" version = "2.19.1" @@ -4289,6 +4310,33 @@ tomlkit = ">=0.10.1" spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + [[package]] name = "pyparsing" version = "3.2.3" diff --git a/api/src/backend/api/adapters.py b/api/src/backend/api/adapters.py index 15f690ca54..b8380c9a48 100644 --- a/api/src/backend/api/adapters.py +++ b/api/src/backend/api/adapters.py @@ -3,14 +3,7 @@ from django.db import transaction from api.db_router import MainRouter from api.db_utils import rls_transaction -from api.models import ( - Membership, - Role, - SAMLConfiguration, - Tenant, - User, - UserRoleRelationship, -) +from api.models import Membership, Role, Tenant, User, UserRoleRelationship class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter): @@ -24,8 +17,8 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter): def pre_social_login(self, request, sociallogin): # Link existing accounts with the same email address email = sociallogin.account.extra_data.get("email") - if sociallogin.account.provider == "saml": - email = sociallogin.user.email + # if sociallogin.account.provider == "saml": + # email = sociallogin.user.email if email: existing_user = self.get_user_by_email(email) if existing_user: @@ -38,100 +31,98 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter): """ with transaction.atomic(using=MainRouter.admin_db): user = super().save_user(request, sociallogin, form) - provider = sociallogin.account.provider extra = sociallogin.account.extra_data - if provider == "saml": - # Handle SAML-specific logic - user.first_name = ( - extra.get("firstName", [""])[0] if extra.get("firstName") else "" - ) - user.last_name = ( - extra.get("lastName", [""])[0] if extra.get("lastName") else "" - ) - user.company_name = ( - extra.get("organization", [""])[0] - if extra.get("organization") - else "" - ) - user.name = f"{user.first_name} {user.last_name}".strip() - if user.name == "": - user.name = "N/A" + # if provider == "saml": + # # Handle SAML-specific logic + # user.first_name = ( + # extra.get("firstName", [""])[0] if extra.get("firstName") else "" + # ) + # user.last_name = ( + # extra.get("lastName", [""])[0] if extra.get("lastName") else "" + # ) + # user.company_name = ( + # extra.get("organization", [""])[0] + # if extra.get("organization") + # else "" + # ) + # user.name = f"{user.first_name} {user.last_name}".strip() + # if user.name == "": + # user.name = "N/A" + # user.save(using=MainRouter.admin_db) + + # email_domain = user.email.split("@")[-1] + # tenant = ( + # SAMLConfiguration.objects.using(MainRouter.admin_db) + # .get(email_domain=email_domain) + # .tenant + # ) + + # with rls_transaction(str(tenant.id)): + # role_name = ( + # extra.get("userType", ["saml_default_role"])[0].strip() + # if extra.get("userType") + # else "saml_default_role" + # ) + + # try: + # role = Role.objects.using(MainRouter.admin_db).get( + # name=role_name, tenant_id=tenant.id + # ) + # except Role.DoesNotExist: + # role = Role.objects.using(MainRouter.admin_db).create( + # name=role_name, + # tenant_id=tenant.id, + # manage_users=False, + # manage_account=False, + # manage_billing=False, + # manage_providers=False, + # manage_integrations=False, + # manage_scans=False, + # unlimited_visibility=False, + # ) + + # Membership.objects.using(MainRouter.admin_db).create( + # user=user, + # tenant=tenant, + # role=Membership.RoleChoices.MEMBER, + # ) + + # UserRoleRelationship.objects.using(MainRouter.admin_db).create( + # user=user, + # role=role, + # tenant_id=tenant.id, + # ) + + # Handle other providers (e.g., GitHub, Google) + user.save(using=MainRouter.admin_db) + social_account_name = extra.get("name") + if social_account_name: + user.name = social_account_name user.save(using=MainRouter.admin_db) - email_domain = user.email.split("@")[-1] - tenant = ( - SAMLConfiguration.objects.using(MainRouter.admin_db) - .get(email_domain=email_domain) - .tenant + tenant = Tenant.objects.using(MainRouter.admin_db).create( + name=f"{user.email.split('@')[0]} default tenant" + ) + with rls_transaction(str(tenant.id)): + Membership.objects.using(MainRouter.admin_db).create( + user=user, tenant=tenant, role=Membership.RoleChoices.OWNER ) - - with rls_transaction(str(tenant.id)): - role_name = ( - extra.get("userType", ["saml_default_role"])[0].strip() - if extra.get("userType") - else "saml_default_role" - ) - - try: - role = Role.objects.using(MainRouter.admin_db).get( - name=role_name, tenant_id=tenant.id - ) - except Role.DoesNotExist: - role = Role.objects.using(MainRouter.admin_db).create( - name=role_name, - tenant_id=tenant.id, - manage_users=False, - manage_account=False, - manage_billing=False, - manage_providers=False, - manage_integrations=False, - manage_scans=False, - unlimited_visibility=False, - ) - - Membership.objects.using(MainRouter.admin_db).create( - user=user, - tenant=tenant, - role=Membership.RoleChoices.MEMBER, - ) - - UserRoleRelationship.objects.using(MainRouter.admin_db).create( - user=user, - role=role, - tenant_id=tenant.id, - ) - - else: - # Handle other providers (e.g., GitHub, Google) - user.save(using=MainRouter.admin_db) - social_account_name = extra.get("name") - if social_account_name: - user.name = social_account_name - user.save(using=MainRouter.admin_db) - - tenant = Tenant.objects.using(MainRouter.admin_db).create( - name=f"{user.email.split('@')[0]} default tenant" + role = Role.objects.using(MainRouter.admin_db).create( + name="admin", + tenant_id=tenant.id, + manage_users=True, + manage_account=True, + manage_billing=True, + manage_providers=True, + manage_integrations=True, + manage_scans=True, + unlimited_visibility=True, + ) + UserRoleRelationship.objects.using(MainRouter.admin_db).create( + user=user, + role=role, + tenant_id=tenant.id, ) - with rls_transaction(str(tenant.id)): - Membership.objects.using(MainRouter.admin_db).create( - user=user, tenant=tenant, role=Membership.RoleChoices.OWNER - ) - role = Role.objects.using(MainRouter.admin_db).create( - name="admin", - tenant_id=tenant.id, - manage_users=True, - manage_account=True, - manage_billing=True, - manage_providers=True, - manage_integrations=True, - manage_scans=True, - unlimited_visibility=True, - ) - UserRoleRelationship.objects.using(MainRouter.admin_db).create( - user=user, - role=role, - tenant_id=tenant.id, - ) return user diff --git a/api/src/backend/api/migrations/0031_lighthouseconfiguration.py b/api/src/backend/api/migrations/0030_lighthouseconfiguration.py similarity index 98% rename from api/src/backend/api/migrations/0031_lighthouseconfiguration.py rename to api/src/backend/api/migrations/0030_lighthouseconfiguration.py index 9393bbf32f..b12b8cac01 100644 --- a/api/src/backend/api/migrations/0031_lighthouseconfiguration.py +++ b/api/src/backend/api/migrations/0030_lighthouseconfiguration.py @@ -11,7 +11,7 @@ import api.rls class Migration(migrations.Migration): dependencies = [ - ("api", "0030_samlconfigurations"), + ("api", "0029_findings_check_index_parent"), ] operations = [ diff --git a/api/src/backend/api/migrations/0030_samlconfigurations.py b/api/src/backend/api/migrations/0030_samlconfigurations.py deleted file mode 100644 index b5ec1fea1a..0000000000 --- a/api/src/backend/api/migrations/0030_samlconfigurations.py +++ /dev/null @@ -1,120 +0,0 @@ -# Generated by Django 5.1.8 on 2025-05-15 09:54 - -import uuid - -import django.db.models.deletion -from django.db import migrations, models - -import api.rls - - -class Migration(migrations.Migration): - dependencies = [ - ("api", "0029_findings_check_index_parent"), - ] - - operations = [ - migrations.CreateModel( - name="SAMLDomainIndex", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("email_domain", models.CharField(max_length=254, unique=True)), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="api.tenant" - ), - ), - ], - options={ - "db_table": "saml_domain_index", - }, - ), - migrations.AddConstraint( - model_name="samldomainindex", - constraint=models.UniqueConstraint( - fields=("email_domain", "tenant"), - name="unique_resources_by_email_domain", - ), - ), - migrations.AddConstraint( - model_name="samldomainindex", - constraint=api.rls.BaseSecurityConstraint( - name="statements_on_samldomainindex", - statements=["SELECT", "INSERT", "UPDATE", "DELETE"], - ), - ), - migrations.CreateModel( - name="SAMLConfiguration", - fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "email_domain", - models.CharField( - help_text="Email domain used to identify the tenant, e.g. prowlerdemo.com", - max_length=254, - unique=True, - ), - ), - ( - "metadata_xml", - models.TextField( - help_text="Raw IdP metadata XML to configure SingleSignOnService, certificates, etc." - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="api.tenant" - ), - ), - ], - options={ - "db_table": "saml_configurations", - }, - ), - migrations.AddConstraint( - model_name="samlconfiguration", - constraint=api.rls.RowLevelSecurityConstraint( - "tenant_id", - name="rls_on_samlconfiguration", - statements=["SELECT", "INSERT", "UPDATE", "DELETE"], - ), - ), - migrations.AddConstraint( - model_name="samlconfiguration", - constraint=models.UniqueConstraint( - fields=("tenant",), name="unique_samlconfig_per_tenant" - ), - ), - migrations.AlterField( - model_name="integration", - name="integration_type", - field=api.db_utils.IntegrationTypeEnumField( - choices=[ - ("amazon_s3", "Amazon S3"), - ("aws_security_hub", "AWS Security Hub"), - ("jira", "JIRA"), - ("slack", "Slack"), - ] - ), - ), - ] diff --git a/api/src/backend/api/migrations/0032_scan_disable_on_cascade_periodic_tasks.py b/api/src/backend/api/migrations/0031_scan_disable_on_cascade_periodic_tasks.py similarity index 92% rename from api/src/backend/api/migrations/0032_scan_disable_on_cascade_periodic_tasks.py rename to api/src/backend/api/migrations/0031_scan_disable_on_cascade_periodic_tasks.py index 07f3523ef2..c7e990ed13 100644 --- a/api/src/backend/api/migrations/0032_scan_disable_on_cascade_periodic_tasks.py +++ b/api/src/backend/api/migrations/0031_scan_disable_on_cascade_periodic_tasks.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("api", "0031_lighthouseconfiguration"), + ("api", "0030_lighthouseconfiguration"), ("django_celery_beat", "0019_alter_periodictasks_options"), ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 3f8e1264c9..b321c5fe96 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -1,20 +1,15 @@ import json import logging import re -import xml.etree.ElementTree as ET from uuid import UUID, uuid4 -from allauth.socialaccount.models import SocialApp from config.custom_logging import BackendLogger -from config.settings.social_login import SOCIALACCOUNT_PROVIDERS from cryptography.fernet import Fernet, InvalidToken from django.conf import settings from django.contrib.auth.models import AbstractBaseUser from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVector, SearchVectorField -from django.contrib.sites.models import Site -from django.core.exceptions import ValidationError from django.core.validators import MinLengthValidator from django.db import models from django.db.models import Q @@ -26,7 +21,6 @@ from psqlextra.models import PostgresPartitionedModel from psqlextra.types import PostgresPartitioningMethod from uuid6 import uuid7 -from api.db_router import MainRouter from api.db_utils import ( CustomUserManager, FindingDeltaEnumField, @@ -1370,219 +1364,242 @@ class IntegrationProviderRelationship(RowLevelSecurityProtectedModel): ] -class SAMLDomainIndex(models.Model): - """ - Public index of SAML domains. No RLS. Used for fast lookup in SAML login flow. - """ +# class SAMLToken(models.Model): +# id = models.UUIDField(primary_key=True, default=uuid4, editable=False) +# inserted_at = models.DateTimeField(auto_now_add=True, editable=False) +# updated_at = models.DateTimeField(auto_now=True, editable=False) +# expires_at = models.DateTimeField(editable=False) +# token = models.JSONField(unique=True) +# user = models.ForeignKey(User, on_delete=models.CASCADE) - email_domain = models.CharField(max_length=254, unique=True) - tenant = models.ForeignKey("Tenant", on_delete=models.CASCADE) +# class Meta: +# db_table = "saml_tokens" - class Meta: - db_table = "saml_domain_index" +# def save(self, *args, **kwargs): +# if not self.expires_at: +# self.expires_at = datetime.now(timezone.utc) + timedelta(seconds=15) +# super().save(*args, **kwargs) - constraints = [ - models.UniqueConstraint( - fields=("email_domain", "tenant"), - name="unique_resources_by_email_domain", - ), - BaseSecurityConstraint( - name="statements_on_%(class)s", - statements=["SELECT", "INSERT", "UPDATE", "DELETE"], - ), - ] +# def is_expired(self) -> bool: +# return datetime.now(timezone.utc) >= self.expires_at -class SAMLConfiguration(RowLevelSecurityProtectedModel): - """ - Stores per-tenant SAML settings, including email domain and IdP metadata. - Automatically syncs to a SocialApp instance on save. +# class SAMLDomainIndex(models.Model): +# """ +# Public index of SAML domains. No RLS. Used for fast lookup in SAML login flow. +# """ - Note: - This model exists to provide a tenant-aware abstraction over SAML configuration. - It supports row-level security, custom validation, and metadata parsing, enabling - Prowler to expose a clean API and admin interface for managing SAML integrations. +# email_domain = models.CharField(max_length=254, unique=True) +# tenant = models.ForeignKey("Tenant", on_delete=models.CASCADE) - Although Django Allauth uses the SocialApp model to store provider configuration, - it is not designed for multi-tenant use. SocialApp lacks support for tenant scoping, - email domain mapping, and structured metadata handling. +# class Meta: +# db_table = "saml_domain_index" - By managing SAMLConfiguration separately, we ensure: - - Strong isolation between tenants via RLS. - - Ownership of raw IdP metadata and its validation. - - An explicit link between SAML config and business-level identifiers (e.g. email domain). - - Programmatic transformation into the SocialApp format used by Allauth. +# constraints = [ +# models.UniqueConstraint( +# fields=("email_domain", "tenant"), +# name="unique_resources_by_email_domain", +# ), +# BaseSecurityConstraint( +# name="statements_on_%(class)s", +# statements=["SELECT", "INSERT", "UPDATE", "DELETE"], +# ), +# ] - In short, this model acts as a secure and user-friendly layer over Allauth's lower-level primitives. - """ - id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - email_domain = models.CharField( - max_length=254, - unique=True, - help_text="Email domain used to identify the tenant, e.g. prowlerdemo.com", - ) - metadata_xml = models.TextField( - help_text="Raw IdP metadata XML to configure SingleSignOnService, certificates, etc." - ) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) +# class SAMLConfiguration(RowLevelSecurityProtectedModel): +# """ +# Stores per-tenant SAML settings, including email domain and IdP metadata. +# Automatically syncs to a SocialApp instance on save. - class JSONAPIMeta: - resource_name = "saml-configurations" +# Note: +# This model exists to provide a tenant-aware abstraction over SAML configuration. +# It supports row-level security, custom validation, and metadata parsing, enabling +# Prowler to expose a clean API and admin interface for managing SAML integrations. - class Meta: - db_table = "saml_configurations" +# Although Django Allauth uses the SocialApp model to store provider configuration, +# it is not designed for multi-tenant use. SocialApp lacks support for tenant scoping, +# email domain mapping, and structured metadata handling. - constraints = [ - RowLevelSecurityConstraint( - field="tenant_id", - name="rls_on_%(class)s", - statements=["SELECT", "INSERT", "UPDATE", "DELETE"], - ), - # 1 config per tenant - models.UniqueConstraint( - fields=["tenant"], - name="unique_samlconfig_per_tenant", - ), - ] +# By managing SAMLConfiguration separately, we ensure: +# - Strong isolation between tenants via RLS. +# - Ownership of raw IdP metadata and its validation. +# - An explicit link between SAML config and business-level identifiers (e.g. email domain). +# - Programmatic transformation into the SocialApp format used by Allauth. - def clean(self, old_email_domain=None): - # Domain must not contain @ - if "@" in self.email_domain: - raise ValidationError({"email_domain": "Domain must not contain @"}) +# In short, this model acts as a secure and user-friendly layer over Allauth's lower-level primitives. +# """ - # Enforce at most one config per tenant - qs = SAMLConfiguration.objects.filter(tenant=self.tenant) - # Exclude ourselves in case of update - if self.pk: - qs = qs.exclude(pk=self.pk) - if qs.exists(): - raise ValidationError( - {"tenant": "A SAML configuration already exists for this tenant."} - ) +# id = models.UUIDField(primary_key=True, default=uuid4, editable=False) +# email_domain = models.CharField( +# max_length=254, +# unique=True, +# help_text="Email domain used to identify the tenant, e.g. prowlerdemo.com", +# ) +# metadata_xml = models.TextField( +# help_text="Raw IdP metadata XML to configure SingleSignOnService, certificates, etc." +# ) +# created_at = models.DateTimeField(auto_now_add=True) +# updated_at = models.DateTimeField(auto_now=True) - # The email domain must be unique in the entire system - qs = SAMLConfiguration.objects.using(MainRouter.admin_db).filter( - email_domain__iexact=self.email_domain - ) - if qs.exists() and old_email_domain != self.email_domain: - raise ValidationError( - {"tenant": "There is a problem with your email domain."} - ) +# class JSONAPIMeta: +# resource_name = "saml-configurations" - def save(self, *args, **kwargs): - self.email_domain = self.email_domain.strip().lower() - is_create = not SAMLConfiguration.objects.filter(pk=self.pk).exists() +# class Meta: +# db_table = "saml_configurations" - if not is_create: - old = SAMLConfiguration.objects.get(pk=self.pk) - old_email_domain = old.email_domain - old_metadata_xml = old.metadata_xml - else: - old_email_domain = None - old_metadata_xml = None +# constraints = [ +# RowLevelSecurityConstraint( +# field="tenant_id", +# name="rls_on_%(class)s", +# statements=["SELECT", "INSERT", "UPDATE", "DELETE"], +# ), +# # 1 config per tenant +# models.UniqueConstraint( +# fields=["tenant"], +# name="unique_samlconfig_per_tenant", +# ), +# ] - self.clean(old_email_domain) - super().save(*args, **kwargs) +# def clean(self, old_email_domain=None): +# # Domain must not contain @ +# if "@" in self.email_domain: +# raise ValidationError({"email_domain": "Domain must not contain @"}) - if is_create or ( - old_email_domain != self.email_domain - or old_metadata_xml != self.metadata_xml - ): - self._sync_social_app(old_email_domain) +# # Enforce at most one config per tenant +# qs = SAMLConfiguration.objects.filter(tenant=self.tenant) +# # Exclude ourselves in case of update +# if self.pk: +# qs = qs.exclude(pk=self.pk) +# if qs.exists(): +# raise ValidationError( +# {"tenant": "A SAML configuration already exists for this tenant."} +# ) - # Sync the public index - if not is_create and old_email_domain and old_email_domain != self.email_domain: - SAMLDomainIndex.objects.filter(email_domain=old_email_domain).delete() +# # The email domain must be unique in the entire system +# qs = SAMLConfiguration.objects.using(MainRouter.admin_db).filter( +# email_domain__iexact=self.email_domain +# ) +# if qs.exists() and old_email_domain != self.email_domain: +# raise ValidationError( +# {"tenant": "There is a problem with your email domain."} +# ) - # Create/update the new domain index - SAMLDomainIndex.objects.update_or_create( - email_domain=self.email_domain, defaults={"tenant": self.tenant} - ) +# def save(self, *args, **kwargs): +# self.email_domain = self.email_domain.strip().lower() +# is_create = not SAMLConfiguration.objects.filter(pk=self.pk).exists() - def _parse_metadata(self): - """ - Parse the raw IdP metadata XML and extract: - - entity_id - - sso_url - - slo_url (may be None) - - x509cert (required) - """ - ns = { - "md": "urn:oasis:names:tc:SAML:2.0:metadata", - "ds": "http://www.w3.org/2000/09/xmldsig#", - } - try: - root = ET.fromstring(self.metadata_xml) - except ET.ParseError as e: - raise ValidationError({"metadata_xml": f"Invalid XML: {e}"}) +# if not is_create: +# old = SAMLConfiguration.objects.get(pk=self.pk) +# old_email_domain = old.email_domain +# old_metadata_xml = old.metadata_xml +# else: +# old_email_domain = None +# old_metadata_xml = None - # Entity ID - entity_id = root.attrib.get("entityID") +# self.clean(old_email_domain) +# super().save(*args, **kwargs) - # SSO endpoint (must exist) - sso = root.find(".//md:IDPSSODescriptor/md:SingleSignOnService", ns) - if sso is None or "Location" not in sso.attrib: - raise ValidationError( - {"metadata_xml": "Missing SingleSignOnService in metadata."} - ) - sso_url = sso.attrib["Location"] +# if is_create or ( +# old_email_domain != self.email_domain +# or old_metadata_xml != self.metadata_xml +# ): +# self._sync_social_app(old_email_domain) - # SLO endpoint (optional) - slo = root.find(".//md:IDPSSODescriptor/md:SingleLogoutService", ns) - slo_url = slo.attrib.get("Location") if slo is not None else None +# # Sync the public index +# if not is_create and old_email_domain and old_email_domain != self.email_domain: +# SAMLDomainIndex.objects.filter(email_domain=old_email_domain).delete() - # X.509 certificate (required) - cert = root.find( - './/md:KeyDescriptor[@use="signing"]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', - ns, - ) - if cert is None or not cert.text or not cert.text.strip(): - raise ValidationError( - { - "metadata_xml": 'Metadata must include a under .' - } - ) - x509cert = cert.text.strip() +# # Create/update the new domain index +# SAMLDomainIndex.objects.update_or_create( +# email_domain=self.email_domain, defaults={"tenant": self.tenant} +# ) - return { - "entity_id": entity_id, - "sso_url": sso_url, - "slo_url": slo_url, - "x509cert": x509cert, - } +# def _parse_metadata(self): +# """ +# Parse the raw IdP metadata XML and extract: +# - entity_id +# - sso_url +# - slo_url (may be None) +# - x509cert (required) +# """ +# ns = { +# "md": "urn:oasis:names:tc:SAML:2.0:metadata", +# "ds": "http://www.w3.org/2000/09/xmldsig#", +# } +# try: +# root = ET.fromstring(self.metadata_xml) +# except ET.ParseError as e: +# raise ValidationError({"metadata_xml": f"Invalid XML: {e}"}) - def _sync_social_app(self, previous_email_domain=None): - """ - Create or update the corresponding SocialApp based on email_domain. - If the domain changed, update the matching SocialApp. - """ - idp_settings = self._parse_metadata() - settings_dict = SOCIALACCOUNT_PROVIDERS["saml"].copy() - settings_dict["idp"] = idp_settings +# # Entity ID +# entity_id = root.attrib.get("entityID") - current_site = Site.objects.get(id=settings.SITE_ID) +# # SSO endpoint (must exist) +# sso = root.find(".//md:IDPSSODescriptor/md:SingleSignOnService", ns) +# if sso is None or "Location" not in sso.attrib: +# raise ValidationError( +# {"metadata_xml": "Missing SingleSignOnService in metadata."} +# ) +# sso_url = sso.attrib["Location"] - social_app_qs = SocialApp.objects.filter( - provider="saml", client_id=previous_email_domain or self.email_domain - ) +# # SLO endpoint (optional) +# slo = root.find(".//md:IDPSSODescriptor/md:SingleLogoutService", ns) +# slo_url = slo.attrib.get("Location") if slo is not None else None - if social_app_qs.exists(): - social_app = social_app_qs.first() - social_app.client_id = self.email_domain - social_app.name = f"{self.tenant.name} SAML ({self.email_domain})" - social_app.settings = settings_dict - social_app.save() - social_app.sites.set([current_site]) - else: - social_app = SocialApp.objects.create( - provider="saml", - client_id=self.email_domain, - name=f"{self.tenant.name} SAML ({self.email_domain})", - settings=settings_dict, - ) - social_app.sites.set([current_site]) +# # X.509 certificate (required) +# cert = root.find( +# './/md:KeyDescriptor[@use="signing"]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', +# ns, +# ) +# if cert is None or not cert.text or not cert.text.strip(): +# raise ValidationError( +# { +# "metadata_xml": 'Metadata must include a under .' +# } +# ) +# x509cert = cert.text.strip() + +# return { +# "entity_id": entity_id, +# "sso_url": sso_url, +# "slo_url": slo_url, +# "x509cert": x509cert, +# } + +# def _sync_social_app(self, previous_email_domain=None): +# """ +# Create or update the corresponding SocialApp based on email_domain. +# If the domain changed, update the matching SocialApp. +# """ +# idp_settings = self._parse_metadata() +# settings_dict = SOCIALACCOUNT_PROVIDERS["saml"].copy() +# settings_dict["idp"] = idp_settings + +# current_site = Site.objects.get(id=settings.SITE_ID) + +# social_app_qs = SocialApp.objects.filter( +# provider="saml", client_id=previous_email_domain or self.email_domain +# ) + +# client_id = self.email_domain[:191] +# name = f"SAML-{self.email_domain}"[:40] + +# if social_app_qs.exists(): +# social_app = social_app_qs.first() +# social_app.client_id = client_id +# social_app.name = name +# social_app.settings = settings_dict +# social_app.save() +# social_app.sites.set([current_site]) +# else: +# social_app = SocialApp.objects.create( +# provider="saml", +# client_id=client_id, +# name=name, +# settings=settings_dict, +# ) +# social_app.sites.set([current_site]) class ResourceScanSummary(RowLevelSecurityProtectedModel): diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 9a56a91f65..c17b6e996e 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -5152,199 +5152,6 @@ paths: responses: '204': description: Relationship deleted successfully - /api/v1/saml-config: - get: - operationId: saml_config_list - description: Returns all the SAML-based SSO configurations associated with the - current tenant. - summary: List all SSO configurations - parameters: - - in: query - name: fields[saml-configurations] - schema: - type: array - items: - type: string - enum: - - email_domain - - metadata_xml - - created_at - - updated_at - description: endpoint return only specific fields in the response on a per-type - basis by including a fields[TYPE] query parameter. - explode: false - - name: filter[search] - required: false - in: query - description: A search term. - schema: - type: string - - name: page[number] - required: false - in: query - description: A page number within the paginated result set. - schema: - type: integer - - name: page[size] - required: false - in: query - description: Number of results to return per page. - schema: - type: integer - - name: sort - required: false - in: query - description: '[list of fields to sort by](https://jsonapi.org/format/#fetching-sorting)' - schema: - type: array - items: - type: string - enum: - - id - - -id - - email_domain - - -email_domain - - metadata_xml - - -metadata_xml - - created_at - - -created_at - - updated_at - - -updated_at - explode: false - tags: - - SAML - security: - - jwtAuth: [] - responses: - '200': - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/PaginatedSAMLConfigurationList' - description: '' - post: - operationId: saml_config_create - description: Creates a new SAML SSO configuration for the current tenant, including - email domain and metadata XML. - summary: Create the SSO configuration - tags: - - SAML - requestBody: - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/SAMLConfigurationRequest' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/SAMLConfigurationRequest' - multipart/form-data: - schema: - $ref: '#/components/schemas/SAMLConfigurationRequest' - required: true - security: - - jwtAuth: [] - responses: - '201': - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/SAMLConfigurationResponse' - description: '' - /api/v1/saml-config/{id}: - get: - operationId: saml_config_retrieve - description: Returns the details of a specific SAML configuration belonging - to the current tenant. - summary: Retrieve SSO configuration details - parameters: - - in: query - name: fields[saml-configurations] - schema: - type: array - items: - type: string - enum: - - email_domain - - metadata_xml - - created_at - - updated_at - description: endpoint return only specific fields in the response on a per-type - basis by including a fields[TYPE] query parameter. - explode: false - - in: path - name: id - schema: - type: string - format: uuid - description: A UUID string identifying this saml configuration. - required: true - tags: - - SAML - security: - - jwtAuth: [] - responses: - '200': - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/SAMLConfigurationResponse' - description: '' - patch: - operationId: saml_config_partial_update - description: Partially updates an existing SAML SSO configuration. Supports - changes to email domain and metadata XML. - summary: Update the SSO configuration - parameters: - - in: path - name: id - schema: - type: string - format: uuid - description: A UUID string identifying this saml configuration. - required: true - tags: - - SAML - requestBody: - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/PatchedSAMLConfigurationRequest' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/PatchedSAMLConfigurationRequest' - multipart/form-data: - schema: - $ref: '#/components/schemas/PatchedSAMLConfigurationRequest' - required: true - security: - - jwtAuth: [] - responses: - '200': - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/SAMLConfigurationResponse' - description: '' - delete: - operationId: saml_config_destroy - description: Deletes an existing SAML SSO configuration associated with the - current tenant. - summary: Delete the SSO configuration - parameters: - - in: path - name: id - schema: - type: string - format: uuid - description: A UUID string identifying this saml configuration. - required: true - tags: - - SAML - security: - - jwtAuth: [] - responses: - '204': - description: No response body /api/v1/scans: get: operationId: scans_list @@ -9351,15 +9158,6 @@ components: $ref: '#/components/schemas/Role' required: - data - PaginatedSAMLConfigurationList: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/SAMLConfiguration' - required: - - data PaginatedScanList: type: object properties: @@ -10244,52 +10042,6 @@ components: readOnly: true required: - data - PatchedSAMLConfigurationRequest: - type: object - properties: - data: - type: object - required: - - type - - id - additionalProperties: false - properties: - type: - type: string - description: The [type](https://jsonapi.org/format/#document-resource-object-identification) - member is used to describe resource objects that share common attributes - and relationships. - enum: - - saml-configurations - id: - type: string - format: uuid - attributes: - type: object - properties: - email_domain: - type: string - minLength: 1 - description: Email domain used to identify the tenant, e.g. prowlerdemo.com - maxLength: 254 - metadata_xml: - type: string - minLength: 1 - description: Raw IdP metadata XML to configure SingleSignOnService, - certificates, etc. - created_at: - type: string - format: date-time - readOnly: true - updated_at: - type: string - format: date-time - readOnly: true - required: - - email_domain - - metadata_xml - required: - - data PatchedScanUpdateRequest: type: object properties: @@ -12403,97 +12155,6 @@ components: $ref: '#/components/schemas/Role' required: - data - SAMLConfiguration: - type: object - required: - - type - - id - additionalProperties: false - properties: - type: - allOf: - - $ref: '#/components/schemas/SAMLConfigurationTypeEnum' - description: The [type](https://jsonapi.org/format/#document-resource-object-identification) - member is used to describe resource objects that share common attributes - and relationships. - id: - type: string - format: uuid - attributes: - type: object - properties: - email_domain: - type: string - description: Email domain used to identify the tenant, e.g. prowlerdemo.com - maxLength: 254 - metadata_xml: - type: string - description: Raw IdP metadata XML to configure SingleSignOnService, - certificates, etc. - created_at: - type: string - format: date-time - readOnly: true - updated_at: - type: string - format: date-time - readOnly: true - required: - - email_domain - - metadata_xml - SAMLConfigurationRequest: - type: object - properties: - data: - type: object - required: - - type - additionalProperties: false - properties: - type: - type: string - description: The [type](https://jsonapi.org/format/#document-resource-object-identification) - member is used to describe resource objects that share common attributes - and relationships. - enum: - - saml-configurations - attributes: - type: object - properties: - email_domain: - type: string - minLength: 1 - description: Email domain used to identify the tenant, e.g. prowlerdemo.com - maxLength: 254 - metadata_xml: - type: string - minLength: 1 - description: Raw IdP metadata XML to configure SingleSignOnService, - certificates, etc. - created_at: - type: string - format: date-time - readOnly: true - updated_at: - type: string - format: date-time - readOnly: true - required: - - email_domain - - metadata_xml - required: - - data - SAMLConfigurationResponse: - type: object - properties: - data: - $ref: '#/components/schemas/SAMLConfiguration' - required: - - data - SAMLConfigurationTypeEnum: - type: string - enum: - - saml-configurations Scan: type: object required: diff --git a/api/src/backend/api/tests/test_adapters.py b/api/src/backend/api/tests/test_adapters.py index 05b4195121..b554719836 100644 --- a/api/src/backend/api/tests/test_adapters.py +++ b/api/src/backend/api/tests/test_adapters.py @@ -5,8 +5,6 @@ from allauth.socialaccount.models import SocialLogin from django.contrib.auth import get_user_model from api.adapters import ProwlerSocialAccountAdapter -from api.db_router import MainRouter -from api.models import Membership, SAMLConfiguration, Tenant User = get_user_model() @@ -22,24 +20,24 @@ class TestProwlerSocialAccountAdapter: adapter = ProwlerSocialAccountAdapter() assert adapter.get_user_by_email("notfound@example.com") is None - def test_pre_social_login_links_existing_user(self, create_test_user, rf): - adapter = ProwlerSocialAccountAdapter() + # def test_pre_social_login_links_existing_user(self, create_test_user, rf): + # adapter = ProwlerSocialAccountAdapter() - sociallogin = MagicMock(spec=SocialLogin) - sociallogin.account = MagicMock() - sociallogin.account.provider = "saml" - sociallogin.account.extra_data = {} - sociallogin.user = create_test_user - sociallogin.connect = MagicMock() + # sociallogin = MagicMock(spec=SocialLogin) + # sociallogin.account = MagicMock() + # sociallogin.account.provider = "saml" + # sociallogin.account.extra_data = {} + # sociallogin.user = create_test_user + # sociallogin.connect = MagicMock() - adapter.pre_social_login(rf.get("/"), sociallogin) + # adapter.pre_social_login(rf.get("/"), sociallogin) - call_args = sociallogin.connect.call_args - assert call_args is not None + # call_args = sociallogin.connect.call_args + # assert call_args is not None - called_request, called_user = call_args[0] - assert called_request.path == "/" - assert called_user.email == create_test_user.email + # called_request, called_user = call_args[0] + # assert called_request.path == "/" + # assert called_user.email == create_test_user.email def test_pre_social_login_no_link_if_email_missing(self, rf): adapter = ProwlerSocialAccountAdapter() @@ -54,37 +52,37 @@ class TestProwlerSocialAccountAdapter: sociallogin.connect.assert_not_called() - def test_save_user_saml_flow( - self, - rf, - saml_setup, - saml_sociallogin, - ): - adapter = ProwlerSocialAccountAdapter() - request = rf.get("/") - saml_sociallogin.user.email = saml_setup["email"] - saml_sociallogin.account.extra_data = { - "firstName": [], - "lastName": [], - "organization": [], - "userType": [], - } + # def test_save_user_saml_flow( + # self, + # rf, + # saml_setup, + # saml_sociallogin, + # ): + # adapter = ProwlerSocialAccountAdapter() + # request = rf.get("/") + # saml_sociallogin.user.email = saml_setup["email"] + # saml_sociallogin.account.extra_data = { + # "firstName": [], + # "lastName": [], + # "organization": [], + # "userType": [], + # } - tenant = Tenant.objects.using(MainRouter.admin_db).get( - id=saml_setup["tenant_id"] - ) - saml_config = SAMLConfiguration.objects.using(MainRouter.admin_db).get( - tenant=tenant - ) - assert saml_config.email_domain == saml_setup["domain"] + # tenant = Tenant.objects.using(MainRouter.admin_db).get( + # id=saml_setup["tenant_id"] + # ) + # saml_config = SAMLConfiguration.objects.using(MainRouter.admin_db).get( + # tenant=tenant + # ) + # assert saml_config.email_domain == saml_setup["domain"] - user = adapter.save_user(request, saml_sociallogin) + # user = adapter.save_user(request, saml_sociallogin) - assert user.name == "N/A" - assert user.company_name == "" - assert user.email == saml_setup["email"] - assert ( - Membership.objects.using(MainRouter.admin_db) - .filter(user=user, tenant=tenant) - .exists() - ) + # assert user.name == "N/A" + # assert user.company_name == "" + # assert user.email == saml_setup["email"] + # assert ( + # Membership.objects.using(MainRouter.admin_db) + # .filter(user=user, tenant=tenant) + # .exists() + # ) diff --git a/api/src/backend/api/tests/test_models.py b/api/src/backend/api/tests/test_models.py index d834143ed1..f02cf7b9d2 100644 --- a/api/src/backend/api/tests/test_models.py +++ b/api/src/backend/api/tests/test_models.py @@ -1,9 +1,6 @@ import pytest -from allauth.socialaccount.models import SocialApp -from django.core.exceptions import ValidationError -from api.db_router import MainRouter -from api.models import Resource, ResourceTag, SAMLConfiguration, Tenant +from api.models import Resource, ResourceTag @pytest.mark.django_db @@ -125,147 +122,147 @@ class TestResourceModel: # assert Finding.objects.filter(uid=long_uid).exists() -@pytest.mark.django_db -class TestSAMLConfigurationModel: - VALID_METADATA = """ - - - - - - FAKECERTDATA - - - - - - - """ +# @pytest.mark.django_db +# class TestSAMLConfigurationModel: +# VALID_METADATA = """ +# +# +# +# +# +# FAKECERTDATA +# +# +# +# +# +# +# """ - def test_creates_valid_configuration(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant A") - config = SAMLConfiguration.objects.using(MainRouter.admin_db).create( - email_domain="ssoexample.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant, - ) +# def test_creates_valid_configuration(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant A") +# config = SAMLConfiguration.objects.using(MainRouter.admin_db).create( +# email_domain="ssoexample.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant, +# ) - assert config.email_domain == "ssoexample.com" - assert SocialApp.objects.filter(client_id="ssoexample.com").exists() +# assert config.email_domain == "ssoexample.com" +# assert SocialApp.objects.filter(client_id="ssoexample.com").exists() - def test_email_domain_with_at_symbol_fails(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant B") - config = SAMLConfiguration( - email_domain="invalid@domain.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant, - ) +# def test_email_domain_with_at_symbol_fails(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant B") +# config = SAMLConfiguration( +# email_domain="invalid@domain.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant, +# ) - with pytest.raises(ValidationError) as exc_info: - config.clean() +# with pytest.raises(ValidationError) as exc_info: +# config.clean() - errors = exc_info.value.message_dict - assert "email_domain" in errors - assert "Domain must not contain @" in errors["email_domain"][0] +# errors = exc_info.value.message_dict +# assert "email_domain" in errors +# assert "Domain must not contain @" in errors["email_domain"][0] - def test_duplicate_email_domain_fails(self): - tenant1 = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant C1") - tenant2 = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant C2") +# def test_duplicate_email_domain_fails(self): +# tenant1 = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant C1") +# tenant2 = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant C2") - SAMLConfiguration.objects.using(MainRouter.admin_db).create( - email_domain="duplicate.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant1, - ) +# SAMLConfiguration.objects.using(MainRouter.admin_db).create( +# email_domain="duplicate.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant1, +# ) - config = SAMLConfiguration( - email_domain="duplicate.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant2, - ) +# config = SAMLConfiguration( +# email_domain="duplicate.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant2, +# ) - with pytest.raises(ValidationError) as exc_info: - config.clean() +# with pytest.raises(ValidationError) as exc_info: +# config.clean() - errors = exc_info.value.message_dict - assert "tenant" in errors - assert "There is a problem with your email domain." in errors["tenant"][0] +# errors = exc_info.value.message_dict +# assert "tenant" in errors +# assert "There is a problem with your email domain." in errors["tenant"][0] - def test_duplicate_tenant_config_fails(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant D") +# def test_duplicate_tenant_config_fails(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant D") - SAMLConfiguration.objects.using(MainRouter.admin_db).create( - email_domain="unique1.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant, - ) +# SAMLConfiguration.objects.using(MainRouter.admin_db).create( +# email_domain="unique1.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant, +# ) - config = SAMLConfiguration( - email_domain="unique2.com", - metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, - tenant=tenant, - ) +# config = SAMLConfiguration( +# email_domain="unique2.com", +# metadata_xml=TestSAMLConfigurationModel.VALID_METADATA, +# tenant=tenant, +# ) - with pytest.raises(ValidationError) as exc_info: - config.clean() +# with pytest.raises(ValidationError) as exc_info: +# config.clean() - errors = exc_info.value.message_dict - assert "tenant" in errors - assert ( - "A SAML configuration already exists for this tenant." - in errors["tenant"][0] - ) +# errors = exc_info.value.message_dict +# assert "tenant" in errors +# assert ( +# "A SAML configuration already exists for this tenant." +# in errors["tenant"][0] +# ) - def test_invalid_metadata_xml_fails(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant E") - config = SAMLConfiguration( - email_domain="brokenxml.com", - metadata_xml="", - tenant=tenant, - ) +# def test_invalid_metadata_xml_fails(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant E") +# config = SAMLConfiguration( +# email_domain="brokenxml.com", +# metadata_xml="", +# tenant=tenant, +# ) - with pytest.raises(ValidationError) as exc_info: - config._parse_metadata() +# with pytest.raises(ValidationError) as exc_info: +# config._parse_metadata() - errors = exc_info.value.message_dict - assert "metadata_xml" in errors - assert "Invalid XML" in errors["metadata_xml"][0] - assert "not well-formed" in errors["metadata_xml"][0] +# errors = exc_info.value.message_dict +# assert "metadata_xml" in errors +# assert "Invalid XML" in errors["metadata_xml"][0] +# assert "not well-formed" in errors["metadata_xml"][0] - def test_metadata_missing_sso_fails(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant F") - xml = """ - - """ - config = SAMLConfiguration( - email_domain="nosso.com", - metadata_xml=xml, - tenant=tenant, - ) +# def test_metadata_missing_sso_fails(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant F") +# xml = """ +# +# """ +# config = SAMLConfiguration( +# email_domain="nosso.com", +# metadata_xml=xml, +# tenant=tenant, +# ) - with pytest.raises(ValidationError) as exc_info: - config._parse_metadata() +# with pytest.raises(ValidationError) as exc_info: +# config._parse_metadata() - errors = exc_info.value.message_dict - assert "metadata_xml" in errors - assert "Missing SingleSignOnService" in errors["metadata_xml"][0] +# errors = exc_info.value.message_dict +# assert "metadata_xml" in errors +# assert "Missing SingleSignOnService" in errors["metadata_xml"][0] - def test_metadata_missing_certificate_fails(self): - tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant G") - xml = """ - - - - """ - config = SAMLConfiguration( - email_domain="nocert.com", - metadata_xml=xml, - tenant=tenant, - ) +# def test_metadata_missing_certificate_fails(self): +# tenant = Tenant.objects.using(MainRouter.admin_db).create(name="Tenant G") +# xml = """ +# +# +# +# """ +# config = SAMLConfiguration( +# email_domain="nocert.com", +# metadata_xml=xml, +# tenant=tenant, +# ) - with pytest.raises(ValidationError) as exc_info: - config._parse_metadata() +# with pytest.raises(ValidationError) as exc_info: +# config._parse_metadata() - errors = exc_info.value.message_dict - assert "metadata_xml" in errors - assert "X509Certificate" in errors["metadata_xml"][0] +# errors = exc_info.value.message_dict +# assert "metadata_xml" in errors +# assert "X509Certificate" in errors["metadata_xml"][0] diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 8771e6a1ee..fd744af4df 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -9,19 +9,15 @@ from unittest.mock import ANY, MagicMock, Mock, patch import jwt import pytest -from allauth.socialaccount.models import SocialAccount, SocialApp from botocore.exceptions import ClientError, NoCredentialsError from conftest import API_JSON_CONTENT_TYPE, TEST_PASSWORD, TEST_USER from django.conf import settings -from django.http import JsonResponse -from django.test import RequestFactory from django.urls import reverse from django_celery_results.models import TaskResult from rest_framework import status from rest_framework.response import Response from api.compliance import get_compliance_frameworks -from api.db_router import MainRouter from api.models import ( Integration, Invitation, @@ -32,7 +28,6 @@ from api.models import ( ProviderSecret, Role, RoleProviderGroupRelationship, - SAMLConfiguration, Scan, StateChoices, Task, @@ -40,7 +35,7 @@ from api.models import ( UserRoleRelationship, ) from api.rls import Tenant -from api.v1.views import ComplianceOverviewViewSet, TenantFinishACSView +from api.v1.views import ComplianceOverviewViewSet TODAY = str(datetime.today().date()) @@ -1241,10 +1236,10 @@ class TestProviderViewSet: ("uid.icontains", "1", 5), ("alias", "aws_testing_1", 1), ("alias.icontains", "aws", 2), - ("inserted_at", TODAY, 5), - ("inserted_at.gte", "2024-01-01", 5), + ("inserted_at", TODAY, 6), + ("inserted_at.gte", "2024-01-01", 6), ("inserted_at.lte", "2024-01-01", 0), - ("updated_at.gte", "2024-01-01", 5), + ("updated_at.gte", "2024-01-01", 6), ("updated_at.lte", "2024-01-01", 0), ] ), @@ -1723,6 +1718,50 @@ class TestProviderSecretViewSet: "kubeconfig_content": "kubeconfig-content", }, ), + # M365 with STATIC secret - no user or password + ( + Provider.ProviderChoices.M365.value, + ProviderSecret.TypeChoices.STATIC, + { + "client_id": "client-id", + "client_secret": "client-secret", + "tenant_id": "tenant-id", + }, + ), + # M365 with user only + ( + Provider.ProviderChoices.M365.value, + ProviderSecret.TypeChoices.STATIC, + { + "client_id": "client-id", + "client_secret": "client-secret", + "tenant_id": "tenant-id", + "user": "test@domain.com", + }, + ), + # M365 with password only + ( + Provider.ProviderChoices.M365.value, + ProviderSecret.TypeChoices.STATIC, + { + "client_id": "client-id", + "client_secret": "client-secret", + "tenant_id": "tenant-id", + "password": "supersecret", + }, + ), + # M365 with user and password + ( + Provider.ProviderChoices.M365.value, + ProviderSecret.TypeChoices.STATIC, + { + "client_id": "client-id", + "client_secret": "client-secret", + "tenant_id": "tenant-id", + "user": "test@domain.com", + "password": "supersecret", + }, + ), ], ) def test_provider_secrets_create_valid( @@ -1734,7 +1773,10 @@ class TestProviderSecretViewSet: secret_data, ): # Get the provider from the fixture and set its type - provider = Provider.objects.filter(provider=provider_type)[0] + try: + provider = Provider.objects.filter(provider=provider_type)[0] + except IndexError: + print(f"Provider {provider_type} not found") data = { "data": { @@ -5561,265 +5603,334 @@ class TestIntegrationViewSet: assert response.status_code == status.HTTP_400_BAD_REQUEST -@pytest.mark.django_db -class TestSAMLInitiateAPIView: - def test_valid_email_domain_and_certificates( - self, authenticated_client, saml_setup, monkeypatch - ): - monkeypatch.setenv("SAML_PUBLIC_CERT", "fake_cert") - monkeypatch.setenv("SAML_PRIVATE_KEY", "fake_key") +# @pytest.mark.django_db +# class TestSAMLTokenValidation: +# def test_valid_token_returns_tokens(self, authenticated_client, create_test_user): +# user = create_test_user +# valid_token_data = { +# "access": "mock_access_token", +# "refresh": "mock_refresh_token", +# } +# saml_token = SAMLToken.objects.create( +# token=valid_token_data, +# user=user, +# expires_at=datetime.now(timezone.utc) + timedelta(seconds=10), +# ) - url = reverse("api_saml_initiate") - payload = {"email_domain": saml_setup["email"]} +# url = reverse("token-saml") +# response = authenticated_client.post(f"{url}?id={saml_token.id}") - response = authenticated_client.post(url, data=payload, format="json") +# assert response.status_code == status.HTTP_200_OK +# assert response.json() == {"data": valid_token_data} +# assert not SAMLToken.objects.filter(id=saml_token.id).exists() - assert response.status_code == status.HTTP_302_FOUND - assert f"email={saml_setup['email']}" in response.url - assert ( - reverse("saml_login", kwargs={"organization_slug": saml_setup["domain"]}) - in response.url - ) +# def test_invalid_token_id_returns_404(self, authenticated_client): +# url = reverse("token-saml") +# response = authenticated_client.post(f"{url}?id={str(uuid4())}") - def test_invalid_email_domain(self, authenticated_client): - url = reverse("api_saml_initiate") - payload = {"email_domain": "user@unauthorized.com"} +# assert response.status_code == status.HTTP_404_NOT_FOUND +# assert response.json()["errors"]["detail"] == "Invalid token ID." - response = authenticated_client.post(url, data=payload, format="json") +# def test_expired_token_returns_400(self, authenticated_client, create_test_user): +# user = create_test_user +# expired_token_data = { +# "access": "expired_access_token", +# "refresh": "expired_refresh_token", +# } +# saml_token = SAMLToken.objects.create( +# token=expired_token_data, +# user=user, +# expires_at=datetime.now(timezone.utc) - timedelta(seconds=1), +# ) - assert response.status_code == status.HTTP_403_FORBIDDEN - assert response.json()["errors"]["detail"] == "Unauthorized domain." +# url = reverse("token-saml") +# response = authenticated_client.post(f"{url}?id={saml_token.id}") - def test_missing_certificates(self, authenticated_client, saml_setup, monkeypatch): - monkeypatch.setenv("SAML_PUBLIC_CERT", "") - monkeypatch.setenv("SAML_PRIVATE_KEY", "") +# assert response.status_code == status.HTTP_400_BAD_REQUEST +# assert response.json()["errors"]["detail"] == "Token expired." +# assert SAMLToken.objects.filter(id=saml_token.id).exists() - url = reverse("api_saml_initiate") - payload = {"email_domain": saml_setup["email"]} +# def test_token_can_be_used_only_once(self, authenticated_client, create_test_user): +# user = create_test_user +# token_data = { +# "access": "single_use_token", +# "refresh": "single_use_refresh", +# } +# saml_token = SAMLToken.objects.create( +# token=token_data, +# user=user, +# expires_at=datetime.now(timezone.utc) + timedelta(seconds=10), +# ) - response = authenticated_client.post(url, data=payload, format="json") +# url = reverse("token-saml") - assert response.status_code == status.HTTP_403_FORBIDDEN - assert ( - response.json()["errors"]["detail"] - == "SAML configuration is invalid: missing certificates." - ) +# # First use: should succeed +# response1 = authenticated_client.post(f"{url}?id={saml_token.id}") +# assert response1.status_code == status.HTTP_200_OK + +# # Second use: should fail (already deleted) +# response2 = authenticated_client.post(f"{url}?id={saml_token.id}") +# assert response2.status_code == status.HTTP_404_NOT_FOUND -@pytest.mark.django_db -class TestSAMLConfigurationViewSet: - def test_list_saml_configurations(self, authenticated_client, saml_setup): - config = SAMLConfiguration.objects.get( - email_domain=saml_setup["email"].split("@")[-1] - ) - response = authenticated_client.get(reverse("saml-config-list")) - assert response.status_code == status.HTTP_200_OK - assert ( - response.json()["data"][0]["attributes"]["email_domain"] - == config.email_domain - ) +# @pytest.mark.django_db +# class TestSAMLInitiateAPIView: +# def test_valid_email_domain_and_certificates( +# self, authenticated_client, saml_setup, monkeypatch +# ): +# monkeypatch.setenv("SAML_PUBLIC_CERT", "fake_cert") +# monkeypatch.setenv("SAML_PRIVATE_KEY", "fake_key") - def test_retrieve_saml_configuration(self, authenticated_client, saml_setup): - config = SAMLConfiguration.objects.get( - email_domain=saml_setup["email"].split("@")[-1] - ) - response = authenticated_client.get( - reverse("saml-config-detail", kwargs={"pk": config.id}) - ) - assert response.status_code == status.HTTP_200_OK - assert ( - response.json()["data"]["attributes"]["metadata_xml"] == config.metadata_xml - ) +# url = reverse("api_saml_initiate") +# payload = {"email_domain": saml_setup["email"]} - def test_create_saml_configuration(self, authenticated_client, tenants_fixture): - payload = { - "email_domain": "newdomain.com", - "metadata_xml": """ - - - - - - TEST - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - - - - - """, - } - response = authenticated_client.post( - reverse("saml-config-list"), data=payload, format="json" - ) - assert response.status_code == status.HTTP_201_CREATED - assert SAMLConfiguration.objects.filter(email_domain="newdomain.com").exists() +# response = authenticated_client.post(url, data=payload, format="json") - def test_update_saml_configuration(self, authenticated_client, saml_setup): - config = SAMLConfiguration.objects.get( - email_domain=saml_setup["email"].split("@")[-1] - ) - payload = { - "data": { - "type": "saml-configurations", - "id": str(config.id), - "attributes": { - "metadata_xml": """ - - - - - - TEST2 - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - - - - - """ - }, - } - } - response = authenticated_client.patch( - reverse("saml-config-detail", kwargs={"pk": config.id}), - data=payload, - content_type="application/vnd.api+json", - ) - assert response.status_code == status.HTTP_200_OK - config.refresh_from_db() - assert ( - config.metadata_xml.strip() - == payload["data"]["attributes"]["metadata_xml"].strip() - ) +# assert response.status_code == status.HTTP_302_FOUND +# assert ( +# reverse("saml_login", kwargs={"organization_slug": saml_setup["domain"]}) +# in response.url +# ) +# assert "SAMLRequest" not in response.url - def test_delete_saml_configuration(self, authenticated_client, saml_setup): - config = SAMLConfiguration.objects.get( - email_domain=saml_setup["email"].split("@")[-1] - ) - response = authenticated_client.delete( - reverse("saml-config-detail", kwargs={"pk": config.id}) - ) - assert response.status_code == status.HTTP_204_NO_CONTENT - assert not SAMLConfiguration.objects.filter(id=config.id).exists() +# def test_invalid_email_domain(self, authenticated_client): +# url = reverse("api_saml_initiate") +# payload = {"email_domain": "user@unauthorized.com"} + +# response = authenticated_client.post(url, data=payload, format="json") + +# assert response.status_code == status.HTTP_403_FORBIDDEN +# assert response.json()["errors"]["detail"] == "Unauthorized domain." -@pytest.mark.django_db -class TestTenantFinishACSView: - def test_dispatch_skips_if_user_not_authenticated(self): - request = RequestFactory().get( - reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) - ) - request.user = type("Anonymous", (), {"is_authenticated": False})() +# @pytest.mark.django_db +# class TestSAMLConfigurationViewSet: +# def test_list_saml_configurations(self, authenticated_client, saml_setup): +# config = SAMLConfiguration.objects.get( +# email_domain=saml_setup["email"].split("@")[-1] +# ) +# response = authenticated_client.get(reverse("saml-config-list")) +# assert response.status_code == status.HTTP_200_OK +# assert ( +# response.json()["data"][0]["attributes"]["email_domain"] +# == config.email_domain +# ) - with patch( - "allauth.socialaccount.providers.saml.views.get_app_or_404" - ) as mock_get_app: - mock_get_app.return_value = SocialApp( - provider="saml", - client_id="testtenant", - name="Test App", - settings={}, - ) +# def test_retrieve_saml_configuration(self, authenticated_client, saml_setup): +# config = SAMLConfiguration.objects.get( +# email_domain=saml_setup["email"].split("@")[-1] +# ) +# response = authenticated_client.get( +# reverse("saml-config-detail", kwargs={"pk": config.id}) +# ) +# assert response.status_code == status.HTTP_200_OK +# assert ( +# response.json()["data"]["attributes"]["metadata_xml"] == config.metadata_xml +# ) - view = TenantFinishACSView.as_view() - response = view(request, organization_slug="testtenant") +# def test_create_saml_configuration(self, authenticated_client, tenants_fixture): +# payload = { +# "email_domain": "newdomain.com", +# "metadata_xml": """ +# +# +# +# +# +# TEST +# +# +# +# urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +# +# +# +# +# """, +# } +# response = authenticated_client.post( +# reverse("saml-config-list"), data=payload, format="json" +# ) +# assert response.status_code == status.HTTP_201_CREATED +# assert SAMLConfiguration.objects.filter(email_domain="newdomain.com").exists() - assert response.status_code in [200, 302] +# def test_update_saml_configuration(self, authenticated_client, saml_setup): +# config = SAMLConfiguration.objects.get( +# email_domain=saml_setup["email"].split("@")[-1] +# ) +# payload = { +# "data": { +# "type": "saml-configurations", +# "id": str(config.id), +# "attributes": { +# "metadata_xml": """ +# +# +# +# +# +# TEST2 +# +# +# +# urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +# +# +# +# +# """ +# }, +# } +# } +# response = authenticated_client.patch( +# reverse("saml-config-detail", kwargs={"pk": config.id}), +# data=payload, +# content_type="application/vnd.api+json", +# ) +# assert response.status_code == status.HTTP_200_OK +# config.refresh_from_db() +# assert ( +# config.metadata_xml.strip() +# == payload["data"]["attributes"]["metadata_xml"].strip() +# ) - def test_dispatch_skips_if_social_app_not_found(self, users_fixture): - request = RequestFactory().get( - reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) - ) - request.user = users_fixture[0] +# def test_delete_saml_configuration(self, authenticated_client, saml_setup): +# config = SAMLConfiguration.objects.get( +# email_domain=saml_setup["email"].split("@")[-1] +# ) +# response = authenticated_client.delete( +# reverse("saml-config-detail", kwargs={"pk": config.id}) +# ) +# assert response.status_code == status.HTTP_204_NO_CONTENT +# assert not SAMLConfiguration.objects.filter(id=config.id).exists() - with patch( - "allauth.socialaccount.providers.saml.views.get_app_or_404" - ) as mock_get_app: - mock_get_app.return_value = SocialApp( - provider="saml", - client_id="testtenant", - name="Test App", - settings={}, - ) - view = TenantFinishACSView.as_view() - response = view(request, organization_slug="testtenant") +# @pytest.mark.django_db +# class TestTenantFinishACSView: +# def test_dispatch_skips_if_user_not_authenticated(self): +# request = RequestFactory().get( +# reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) +# ) +# request.user = type("Anonymous", (), {"is_authenticated": False})() - assert isinstance(response, JsonResponse) or response.status_code in [200, 302] +# with patch( +# "allauth.socialaccount.providers.saml.views.get_app_or_404" +# ) as mock_get_app: +# mock_get_app.return_value = SocialApp( +# provider="saml", +# client_id="testtenant", +# name="Test App", +# settings={}, +# ) - def test_dispatch_sets_user_profile_and_assigns_role( - self, create_test_user, tenants_fixture, saml_setup - ): - user = create_test_user - original_email = user.email - original_name = user.name - original_company = user.company_name - user.email = f"doe@{saml_setup['email']}" +# view = TenantFinishACSView.as_view() +# response = view(request, organization_slug="testtenant") - social_account = SocialAccount( - user=user, - provider="saml", - extra_data={ - "firstName": ["John"], - "lastName": ["Doe"], - "organization": ["TestOrg"], - "userType": ["saml_default_role"], - }, - ) +# assert response.status_code in [200, 302] - request = RequestFactory().get( - reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) - ) - request.user = user +# def test_dispatch_skips_if_social_app_not_found(self, users_fixture): +# request = RequestFactory().get( +# reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) +# ) +# request.user = users_fixture[0] - with ( - patch( - "allauth.socialaccount.providers.saml.views.get_app_or_404" - ) as mock_get_app_or_404, - patch("allauth.socialaccount.models.SocialApp.objects.get"), - patch( - "allauth.socialaccount.models.SocialAccount.objects.get" - ) as mock_socialaccount_get, - patch("api.v1.serializers.TokenSocialLoginSerializer") as mock_serializer, - ): - mock_get_app_or_404.return_value = MagicMock( - provider="saml", client_id="testtenant", name="Test App", settings={} - ) +# with patch( +# "allauth.socialaccount.providers.saml.views.get_app_or_404" +# ) as mock_get_app: +# mock_get_app.return_value = SocialApp( +# provider="saml", +# client_id="testtenant", +# name="Test App", +# settings={}, +# ) - mock_socialaccount_get.return_value = social_account +# view = TenantFinishACSView.as_view() +# response = view(request, organization_slug="testtenant") - mock_instance = mock_serializer.return_value - mock_instance.is_valid.return_value = True - mock_instance.validated_data = { - "token": "mocktoken", - "refresh_token": "mockrefresh", - } +# assert isinstance(response, JsonResponse) or response.status_code in [200, 302] - view = TenantFinishACSView.as_view() - response = view(request, organization_slug="testtenant") +# def test_dispatch_sets_user_profile_and_assigns_role_and_creates_token( +# self, create_test_user, tenants_fixture, saml_setup, settings, monkeypatch +# ): +# monkeypatch.setenv("SAML_SSO_CALLBACK_URL", "http://localhost/sso-complete") +# user = create_test_user +# original_email = user.email +# original_name = user.name +# original_company = user.company_name +# user.email = f"doe@{saml_setup['email']}" - assert response.status_code == 200 - user.refresh_from_db() - assert user.name == "John Doe" - assert user.company_name == "TestOrg" +# social_account = SocialAccount( +# user=user, +# provider="saml", +# extra_data={ +# "firstName": ["John"], +# "lastName": ["Doe"], +# "organization": ["TestOrg"], +# "userType": ["saml_default_role"], +# }, +# ) - role = Role.objects.using(MainRouter.admin_db).get(name="saml_default_role") - assert role.tenant == tenants_fixture[0] +# request = RequestFactory().get( +# reverse("saml_finish_acs", kwargs={"organization_slug": "testtenant"}) +# ) +# request.user = user - assert ( - UserRoleRelationship.objects.using(MainRouter.admin_db) - .filter(user=user, tenant_id=tenants_fixture[0].id) - .exists() - ) - user.email = original_email - user.name = original_name - user.company_name = original_company - user.save() +# with ( +# patch( +# "allauth.socialaccount.providers.saml.views.get_app_or_404" +# ) as mock_get_app_or_404, +# patch("allauth.socialaccount.models.SocialApp.objects.get"), +# patch( +# "allauth.socialaccount.models.SocialAccount.objects.get" +# ) as mock_sa_get, +# ): +# mock_get_app_or_404.return_value = MagicMock( +# provider="saml", client_id="testtenant", name="Test App", settings={} +# ) +# mock_sa_get.return_value = social_account + +# view = TenantFinishACSView.as_view() +# response = view(request, organization_slug="testtenant") + +# assert response.status_code == 302 + +# expected_callback_host = "localhost" +# parsed_url = urlparse(response.url) +# assert parsed_url.netloc == expected_callback_host +# query_params = parse_qs(parsed_url.query) +# assert "id" in query_params + +# token_id = query_params["id"][0] +# token_obj = SAMLToken.objects.get(id=token_id) +# assert token_obj.user == user +# assert not token_obj.is_expired() + +# user.refresh_from_db() +# assert user.name == "John Doe" +# assert user.company_name == "TestOrg" + +# role = Role.objects.using(MainRouter.admin_db).get(name="saml_default_role") +# assert role.tenant == tenants_fixture[0] + +# assert ( +# UserRoleRelationship.objects.using(MainRouter.admin_db) +# .filter(user=user, tenant_id=tenants_fixture[0].id) +# .exists() +# ) + +# # Membership should have been created with default role +# membership = Membership.objects.using(MainRouter.admin_db).get( +# user=user, tenant=tenants_fixture[0] +# ) +# assert membership.role == Membership.RoleChoices.MEMBER +# assert membership.user == user +# assert membership.tenant == tenants_fixture[0] + +# # Restore original user state +# user.email = original_email +# user.name = original_name +# user.company_name = original_company +# user.save() @pytest.mark.django_db diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 73d9cea5b8..858a9c0456 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -29,7 +29,6 @@ from api.models import ( ResourceTag, Role, RoleProviderGroupRelationship, - SAMLConfiguration, Scan, StateChoices, StatusChoices, @@ -1200,8 +1199,8 @@ class M365ProviderSecret(serializers.Serializer): client_id = serializers.CharField() client_secret = serializers.CharField() tenant_id = serializers.CharField() - user = serializers.EmailField() - password = serializers.CharField() + user = serializers.EmailField(required=False) + password = serializers.CharField(required=False) class Meta: resource_name = "provider-secrets" @@ -2068,23 +2067,23 @@ class IntegrationUpdateSerializer(BaseWriteIntegrationSerializer): # SSO -class SamlInitiateSerializer(serializers.Serializer): - email_domain = serializers.CharField() +# class SamlInitiateSerializer(serializers.Serializer): +# email_domain = serializers.CharField() - class JSONAPIMeta: - resource_name = "saml-initiate" +# class JSONAPIMeta: +# resource_name = "saml-initiate" -class SamlMetadataSerializer(serializers.Serializer): - class JSONAPIMeta: - resource_name = "saml-meta" +# class SamlMetadataSerializer(serializers.Serializer): +# class JSONAPIMeta: +# resource_name = "saml-meta" -class SAMLConfigurationSerializer(RLSSerializer): - class Meta: - model = SAMLConfiguration - fields = ["id", "email_domain", "metadata_xml", "created_at", "updated_at"] - read_only_fields = ["id", "created_at", "updated_at"] +# class SAMLConfigurationSerializer(RLSSerializer): +# class Meta: +# model = SAMLConfiguration +# fields = ["id", "email_domain", "metadata_xml", "created_at", "updated_at"] +# read_only_fields = ["id", "created_at", "updated_at"] class LighthouseConfigSerializer(RLSSerializer): diff --git a/api/src/backend/api/v1/urls.py b/api/src/backend/api/v1/urls.py index c96a6f56d0..8c07b10470 100644 --- a/api/src/backend/api/v1/urls.py +++ b/api/src/backend/api/v1/urls.py @@ -23,13 +23,10 @@ from api.v1.views import ( ResourceViewSet, RoleProviderGroupRelationshipView, RoleViewSet, - SAMLConfigurationViewSet, - SAMLInitiateAPIView, ScanViewSet, ScheduleViewSet, SchemaView, TaskViewSet, - TenantFinishACSView, TenantMembersViewSet, TenantViewSet, UserRoleRelationshipView, @@ -53,7 +50,7 @@ router.register( router.register(r"overviews", OverviewViewSet, basename="overview") router.register(r"schedules", ScheduleViewSet, basename="schedule") router.register(r"integrations", IntegrationViewSet, basename="integration") -router.register(r"saml-config", SAMLConfigurationViewSet, basename="saml-config") +# router.register(r"saml-config", SAMLConfigurationViewSet, basename="saml-config") router.register( r"lighthouse-configurations", LighthouseConfigViewSet, @@ -122,17 +119,24 @@ urlpatterns = [ ), name="provider_group-providers-relationship", ), - # API endpoint to start SAML SSO flow - path( - "auth/saml/initiate/", SAMLInitiateAPIView.as_view(), name="api_saml_initiate" - ), - # Allauth SAML endpoints for tenants - path("accounts/", include("allauth.urls")), - path( - "api/v1/accounts/saml//acs/finish/", - TenantFinishACSView.as_view(), - name="saml_finish_acs", - ), + # API endpoint to start SAML SSO flow (WIP) + # path( + # "auth/saml/initiate/", SAMLInitiateAPIView.as_view(), name="api_saml_initiate" + # ), + # # Custom SAML endpoints (must come before allauth.urls) (WIP) + # path( + # "accounts/saml//login/", + # CustomSAMLLoginView.as_view(), + # name="saml_login", + # ), + # path( + # "accounts/saml//acs/finish/", + # TenantFinishACSView.as_view(), + # name="saml_finish_acs", + # ), + # Allauth SAML endpoints for tenants (WIP) + # path("accounts/", include("allauth.urls")), + # path("tokens/saml", SAMLTokenValidateView.as_view(), name="token-saml"), path("tokens/google", GoogleSocialLoginView.as_view(), name="token-google"), path("tokens/github", GithubSocialLoginView.as_view(), name="token-github"), path("", include(router.urls)), diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 7d6cf1bfa0..6fe771ebfc 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -3,10 +3,8 @@ import os from datetime import datetime, timedelta, timezone import sentry_sdk -from allauth.socialaccount.models import SocialAccount, SocialApp from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter -from allauth.socialaccount.providers.saml.views import FinishACSView from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError from celery.result import AsyncResult from config.env import env @@ -21,8 +19,7 @@ from django.contrib.postgres.search import SearchQuery from django.db import transaction from django.db.models import Count, Exists, F, OuterRef, Prefetch, Q, Sum from django.db.models.functions import Coalesce -from django.http import HttpResponse, JsonResponse -from django.shortcuts import redirect +from django.http import HttpResponse from django.urls import reverse from django.utils.dateparse import parse_date from django.utils.decorators import method_decorator @@ -67,7 +64,6 @@ from api.compliance import ( get_compliance_frameworks, ) from api.db_router import MainRouter -from api.db_utils import rls_transaction from api.exceptions import TaskFailedException from api.filters import ( ComplianceOverviewFilter, @@ -105,8 +101,6 @@ from api.models import ( ResourceScanSummary, Role, RoleProviderGroupRelationship, - SAMLConfiguration, - SAMLDomainIndex, Scan, ScanSummary, SeverityChoices, @@ -163,8 +157,6 @@ from api.v1.serializers import ( RoleProviderGroupRelationshipSerializer, RoleSerializer, RoleUpdateSerializer, - SAMLConfigurationSerializer, - SamlInitiateSerializer, ScanComplianceReportSerializer, ScanCreateSerializer, ScanReportSerializer, @@ -401,171 +393,240 @@ class GithubSocialLoginView(SocialLoginView): return original_response -@extend_schema(exclude=True) -class SAMLInitiateAPIView(GenericAPIView): - serializer_class = SamlInitiateSerializer - permission_classes = [] +# @extend_schema(exclude=True) +# class SAMLTokenValidateView(GenericAPIView): +# resource_name = "tokens" +# http_method_names = ["post"] - def post(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - email = serializer.validated_data["email_domain"] - domain = email.split("@", 1)[-1].lower() +# def post(self, request): +# token_id = request.query_params.get("id", "invalid") +# try: +# saml_token = SAMLToken.objects.using(MainRouter.admin_db).get(id=token_id) +# except SAMLToken.DoesNotExist: +# return Response({"detail": "Invalid token ID."}, status=404) - try: - check = SAMLDomainIndex.objects.get(email_domain=domain) - with rls_transaction(str(check.tenant_id)): - config = SAMLConfiguration.objects.get(tenant_id=str(check.tenant_id)) - except (SAMLDomainIndex.DoesNotExist, SAMLConfiguration.DoesNotExist): - return Response( - {"detail": "Unauthorized domain."}, status=status.HTTP_403_FORBIDDEN - ) +# if saml_token.is_expired(): +# return Response({"detail": "Token expired."}, status=400) - # Check certificates are not empty - saml_public_cert = os.getenv("SAML_PUBLIC_CERT", "").strip() - saml_private_key = os.getenv("SAML_PRIVATE_KEY", "").strip() +# token_data = saml_token.token +# # Currently we don't store the tokens in the database, so we delete the token after use +# saml_token.delete() - if not saml_public_cert or not saml_private_key: - return Response( - {"detail": "SAML configuration is invalid: missing certificates."}, - status=status.HTTP_403_FORBIDDEN, - ) - - saml_login_url = reverse( - "saml_login", kwargs={"organization_slug": config.email_domain} - ) - return redirect(f"{saml_login_url}?email={email}") +# return Response(token_data, status=200) -@extend_schema_view( - list=extend_schema( - tags=["SAML"], - summary="List all SSO configurations", - description="Returns all the SAML-based SSO configurations associated with the current tenant.", - ), - retrieve=extend_schema( - tags=["SAML"], - summary="Retrieve SSO configuration details", - description="Returns the details of a specific SAML configuration belonging to the current tenant.", - ), - create=extend_schema( - tags=["SAML"], - summary="Create the SSO configuration", - description="Creates a new SAML SSO configuration for the current tenant, including email domain and metadata XML.", - ), - partial_update=extend_schema( - tags=["SAML"], - summary="Update the SSO configuration", - description="Partially updates an existing SAML SSO configuration. Supports changes to email domain and metadata XML.", - ), - destroy=extend_schema( - tags=["SAML"], - summary="Delete the SSO configuration", - description="Deletes an existing SAML SSO configuration associated with the current tenant.", - ), -) -@method_decorator(CACHE_DECORATOR, name="retrieve") -@method_decorator(CACHE_DECORATOR, name="list") -class SAMLConfigurationViewSet(BaseRLSViewSet): - """ - ViewSet for managing SAML SSO configurations per tenant. +# @extend_schema(exclude=True) +# class CustomSAMLLoginView(LoginView): +# def dispatch(self, request, *args, **kwargs): +# """ +# Convert GET requests to POST to bypass allauth's confirmation screen. - This endpoint allows authorized users to perform CRUD operations on SAMLConfiguration, - which define how a tenant integrates with an external SAML Identity Provider (IdP). +# Why this is necessary: +# - django-allauth requires POST for social logins to prevent open redirect attacks +# - SAML login links typically use GET requests (e.g., ) +# - This conversion allows seamless login without user-facing confirmation - Typical use cases include: - - Listing all existing configurations for auditing or UI display. - - Retrieving a single configuration to show setup details. - - Creating or updating a configuration to onboard or modify SAML integration. - - Deleting a configuration when deactivating SAML for a tenant. - """ +# Security considerations: +# 1. Preserves CSRF protection: Original POST handling remains intact +# 2. Avoids global SOCIALACCOUNT_LOGIN_ON_GET=True which would: +# - Enable GET logins for ALL providers (security risk) +# - Potentially expose open redirect vulnerabilities +# 3. SAML payloads remain signed/encrypted regardless of HTTP method +# 4. No sensitive parameters are exposed in URLs (copied to POST body) - serializer_class = SAMLConfigurationSerializer - required_permissions = [Permissions.MANAGE_INTEGRATIONS] - queryset = SAMLConfiguration.objects.all() - - def get_queryset(self): - # If called during schema generation, return an empty queryset - if getattr(self, "swagger_fake_view", False): - return SAMLConfiguration.objects.none() - return SAMLConfiguration.objects.filter(tenant=self.request.tenant_id) +# This approach maintains security while providing better UX. +# """ +# if request.method == "GET": +# # Convert GET to POST while preserving parameters +# request.method = "POST" +# # Safe because SAML validates signatures +# request.POST = request.GET.copy() +# return super().dispatch(request, *args, **kwargs) -class TenantFinishACSView(FinishACSView): - def dispatch(self, request, organization_slug): - response = super().dispatch(request, organization_slug) - user = getattr(request, "user", None) - if not user or not user.is_authenticated: - return response +# @extend_schema(exclude=True) +# class SAMLInitiateAPIView(GenericAPIView): +# serializer_class = SamlInitiateSerializer +# permission_classes = [] - try: - social_app = SocialApp.objects.get( - provider="saml", client_id=organization_slug - ) - social_account = SocialAccount.objects.get( - user=user, provider=social_app.provider - ) - except (SocialApp.DoesNotExist, SocialAccount.DoesNotExist): - return response +# def post(self, request, *args, **kwargs): +# # Validate the input payload and extract the domain +# serializer = self.get_serializer(data=request.data) +# serializer.is_valid(raise_exception=True) +# email = serializer.validated_data["email_domain"] +# domain = email.split("@", 1)[-1].lower() - extra = social_account.extra_data - user.first_name = ( - extra.get("firstName", [""])[0] if extra.get("firstName") else "" - ) - user.last_name = extra.get("lastName", [""])[0] if extra.get("lastName") else "" - user.company_name = ( - extra.get("organization", [""])[0] if extra.get("organization") else "" - ) - user.name = f"{user.first_name} {user.last_name}".strip() - if user.name == "": - user.name = "N/A" - user.save() +# # Retrieve the SAML configuration for the given email domain +# try: +# check = SAMLDomainIndex.objects.get(email_domain=domain) +# with rls_transaction(str(check.tenant_id)): +# config = SAMLConfiguration.objects.get(tenant_id=str(check.tenant_id)) +# except (SAMLDomainIndex.DoesNotExist, SAMLConfiguration.DoesNotExist): +# return Response( +# {"detail": "Unauthorized domain."}, status=status.HTTP_403_FORBIDDEN +# ) - email_domain = user.email.split("@")[-1] - tenant = ( - SAMLConfiguration.objects.using(MainRouter.admin_db) - .get(email_domain=email_domain) - .tenant - ) - role_name = ( - extra.get("userType", ["saml_default_role"])[0].strip() - if extra.get("userType") - else "saml_default_role" - ) - try: - role = Role.objects.using(MainRouter.admin_db).get( - name=role_name, tenant=tenant - ) - except Role.DoesNotExist: - role = Role.objects.using(MainRouter.admin_db).create( - name=role_name, - tenant=tenant, - manage_users=False, - manage_account=False, - manage_billing=False, - manage_providers=False, - manage_integrations=False, - manage_scans=False, - unlimited_visibility=False, - ) - UserRoleRelationship.objects.using(MainRouter.admin_db).filter( - user=user, - tenant_id=tenant.id, - ).delete() - UserRoleRelationship.objects.using(MainRouter.admin_db).create( - user=user, - role=role, - tenant_id=tenant.id, - ) +# # Check certificates are not empty (TODO: Validate certificates) +# # saml_public_cert = os.getenv("SAML_PUBLIC_CERT", "").strip() +# # saml_private_key = os.getenv("SAML_PRIVATE_KEY", "").strip() - serializer = TokenSocialLoginSerializer(data={"email": user.email}) - serializer.is_valid(raise_exception=True) - return JsonResponse( - { - "type": "saml-social-tokens", - "attributes": serializer.validated_data, - } - ) +# # if not saml_public_cert or not saml_private_key: +# # return Response( +# # {"detail": "SAML configuration is invalid: missing certificates."}, +# # status=status.HTTP_403_FORBIDDEN, +# # ) + +# # Build the SAML login URL using the configured API host +# api_host = os.getenv("API_BASE_URL") +# login_path = reverse( +# "saml_login", kwargs={"organization_slug": config.email_domain} +# ) +# login_url = urljoin(api_host, login_path) + +# return redirect(login_url) + + +# @extend_schema_view( +# list=extend_schema( +# tags=["SAML"], +# summary="List all SSO configurations", +# description="Returns all the SAML-based SSO configurations associated with the current tenant.", +# ), +# retrieve=extend_schema( +# tags=["SAML"], +# summary="Retrieve SSO configuration details", +# description="Returns the details of a specific SAML configuration belonging to the current tenant.", +# ), +# create=extend_schema( +# tags=["SAML"], +# summary="Create the SSO configuration", +# description="Creates a new SAML SSO configuration for the current tenant, including email domain and metadata XML.", +# ), +# partial_update=extend_schema( +# tags=["SAML"], +# summary="Update the SSO configuration", +# description="Partially updates an existing SAML SSO configuration. Supports changes to email domain and metadata XML.", +# ), +# destroy=extend_schema( +# tags=["SAML"], +# summary="Delete the SSO configuration", +# description="Deletes an existing SAML SSO configuration associated with the current tenant.", +# ), +# ) +# @method_decorator(CACHE_DECORATOR, name="retrieve") +# @method_decorator(CACHE_DECORATOR, name="list") +# class SAMLConfigurationViewSet(BaseRLSViewSet): +# """ +# ViewSet for managing SAML SSO configurations per tenant. + +# This endpoint allows authorized users to perform CRUD operations on SAMLConfiguration, +# which define how a tenant integrates with an external SAML Identity Provider (IdP). + +# Typical use cases include: +# - Listing all existing configurations for auditing or UI display. +# - Retrieving a single configuration to show setup details. +# - Creating or updating a configuration to onboard or modify SAML integration. +# - Deleting a configuration when deactivating SAML for a tenant. +# """ + +# serializer_class = SAMLConfigurationSerializer +# required_permissions = [Permissions.MANAGE_INTEGRATIONS] +# queryset = SAMLConfiguration.objects.all() + +# def get_queryset(self): +# # If called during schema generation, return an empty queryset +# if getattr(self, "swagger_fake_view", False): +# return SAMLConfiguration.objects.none() +# return SAMLConfiguration.objects.filter(tenant=self.request.tenant_id) + + +# class TenantFinishACSView(FinishACSView): +# def dispatch(self, request, organization_slug): +# response = super().dispatch(request, organization_slug) +# user = getattr(request, "user", None) +# if not user or not user.is_authenticated: +# return response + +# try: +# social_app = SocialApp.objects.get( +# provider="saml", client_id=organization_slug +# ) +# social_account = SocialAccount.objects.get( +# user=user, provider=social_app.provider +# ) +# except (SocialApp.DoesNotExist, SocialAccount.DoesNotExist): +# return response + +# extra = social_account.extra_data +# user.first_name = ( +# extra.get("firstName", [""])[0] if extra.get("firstName") else "" +# ) +# user.last_name = extra.get("lastName", [""])[0] if extra.get("lastName") else "" +# user.company_name = ( +# extra.get("organization", [""])[0] if extra.get("organization") else "" +# ) +# user.name = f"{user.first_name} {user.last_name}".strip() +# if user.name == "": +# user.name = "N/A" +# user.save() + +# email_domain = user.email.split("@")[-1] +# tenant = ( +# SAMLConfiguration.objects.using(MainRouter.admin_db) +# .get(email_domain=email_domain) +# .tenant +# ) +# role_name = ( +# extra.get("userType", ["saml_default_role"])[0].strip() +# if extra.get("userType") +# else "saml_default_role" +# ) +# try: +# role = Role.objects.using(MainRouter.admin_db).get( +# name=role_name, tenant=tenant +# ) +# except Role.DoesNotExist: +# role = Role.objects.using(MainRouter.admin_db).create( +# name=role_name, +# tenant=tenant, +# manage_users=False, +# manage_account=False, +# manage_billing=False, +# manage_providers=False, +# manage_integrations=False, +# manage_scans=False, +# unlimited_visibility=False, +# ) +# UserRoleRelationship.objects.using(MainRouter.admin_db).filter( +# user=user, +# tenant_id=tenant.id, +# ).delete() +# UserRoleRelationship.objects.using(MainRouter.admin_db).create( +# user=user, +# role=role, +# tenant_id=tenant.id, +# ) +# membership, _ = Membership.objects.using(MainRouter.admin_db).get_or_create( +# user=user, +# tenant=tenant, +# defaults={ +# "user": user, +# "tenant": tenant, +# "role": Membership.RoleChoices.MEMBER, +# }, +# ) + +# serializer = TokenSocialLoginSerializer(data={"email": user.email}) +# serializer.is_valid(raise_exception=True) + +# token_data = serializer.validated_data +# saml_token = SAMLToken.objects.using(MainRouter.admin_db).create( +# token=token_data, user=user +# ) +# callback_url = env.str("SAML_SSO_CALLBACK_URL") +# redirect_url = f"{callback_url}?id={saml_token.id}" + +# return redirect(redirect_url) @extend_schema_view( diff --git a/api/src/backend/config/settings/social_login.py b/api/src/backend/config/settings/social_login.py index cf4e21d75b..ae9deb363a 100644 --- a/api/src/backend/config/settings/social_login.py +++ b/api/src/backend/config/settings/social_login.py @@ -25,9 +25,9 @@ SOCIALACCOUNT_EMAIL_AUTHENTICATION = True SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True SOCIALACCOUNT_ADAPTER = "api.adapters.ProwlerSocialAccountAdapter" -# SAML keys -SAML_PUBLIC_CERT = env("SAML_PUBLIC_CERT", default="") -SAML_PRIVATE_KEY = env("SAML_PRIVATE_KEY", default="") +# SAML keys (TODO: Validate certificates) +# SAML_PUBLIC_CERT = env("SAML_PUBLIC_CERT", default="") +# SAML_PRIVATE_KEY = env("SAML_PRIVATE_KEY", default="") SOCIALACCOUNT_PROVIDERS = { "google": { @@ -60,12 +60,17 @@ SOCIALACCOUNT_PROVIDERS = { "entity_id": "urn:prowler.com:sp", }, "advanced": { - "x509cert": SAML_PUBLIC_CERT, - "private_key": SAML_PRIVATE_KEY, + # "x509cert": SAML_PUBLIC_CERT, + # "private_key": SAML_PRIVATE_KEY, + # "authn_request_signed": True, + # "want_assertion_signed": True, + # "want_message_signed": True, "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "authn_request_signed": True, - "want_assertion_signed": True, - "want_message_signed": True, + "authn_request_signed": False, + "logout_request_signed": False, + "logout_response_signed": False, + "want_assertion_encrypted": False, + "want_name_id_encrypted": False, }, }, } diff --git a/api/src/backend/conftest.py b/api/src/backend/conftest.py index c601e07b80..e4a03b78cb 100644 --- a/api/src/backend/conftest.py +++ b/api/src/backend/conftest.py @@ -1,9 +1,8 @@ import logging from datetime import datetime, timedelta, timezone -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest -from allauth.socialaccount.models import SocialLogin from django.conf import settings from django.db import connection as django_connection from django.db import connections as django_connections @@ -29,8 +28,6 @@ from api.models import ( Resource, ResourceTag, Role, - SAMLConfiguration, - SAMLDomainIndex, Scan, ScanSummary, StateChoices, @@ -381,8 +378,14 @@ def providers_fixture(tenants_fixture): tenant_id=tenant.id, scanner_args={"key1": "value1", "key2": {"key21": "value21"}}, ) + provider6 = Provider.objects.create( + provider="m365", + uid="m365.test.com", + alias="m365_testing", + tenant_id=tenant.id, + ) - return provider1, provider2, provider3, provider4, provider5 + return provider1, provider2, provider3, provider4, provider5, provider6 @pytest.fixture @@ -1115,62 +1118,62 @@ def latest_scan_finding(authenticated_client, providers_fixture, resources_fixtu return finding -@pytest.fixture -def saml_setup(tenants_fixture): - tenant_id = tenants_fixture[0].id - domain = "example.com" +# @pytest.fixture +# def saml_setup(tenants_fixture): +# tenant_id = tenants_fixture[0].id +# domain = "example.com" - SAMLDomainIndex.objects.create(email_domain=domain, tenant_id=tenant_id) +# SAMLDomainIndex.objects.create(email_domain=domain, tenant_id=tenant_id) - metadata_xml = """ - - - - - - TEST - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - - - - - """ - SAMLConfiguration.objects.create( - tenant_id=str(tenant_id), - email_domain=domain, - metadata_xml=metadata_xml, - ) +# metadata_xml = """ +# +# +# +# +# +# TEST +# +# +# +# urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +# +# +# +# +# """ +# SAMLConfiguration.objects.create( +# tenant_id=str(tenant_id), +# email_domain=domain, +# metadata_xml=metadata_xml, +# ) - return { - "email": f"user@{domain}", - "domain": domain, - "tenant_id": tenant_id, - } +# return { +# "email": f"user@{domain}", +# "domain": domain, +# "tenant_id": tenant_id, +# } -@pytest.fixture -def saml_sociallogin(users_fixture): - user = users_fixture[0] - user.email = "samlsso@acme.com" - extra_data = { - "firstName": ["Test"], - "lastName": ["User"], - "organization": ["Prowler"], - "userType": ["member"], - } +# @pytest.fixture +# def saml_sociallogin(users_fixture): +# user = users_fixture[0] +# user.email = "samlsso@acme.com" +# extra_data = { +# "firstName": ["Test"], +# "lastName": ["User"], +# "organization": ["Prowler"], +# "userType": ["member"], +# } - account = MagicMock() - account.provider = "saml" - account.extra_data = extra_data +# account = MagicMock() +# account.provider = "saml" +# account.extra_data = extra_data - sociallogin = MagicMock(spec=SocialLogin) - sociallogin.account = account - sociallogin.user = user +# sociallogin = MagicMock(spec=SocialLogin) +# sociallogin.account = account +# sociallogin.user = user - return sociallogin +# return sociallogin def get_authorization_header(access_token: str) -> dict: diff --git a/api/src/backend/tasks/jobs/export.py b/api/src/backend/tasks/jobs/export.py index 3525da2425..f4b389d8f0 100644 --- a/api/src/backend/tasks/jobs/export.py +++ b/api/src/backend/tasks/jobs/export.py @@ -31,6 +31,7 @@ from prowler.lib.outputs.compliance.iso27001.iso27001_gcp import GCPISO27001 from prowler.lib.outputs.compliance.iso27001.iso27001_kubernetes import ( KubernetesISO27001, ) +from prowler.lib.outputs.compliance.iso27001.iso27001_m365 import M365ISO27001 from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( @@ -90,6 +91,7 @@ COMPLIANCE_CLASS_MAP = { "m365": [ (lambda name: name.startswith("cis_"), M365CIS), (lambda name: name == "prowler_threatscore_m365", ProwlerThreatScoreM365), + (lambda name: name.startswith("iso27001_"), M365ISO27001), ], } diff --git a/api/src/backend/tasks/jobs/scan.py b/api/src/backend/tasks/jobs/scan.py index 684fbfd759..500d9201c8 100644 --- a/api/src/backend/tasks/jobs/scan.py +++ b/api/src/backend/tasks/jobs/scan.py @@ -26,7 +26,7 @@ from api.models import ( StateChoices, ) from api.models import StatusChoices as FindingStatus -from api.utils import initialize_prowler_provider +from api.utils import initialize_prowler_provider, return_prowler_provider from api.v1.serializers import ScanTaskSerializer from prowler.lib.outputs.finding import Finding as ProwlerFinding from prowler.lib.scan.scan import Scan as ProwlerScan @@ -149,7 +149,8 @@ def perform_prowler_scan( provider_instance.save() # If the provider is not connected, raise an exception outside the transaction. - # If raised within the transaction, the transaction will be rolled back and the provider will not be marked as not connected. + # If raised within the transaction, the transaction will be rolled back and the provider will not be marked + # as not connected. if exc: raise exc @@ -526,7 +527,7 @@ def create_compliance_requirements(tenant_id: str, scan_id: str): with rls_transaction(tenant_id): scan_instance = Scan.objects.get(pk=scan_id) provider_instance = scan_instance.provider - prowler_provider = initialize_prowler_provider(provider_instance) + prowler_provider = return_prowler_provider(provider_instance) # Get check status data by region from findings check_status_by_region = {} diff --git a/api/src/backend/tasks/tests/test_scan.py b/api/src/backend/tasks/tests/test_scan.py index e4ec0d4d41..9577e66a09 100644 --- a/api/src/backend/tasks/tests/test_scan.py +++ b/api/src/backend/tasks/tests/test_scan.py @@ -399,9 +399,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -427,9 +425,7 @@ class TestCreateComplianceRequirements: "us-east-1", "us-west-2", ] - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = { "cis_1.4_aws": { @@ -512,9 +508,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -557,9 +551,7 @@ class TestCreateComplianceRequirements: "us-east-1", "us-west-2", ] - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = { "test_compliance": { @@ -607,9 +599,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -641,9 +631,7 @@ class TestCreateComplianceRequirements: mock_prowler_provider_instance.get_regions.side_effect = AttributeError( "No get_regions method" ) - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = { "kubernetes_cis": { @@ -676,9 +664,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -704,9 +690,7 @@ class TestCreateComplianceRequirements: mock_prowler_provider_instance = MagicMock() mock_prowler_provider_instance.get_regions.return_value = ["us-east-1"] - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = { "cis_1.4_aws": { @@ -743,9 +727,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, ): tenant = tenants_fixture[0] scan = scans_fixture[0] @@ -759,7 +741,7 @@ class TestCreateComplianceRequirements: tenant_id = str(tenant.id) scan_id = str(scan.id) - mock_initialize_prowler_provider.side_effect = Exception( + mock_prowler_provider.side_effect = Exception( "Provider initialization failed" ) @@ -774,9 +756,7 @@ class TestCreateComplianceRequirements: ): with ( patch("api.db_utils.rls_transaction"), - patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + patch("tasks.jobs.scan.return_prowler_provider") as mock_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -800,9 +780,7 @@ class TestCreateComplianceRequirements: mock_prowler_provider_instance = MagicMock() mock_prowler_provider_instance.get_regions.return_value = ["us-east-1"] - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = {} @@ -821,8 +799,8 @@ class TestCreateComplianceRequirements: with ( patch("api.db_utils.rls_transaction"), patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + "tasks.jobs.scan.return_prowler_provider" + ) as mock_return_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -862,9 +840,7 @@ class TestCreateComplianceRequirements: mock_prowler_provider_instance = MagicMock() mock_prowler_provider_instance.get_regions.return_value = ["us-east-1"] - mock_initialize_prowler_provider.return_value = ( - mock_prowler_provider_instance - ) + mock_return_prowler_provider.return_value = mock_prowler_provider_instance mock_compliance_template.__getitem__.return_value = { "cis_1.4_aws": { @@ -898,8 +874,8 @@ class TestCreateComplianceRequirements: with ( patch("api.db_utils.rls_transaction"), patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + "tasks.jobs.scan.return_prowler_provider" + ) as mock_return_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -911,7 +887,6 @@ class TestCreateComplianceRequirements: ): tenant = tenants_fixture[0] scan = scans_fixture[0] - providers_fixture[0] mock_findings_filter.return_value = [] @@ -921,7 +896,7 @@ class TestCreateComplianceRequirements: "us-west-2", "eu-west-1", ] - mock_initialize_prowler_provider.return_value = mock_prowler_provider + mock_return_prowler_provider.return_value = mock_prowler_provider mock_compliance_template.__getitem__.return_value = { "test_compliance": { @@ -990,8 +965,8 @@ class TestCreateComplianceRequirements: with ( patch("api.db_utils.rls_transaction"), patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + "tasks.jobs.scan.return_prowler_provider" + ) as mock_return_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -1009,7 +984,7 @@ class TestCreateComplianceRequirements: mock_prowler_provider = MagicMock() mock_prowler_provider.get_regions.return_value = ["us-east-1", "us-west-2"] - mock_initialize_prowler_provider.return_value = mock_prowler_provider + mock_return_prowler_provider.return_value = mock_prowler_provider mock_compliance_template.__getitem__.return_value = { "test_compliance": { @@ -1077,8 +1052,8 @@ class TestCreateComplianceRequirements: with ( patch("api.db_utils.rls_transaction"), patch( - "tasks.jobs.scan.initialize_prowler_provider" - ) as mock_initialize_prowler_provider, + "tasks.jobs.scan.return_prowler_provider" + ) as mock_return_prowler_provider, patch( "tasks.jobs.scan.PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE" ) as mock_compliance_template, @@ -1090,13 +1065,12 @@ class TestCreateComplianceRequirements: ): tenant = tenants_fixture[0] scan = scans_fixture[0] - providers_fixture[0] mock_findings_filter.return_value = [] mock_prowler_provider = MagicMock() mock_prowler_provider.get_regions.return_value = ["us-east-1", "us-west-2"] - mock_initialize_prowler_provider.return_value = mock_prowler_provider + mock_return_prowler_provider.return_value = mock_prowler_provider mock_compliance_template.__getitem__.return_value = { "test_compliance": { diff --git a/docs/developer-guide/checks.md b/docs/developer-guide/checks.md index 400da7134d..e5dba4ba9c 100644 --- a/docs/developer-guide/checks.md +++ b/docs/developer-guide/checks.md @@ -212,34 +212,9 @@ Each check **must** populate the report with an unique identifier for the audite - Resource Name — `report.resource_name`. - The name of the Github resource. In the case of a repository, this is just the repository name. For full repository names use the resource `full_name`. -### Using the Audit Configuration +### Configurable Checks in Prowler -Prowler has a [configuration file](../tutorials/configuration_file.md) which is used to pass certain configuration values to the checks. For example: - -```python title="ec2_securitygroup_with_many_ingress_egress_rules.py" -class ec2_securitygroup_with_many_ingress_egress_rules(Check): - def execute(self): - findings = [] - - max_security_group_rules = ec2_client.audit_config.get( - "max_security_group_rules", 50 - ) - for security_group_arn, security_group in ec2_client.security_groups.items(): -``` - -We use the `audit_config` object to retrieve the value of `max_security_group_rules`, which is the default value of 50 if the configuration value is not present. - -The configuration file is located at [`prowler/config/config.yaml`](https://github.com/prowler-cloud/prowler/blob/master/prowler/config/config.yaml) and is used to pass certain configuration values to the checks. For example: - -```yaml title="config.yaml" - aws: - max_security_group_rules: 50 -``` - -This `audit_config` object is a Python dictionary that stores values read from the configuration file. It can be accessed by the check using the `audit_config` attribute of the service client. - -???+ note - Always use the `dictionary.get(value, default)` syntax to ensure a default value is set when the configuration value is not present. +See [Configurable Checks](./configurable-checks.md) for detailed information on making checks configurable using the `audit_config` object and configuration file. ## Metadata Structure for Prowler Checks diff --git a/docs/developer-guide/configurable-checks.md b/docs/developer-guide/configurable-checks.md new file mode 100644 index 0000000000..aad072ddd5 --- /dev/null +++ b/docs/developer-guide/configurable-checks.md @@ -0,0 +1,46 @@ +# Configurable Checks in Prowler + +Prowler empowers users to extend and adapt cloud security coverage by making checks configurable through the use of the `audit_config` object. This approach enables customization of checks to meet specific requirements through a configuration file. + +## Understanding the `audit_config` Object + +The `audit_config` object is a dictionary attached to each provider's service client (for example, `_client.audit_config`). This object loads configuration values from the main configuration file (`prowler/config/config.yaml`). Use `audit_config` to make checks flexible and user-configurable. + +## Using `audit_config` to Configure Checks + +Retrieve configuration values in a check by using the `.get()` method on the `audit_config` object. For example, to get the minimum number of Availability Zones for Lambda from the configuration file, use the following code. If the value is not set in the configuration, the check defaults to 2: + +```python +LAMBDA_MIN_AZS = awslambda_client.audit_config.get("lambda_min_azs", 2) +``` + +Always provide a default value in `.get()` to ensure the check works even if the configuration is missing the variable. + +### Example: Security Group Rule Limit + +```python title="ec2_securitygroup_with_many_ingress_egress_rules.py" +class ec2_securitygroup_with_many_ingress_egress_rules(Check): + def execute(self): + findings = [] + max_security_group_rules = ec2_client.audit_config.get( + "max_security_group_rules", 50 + ) + for security_group_arn, security_group in ec2_client.security_groups.items(): + # ... check logic ... +``` + +## Required File Updates for Configurable Variables + +When adding a new configurable check to Prowler, update the following files: + +- **Configuration File:** Add the new variable under the relevant provider or service section in `prowler/config/config.yaml`. + ```yaml + # aws.awslambda_function_vpc_multi_az + lambda_min_azs: 2 + ``` +- **Test Fixtures:** If tests depend on this configuration, add the variable to `tests/config/fixtures/config.yaml`. +- **Documentation:** Document the new variable in the list of configurable checks in `docs/tutorials/configuration_file.md`. + +For a complete list of checks that already support configuration, see the [Configuration File Tutorial](../tutorials/configuration_file.md). + +This approach ensures that checks are easily configurable, making Prowler highly adaptable to different environments and requirements. diff --git a/docs/getting-started/requirements.md b/docs/getting-started/requirements.md index 37b8ab5663..49677119b2 100644 --- a/docs/getting-started/requirements.md +++ b/docs/getting-started/requirements.md @@ -139,7 +139,7 @@ Prowler for M365 currently supports the following authentication types: ???+ warning For Prowler App only the Service Principal with User Credentials authentication method is supported. -### Service Principal authentication +### Service Principal authentication (recommended) Authentication flag: `--sp-env-auth` @@ -154,9 +154,11 @@ export AZURE_TENANT_ID="XXXXXXXXX" If you try to execute Prowler with the `--sp-env-auth` flag and those variables are empty or not exported, the execution is going to fail. Follow the instructions in the [Create Prowler Service Principal](../tutorials/microsoft365/getting-started-m365.md#create-the-service-principal-app) section to create a service principal. -With this credentials you will only be able to run the checks that work through MS Graph, this means that you won't run all the provider. If you want to scan all the checks from M365 you will need to use the recommended authentication method. +If you don't add the external API permissions described in the mentioned section above you will only be able to run the checks that work through MS Graph. This means that you won't run all the provider. -### Service Principal and User Credentials authentication (recommended) +If you want to scan all the checks from M365 you will need to use the recommended authentication method or add the external API permissions. + +### Service Principal and User Credentials authentication Authentication flag: `--env-auth` @@ -173,6 +175,7 @@ export M365_PASSWORD="examplepassword" These two new environment variables are **required** to execute the PowerShell modules needed to retrieve information from M365 services. Prowler uses Service Principal authentication to access Microsoft Graph and user credentials to authenticate to Microsoft PowerShell modules. - `M365_USER` should be your Microsoft account email using the **assigned domain in the tenant**. This means it must look like `example@YourCompany.onmicrosoft.com` or `example@YourCompany.com`, but it must be the exact domain assigned to that user in the tenant. + ???+ warning If the user is newly created, you need to sign in with that account first, as Microsoft will prompt you to change the password. If you don’t complete this step, user authentication will fail because Microsoft marks the initial password as expired. @@ -213,6 +216,8 @@ Prowler for M365 requires two types of permission scopes to be set (if you want - `Policy.Read.All`: Required for all services. - `SharePointTenantSettings.Read.All`: Required for SharePoint service. - `User.Read` (IMPORTANT: this must be set as **delegated**): Required for the sign-in. + - `Exchange.ManageAsApp` from external API `Office 365 Exchange Online`: Required for Exchange PowerShell module app authentication. You also need to assign the `Exchange Administrator` role to the app. + - `application_access` from external API `Skype and Teams Tenant Admin API`: Required for Teams PowerShell module app authentication. ???+ note You can replace `Directory.Read.All` with `Domain.Read.All` is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers. @@ -221,7 +226,8 @@ Prowler for M365 requires two types of permission scopes to be set (if you want -- **Powershell Modules Permissions**: These are set at the `M365_USER` level, so the user used to run Prowler must have one of the following roles: + +- **Powershell Modules Permissions** (if using user credentials): These are set at the `M365_USER` level, so the user used to run Prowler must have one of the following roles: - `Global Reader` (recommended): this allows you to read all roles needed. - `Exchange Administrator` and `Teams Administrator`: user needs both roles but with this [roles](https://learn.microsoft.com/en-us/exchange/permissions-exo/permissions-exo#microsoft-365-permissions-in-exchange-online) you can access to the same information as a Global Reader (since only read access is needed, Global Reader is recommended). @@ -439,6 +445,7 @@ The required modules are: - [ExchangeOnlineManagement](https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.6.0): Minimum version 3.6.0. Required for several checks across Exchange, Defender, and Purview. - [MicrosoftTeams](https://www.powershellgallery.com/packages/MicrosoftTeams/6.6.0): Minimum version 6.6.0. Required for all Teams checks. +- [MSAL.PS](https://www.powershellgallery.com/packages/MSAL.PS/4.32.0): Required for Exchange module via application authentication. ## GitHub ### Authentication @@ -455,3 +462,54 @@ The provided credentials must have the appropriate permissions to perform all th ???+ note GitHub App Credentials support less checks than other authentication methods. + +## Infrastructure as Code (IaC) + +Prowler's Infrastructure as Code (IaC) provider enables you to scan local infrastructure code for security and compliance issues using [Checkov](https://www.checkov.io/). This provider supports a wide range of IaC frameworks and requires no cloud authentication. + +### Authentication + +The IaC provider does not require any authentication or credentials since it scans local files directly. This makes it ideal for CI/CD pipelines and local development environments. + +### Supported Frameworks + +The IaC provider leverages Checkov to support multiple frameworks, including: + +- Terraform +- CloudFormation +- Kubernetes +- ARM (Azure Resource Manager) +- Serverless +- Dockerfile +- YAML/JSON (generic IaC) +- Bicep +- Helm +- GitHub Actions, GitLab CI, Bitbucket Pipelines, Azure Pipelines, CircleCI, Argo Workflows +- Ansible +- Kustomize +- OpenAPI +- SAST, SCA (Software Composition Analysis) + +### Usage + +To run Prowler with the IaC provider, use the `iac` flag. You can specify the directory to scan, frameworks to include, and paths to exclude. + +#### Basic Example + +```console +prowler iac --scan-path ./my-iac-directory +``` + +#### Specify Frameworks + +Scan only Terraform and Kubernetes files: + +```console +prowler iac --scan-path ./my-iac-directory --frameworks terraform kubernetes +``` + +#### Exclude Paths + +```console +prowler iac --scan-path ./my-iac-directory --exclude-path ./my-iac-directory/test,./my-iac-directory/examples +``` diff --git a/docs/index.md b/docs/index.md index ff0f3237a2..fb8e1d734e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -572,12 +572,12 @@ With M365 you need to specify which auth method is going to be used: ```console +# To use service principal authentication for MSGraph and PowerShell modules +prowler m365 --sp-env-auth + # To use both service principal (for MSGraph) and user credentials (for PowerShell modules) prowler m365 --env-auth -# To use service principal authentication -prowler m365 --sp-env-auth - # To use az cli authentication prowler m365 --az-cli-auth @@ -612,5 +612,27 @@ prowler github --github-app-id app_id --github-app-key app_key 2. `OAUTH_APP_TOKEN` 3. `GITHUB_APP_ID` and `GITHUB_APP_KEY` +#### Infrastructure as Code (IaC) + +Prowler's Infrastructure as Code (IaC) provider enables you to scan local infrastructure code for security and compliance issues using [Checkov](https://www.checkov.io/). This provider supports a wide range of IaC frameworks, allowing you to assess your code before deployment. + +```console +# Scan a directory for IaC files +prowler iac --scan-path ./my-iac-directory + +# Specify frameworks to scan (default: all) +prowler iac --scan-path ./my-iac-directory --frameworks terraform kubernetes + +# Exclude specific paths +prowler iac --scan-path ./my-iac-directory --exclude-path ./my-iac-directory/test,./my-iac-directory/examples +``` + +???+ note + - The IaC provider does not require cloud authentication + - It is ideal for CI/CD pipelines and local development environments + - For more details on supported frameworks and rules, see the [Checkov documentation](https://www.checkov.io/1.Welcome/Quick%20Start.html) + +See more details about IaC scanning in the [IaC Tutorial](tutorials/iac/getting-started-iac.md) section. + ## Prowler v2 Documentation For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md). diff --git a/docs/tutorials/iac/getting-started-iac.md b/docs/tutorials/iac/getting-started-iac.md new file mode 100644 index 0000000000..63e0e9f78c --- /dev/null +++ b/docs/tutorials/iac/getting-started-iac.md @@ -0,0 +1,67 @@ +# Getting Started with the IaC Provider + +Prowler's Infrastructure as Code (IaC) provider enables you to scan local infrastructure code for security and compliance issues using [Checkov](https://www.checkov.io/). This provider supports a wide range of IaC frameworks, allowing you to assess your code before deployment. + +## Supported Frameworks + +The IaC provider leverages Checkov to support multiple frameworks, including: + +- Terraform +- CloudFormation +- Kubernetes +- ARM (Azure Resource Manager) +- Serverless +- Dockerfile +- YAML/JSON (generic IaC) +- Bicep +- Helm +- GitHub Actions, GitLab CI, Bitbucket Pipelines, Azure Pipelines, CircleCI, Argo Workflows +- Ansible +- Kustomize +- OpenAPI +- SAST, SCA (Software Composition Analysis) + +## How It Works + +- The IaC provider scans your local directory (or a specified path) for supported IaC files. +- No cloud credentials or authentication are required. +- Mutelist logic is handled by Checkov, not Prowler. +- Results are output in the same formats as other Prowler providers (CSV, JSON, HTML, etc.). + +## Usage + +To run Prowler with the IaC provider, use the `iac` argument. You can specify the directory to scan, frameworks to include, and paths to exclude. + +### Basic Example + +```sh +prowler iac --scan-path ./my-iac-directory +``` + +### Specify Frameworks + +Scan only Terraform and Kubernetes files: + +```sh +prowler iac --scan-path ./my-iac-directory --frameworks terraform kubernetes +``` + +### Exclude Paths + +```sh +prowler iac --scan-path ./my-iac-directory --exclude-path ./my-iac-directory/test,./my-iac-directory/examples +``` + +## Output + +You can use the standard Prowler output options, for example: + +```sh +prowler iac --scan-path ./iac --output-formats csv json html +``` + +## Notes + +- The IaC provider does not require cloud authentication. +- It is ideal for CI/CD pipelines and local development environments. +- For more details on supported frameworks and rules, see the [Checkov documentation](https://www.checkov.io/1.Welcome/Quick%20Start.html). diff --git a/docs/tutorials/img/gcp-auth-methods.png b/docs/tutorials/img/gcp-auth-methods.png new file mode 100644 index 0000000000..dc8681396e Binary files /dev/null and b/docs/tutorials/img/gcp-auth-methods.png differ diff --git a/docs/tutorials/img/gcp-service-account-creds.png b/docs/tutorials/img/gcp-service-account-creds.png new file mode 100644 index 0000000000..af09776ab1 Binary files /dev/null and b/docs/tutorials/img/gcp-service-account-creds.png differ diff --git a/docs/tutorials/microsoft365/getting-started-m365.md b/docs/tutorials/microsoft365/getting-started-m365.md index 46184d84e2..ef9a50da30 100644 --- a/docs/tutorials/microsoft365/getting-started-m365.md +++ b/docs/tutorials/microsoft365/getting-started-m365.md @@ -30,7 +30,7 @@ Go to the Entra ID portal, then you can search for `Domain` or go to Identity > ![Custom Domain Names](./img/custom-domain-names.png) -Once you are there just select the domain you want to use. +Once you are there just select the domain you want to use as unique identifier for your M365 account in Prowler Cloud/App. --- @@ -92,7 +92,7 @@ With this done you will have all the needed keys, summarized in the following ta --- -### Grant required API permissions +### Grant required Graph API permissions Assign the following Microsoft Graph permissions: @@ -100,7 +100,7 @@ Assign the following Microsoft Graph permissions: - `Directory.Read.All`: Required for all services. - `Policy.Read.All`: Required for all services. - `SharePointTenantSettings.Read.All`: Required for SharePoint service. -- `User.Read` (IMPORTANT: this is set as **delegated**): Required for the sign-in. +- `User.Read` (IMPORTANT: this is set as **delegated**): Required for the sign-in only if using user authentication. ???+ note You can replace `Directory.Read.All` with `Domain.Read.All` is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers. @@ -128,18 +128,83 @@ Follow these steps to assign the permissions: ![Application Permissions](./img/app-permissions.png) +--- -4. Click `+ Add a permission` > `Microsoft Graph` > `Delegated permissions` - ![Add API Permission](./img/add-delegated-api-permission.png) +### Grant PowerShell modules permissions -5. Search and select: +The permissions you need to grant depends on whether you are using user credentials or service principal to authenticate to the M365 modules. + +???+ warning "Warning" + Make sure you add the correct set of permissions for the authentication method you are using. + + +#### If using application(service principal) authentication (Recommended) + +To grant the permissions for the PowerShell modules via application authentication, you need to add the necessary APIs to your app registration. + +???+ warning "Warning" + You need to have a license that allows you to use the APIs. + +1. Add Exchange API: + + - Search and select`Office 365 Exchange Online` API in **APIs my organization uses**. + + ![Office 365 Exchange Online API](./img/search-exchange-api.png) + + - Select `Exchange.ManageAsApp` permission and click on `Add permissions`. + + ![Exchange.ManageAsApp Permission](./img/exchange-permission.png) + + You also need to assign the `Exchange Administrator` role to the app. For that go to `Roles and administrators` and in the `Administrative roles` section click `here` to go to the directory level assignment: + + ![Roles and administrators](./img/here.png) + + Once in the directory level assignment, search for `Exchange Administrator` and click on it to open the assginments page of that role. + + ![Exchange Administrator Role](./img/exchange-administrator-role.png) + + Click on `Add assignments`, search for your app and click on `Assign`. + + You have to select it as `Active` and click on `Assign` to assign the role to the app. + + ![Assign Exchange Administrator Role](./img/assign-exchange-administrator-role.png) + +2. Add Teams API: + + - Search and select `Skype and Teams Tenant Admin API` API in **APIs my organization uses**. + + ![Skype and Teams Tenant Admin API](./img/search-skype-teams-tenant-admin-api.png) + + - Select `application_access` permission and click on `Add permissions`. + + ![application_access Permission](./img/teams-permission.png) + +3. Click on `Grant admin consent for ` to grant admin consent. + + ![Grant Admin Consent](./img/grant-external-api-permissions.png) + + The final result of permission assignment should be this: + + ![Final Permission Assignment](./img/final-permissions.png) + +--- + +#### If using user authentication + +This method is not recommended because it requires a user with MFA enabled and Microsoft will not allow MFA capable users to authenticate programmatically after 1st September 2025. See [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mandatory-multifactor-authentication?tabs=dotnet) for more information. + +???+ warning + Remember that if the user is newly created, you need to sign in with that account first, as Microsoft will prompt you to change the password. If you don’t complete this step, user authentication will fail because Microsoft marks the initial password as expired. + + +1. Search and select: - `User.Read` ![Permission Screenshots](./img/directory-permission-delegated.png) -6. After adding all the permissions, click on `Grant admin consent` +2. Click `Add permissions`, then **grant admin consent** ![Grant Admin Consent](./img/grant-admin-consent.png) @@ -147,37 +212,32 @@ Follow these steps to assign the permissions: ![Final Permission Assignment](./img/final-permissions-m365.png) ---- +3. Assign **required roles** to your **user** -### Assign required roles to your user + Assign one of the following roles to your User: -Assign one of the following roles to your User: + - `Global Reader` (recommended): this allows you to read all roles needed. + - `Exchange Administrator` and `Teams Administrator`: user needs both roles but with this [roles](https://learn.microsoft.com/en-us/exchange/permissions-exo/permissions-exo#microsoft-365-permissions-in-exchange-online) you can access to the same information as a Global Reader (here you only read so that's why we recomend that role). -- `Global Reader` (recommended): this allows you to read all roles needed. -- `Exchange Administrator` and `Teams Administrator`: user needs both roles but with this [roles](https://learn.microsoft.com/en-us/exchange/permissions-exo/permissions-exo#microsoft-365-permissions-in-exchange-online) you can access to the same information as a Global Reader (here you only read so that's why we recomend that role). + Follow these steps to assign the role: -Follow these steps to assign the role: + 1. Go to Users > All Users > Click on the email for the user you will use -1. Go to Users > All Users > Click on the email for the user you will use + ![User Overview](./img/user-info-page.png) - ![User Overview](./img/user-info-page.png) + 2. Click `Assigned Roles` -2. Click `Assigned Roles` + ![User Roles](./img/user-role-page.png) - ![User Roles](./img/user-role-page.png) + 3. Click on `Add assignments`, then search and select: -3. Click on `Add assignments`, then search and select: + - `Global Reader` This is the recommended, if you want to use the others just search for them - - `Global Reader` This is the recommended, if you want to use the others just search for them + ![Global Reader Screenshots](./img/global-reader.png) - ![Global Reader Screenshots](./img/global-reader.png) + 4. Click on next, then assign the role as `Active`, and click on `Assign` to grant admin consent -4. Click on next, then assign the role as `Active`, and click on `Assign` to grant admin consent - - ![Grant Admin Consent for Role](./img/grant-admin-consent-for-role.png) - -???+ warning - Remember that if the user is newly created, you need to sign in with that account first, as Microsoft will prompt you to change the password. If you don’t complete this step, user authentication will fail because Microsoft marks the initial password as expired. + ![Grant Admin Consent for Role](./img/grant-admin-consent-for-role.png) --- diff --git a/docs/tutorials/microsoft365/img/add-assginments.png b/docs/tutorials/microsoft365/img/add-assginments.png new file mode 100644 index 0000000000..af073a1d99 Binary files /dev/null and b/docs/tutorials/microsoft365/img/add-assginments.png differ diff --git a/docs/tutorials/microsoft365/img/assign-exchange-administrator-role.png b/docs/tutorials/microsoft365/img/assign-exchange-administrator-role.png new file mode 100644 index 0000000000..10e5752e8a Binary files /dev/null and b/docs/tutorials/microsoft365/img/assign-exchange-administrator-role.png differ diff --git a/docs/tutorials/microsoft365/img/exchange-administrator-role.png b/docs/tutorials/microsoft365/img/exchange-administrator-role.png new file mode 100644 index 0000000000..08a05dd2d9 Binary files /dev/null and b/docs/tutorials/microsoft365/img/exchange-administrator-role.png differ diff --git a/docs/tutorials/microsoft365/img/exchange-permission.png b/docs/tutorials/microsoft365/img/exchange-permission.png new file mode 100644 index 0000000000..29084600c2 Binary files /dev/null and b/docs/tutorials/microsoft365/img/exchange-permission.png differ diff --git a/docs/tutorials/microsoft365/img/final-permissions.png b/docs/tutorials/microsoft365/img/final-permissions.png new file mode 100644 index 0000000000..f3bed7981e Binary files /dev/null and b/docs/tutorials/microsoft365/img/final-permissions.png differ diff --git a/docs/tutorials/microsoft365/img/grant-external-api-permissions.png b/docs/tutorials/microsoft365/img/grant-external-api-permissions.png new file mode 100644 index 0000000000..0492e25dc3 Binary files /dev/null and b/docs/tutorials/microsoft365/img/grant-external-api-permissions.png differ diff --git a/docs/tutorials/microsoft365/img/here.png b/docs/tutorials/microsoft365/img/here.png new file mode 100644 index 0000000000..9c0f947c07 Binary files /dev/null and b/docs/tutorials/microsoft365/img/here.png differ diff --git a/docs/tutorials/microsoft365/img/search-exchange-api.png b/docs/tutorials/microsoft365/img/search-exchange-api.png new file mode 100644 index 0000000000..5c4514c168 Binary files /dev/null and b/docs/tutorials/microsoft365/img/search-exchange-api.png differ diff --git a/docs/tutorials/microsoft365/img/search-skype-teams-tenant-admin-api.png b/docs/tutorials/microsoft365/img/search-skype-teams-tenant-admin-api.png new file mode 100644 index 0000000000..0009f3c05c Binary files /dev/null and b/docs/tutorials/microsoft365/img/search-skype-teams-tenant-admin-api.png differ diff --git a/docs/tutorials/microsoft365/img/teams-permission.png b/docs/tutorials/microsoft365/img/teams-permission.png new file mode 100644 index 0000000000..734a24f184 Binary files /dev/null and b/docs/tutorials/microsoft365/img/teams-permission.png differ diff --git a/docs/tutorials/prowler-app-sso.md b/docs/tutorials/prowler-app-sso.md index a82971c8a9..0c3089291c 100644 --- a/docs/tutorials/prowler-app-sso.md +++ b/docs/tutorials/prowler-app-sso.md @@ -24,7 +24,7 @@ DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,prowler-api,mycompany.prowler To enable SAML support, you must provide a public certificate and private key to allow Prowler to sign SAML requests and validate responses. -### Why is this necessary? +### Why is this necessary? SAML relies on digital signatures to verify trust between the Identity Provider (IdP) and the Service Provider (SP). Prowler acts as the SP and must use a certificate to sign outbound authentication requests. @@ -121,7 +121,7 @@ Start ngrok on port 8080: ngrok http 8080 ``` -Then, copy the generated ngrok URL and include it in the ALLOWED_HOSTS setting. If you’re using the development environment, it usually defaults to *, but in some cases this may not work properly, like in my tests (investigate): +Then, copy the generated ngrok URL and include it in the ALLOWED_HOSTS setting. If you're using the development environment, it usually defaults to *, but in some cases this may not work properly, like in my tests (investigate): ``` ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["*"]) @@ -129,7 +129,7 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["*"]) ## 4. Configure the Identity Provider (IdP) -Start your environment and configure your IdP. You will need to download the IdP’s metadata XML file. +Start your environment and configure your IdP. You will need to download the IdP's metadata XML file. Your Assertion Consumer Service (ACS) URL must follow this format: @@ -147,7 +147,7 @@ The following fields are expected from the IdP: - userType (this is the name of the role the user should be assigned) -- companyName (this is filled automatically if the IdP includes an “organization” field) +- companyName (this is filled automatically if the IdP includes an "organization" field) These values are dynamic. If the values change in the IdP, they will be updated on the next login. @@ -171,7 +171,37 @@ curl --location 'http://localhost:8080/api/v1/saml-config' \ }' ``` -## 7. Start SAML Login Flow +## 7. SAML SSO Callback Configuration + +### Environment Variable Configuration + +The SAML authentication flow requires proper callback URL configuration to handle post-authentication redirects. Configure the following environment variables: + +#### `SAML_SSO_CALLBACK_URL` + +Specifies the callback endpoint that will be invoked upon successful SAML authentication completion. This URL directs users back to the web application interface. + +```env +SAML_SSO_CALLBACK_URL="${AUTH_URL}/api/auth/callback/saml" +``` + +#### `AUTH_URL` + +Defines the base URL of the web user interface application that serves as the authentication callback destination. + +```env +AUTH_URL="" +``` + +### Configuration Notes + +- The `SAML_SSO_CALLBACK_URL` dynamically references the `AUTH_URL` variable to construct the complete callback endpoint +- Ensure the `AUTH_URL` points to the correct web UI deployment (development, staging, or production) +- The callback endpoint `/api/auth/callback/saml` must be accessible and properly configured to handle SAML authentication responses +- Both environment variables are required for proper SAML SSO functionality +- Verify that the `NEXT_PUBLIC_API_BASE_URL` environment variable is properly configured to reference the correct API server base URL corresponding to your target deployment environment. This ensures proper routing of SAML callback requests to the appropriate backend services. + +## 8. Start SAML Login Flow Once everything is configured, start the SAML login process by visiting the following URL: @@ -181,6 +211,6 @@ https:///api/v1/accounts/saml//login/?email= + +#### **Step 4.3.1: Service Account Authentication** + +First of all, in the same project that you selected in the previous step, you need to create a service account and then generate a key in JSON format for it. For more information about this, you can follow the next Google Cloud documentation tutorials: + +- [Create a service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts) +- [Generate a key for a service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) + +GCP Service Account Credentials + +#### **Step 4.3.2: Application Default Credentials** 1. Run the following command in your terminal to authenticate with GCP: ```bash @@ -151,7 +170,7 @@ By default, the `kubeconfig` file is located at `~/.kube/config`. --- ### **Step 4.5: M365 Credentials** -For M365, Prowler App uses a service principal application with user and password to authenticate, for more information about the requirements needed for this provider check this [section](../getting-started/requirements.md#microsoft-365). Also, the detailed steps of how to add this provider to Prowler Cloud and start using it are [here](./microsoft365/getting-started-m365.md). +For M365, Prowler App uses a service principal application (before we already asked for user and password but now those parameters are optional, they will continue working only if you don't have MFA enabled) to authenticate, for more information about the requirements needed for this provider check this [section](../getting-started/requirements.md#microsoft-365). Also, the detailed steps of how to add this provider to Prowler Cloud and start using it are [here](./microsoft365/getting-started-m365.md). Prowler Cloud M365 Credentials diff --git a/mkdocs.yml b/mkdocs.yml index bf597ce494..17b44eb336 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,6 +106,8 @@ nav: - Getting Started: tutorials/microsoft365/getting-started-m365.md - Authentication: tutorials/microsoft365/authentication.md - Use of PowerShell: tutorials/microsoft365/use-of-powershell.md + - IaC: + - Getting Started: tutorials/iac/getting-started-iac.md - Developer Guide: - General Concepts: - Introduction: developer-guide/introduction.md @@ -128,6 +130,7 @@ nav: - Unit Tests: developer-guide/unit-testing.md - Integration Tests: developer-guide/integration-testing.md - Debugging: developer-guide/debugging.md + - Configurable Checks: developer-guide/configurable-checks.md - Security: security.md - Contact Us: contact.md - Troubleshooting: troubleshooting.md diff --git a/poetry.lock b/poetry.lock index bd0311d754..ababbb8ef8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -41,97 +41,102 @@ files = [ [[package]] name = "aiohttp" -version = "3.11.18" +version = "3.12.13" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"}, - {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"}, - {file = "aiohttp-3.11.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5691dc38750fcb96a33ceef89642f139aa315c8a193bbd42a0c33476fd4a1609"}, - {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554c918ec43f8480b47a5ca758e10e793bd7410b83701676a4782672d670da55"}, - {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a4076a2b3ba5b004b8cffca6afe18a3b2c5c9ef679b4d1e9859cf76295f8d4f"}, - {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:767a97e6900edd11c762be96d82d13a1d7c4fc4b329f054e88b57cdc21fded94"}, - {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ddc9337a0fb0e727785ad4f41163cc314376e82b31846d3835673786420ef1"}, - {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f414f37b244f2a97e79b98d48c5ff0789a0b4b4609b17d64fa81771ad780e415"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb239f47328581e2ec7744ab5911f97afb10752332a6dd3d98e14e429e1a9e7"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2c50bad73ed629cc326cc0f75aed8ecfb013f88c5af116f33df556ed47143eb"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8d8f20c39d3fa84d1c28cdb97f3111387e48209e224408e75f29c6f8e0861d"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:106032eaf9e62fd6bc6578c8b9e6dc4f5ed9a5c1c7fb2231010a1b4304393421"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b491e42183e8fcc9901d8dcd8ae644ff785590f1727f76ca86e731c61bfe6643"}, - {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad8c745ff9460a16b710e58e06a9dec11ebc0d8f4dd82091cefb579844d69868"}, - {file = "aiohttp-3.11.18-cp310-cp310-win32.whl", hash = "sha256:8e57da93e24303a883146510a434f0faf2f1e7e659f3041abc4e3fb3f6702a9f"}, - {file = "aiohttp-3.11.18-cp310-cp310-win_amd64.whl", hash = "sha256:cc93a4121d87d9f12739fc8fab0a95f78444e571ed63e40bfc78cd5abe700ac9"}, - {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9"}, - {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b"}, - {file = "aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66"}, - {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756"}, - {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717"}, - {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4"}, - {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f"}, - {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f"}, - {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd"}, - {file = "aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d"}, - {file = "aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6"}, - {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2"}, - {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508"}, - {file = "aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e"}, - {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f"}, - {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f"}, - {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec"}, - {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6"}, - {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1"}, - {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea"}, - {file = "aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8"}, - {file = "aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8"}, - {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811"}, - {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804"}, - {file = "aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd"}, - {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c"}, - {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118"}, - {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1"}, - {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000"}, - {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261"}, - {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7"}, - {file = "aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78"}, - {file = "aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01"}, - {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:469ac32375d9a716da49817cd26f1916ec787fc82b151c1c832f58420e6d3533"}, - {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cec21dd68924179258ae14af9f5418c1ebdbba60b98c667815891293902e5e0"}, - {file = "aiohttp-3.11.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b426495fb9140e75719b3ae70a5e8dd3a79def0ae3c6c27e012fc59f16544a4a"}, - {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2f41203e2808616292db5d7170cccf0c9f9c982d02544443c7eb0296e8b0c7"}, - {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc0ae0a5e9939e423e065a3e5b00b24b8379f1db46046d7ab71753dfc7dd0e1"}, - {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7cdd3f7d1df43200e1c80f1aed86bb36033bf65e3c7cf46a2b97a253ef8798"}, - {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5199be2a2f01ffdfa8c3a6f5981205242986b9e63eb8ae03fd18f736e4840721"}, - {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ccec9e72660b10f8e283e91aa0295975c7bd85c204011d9f5eb69310555cf30"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1596ebf17e42e293cbacc7a24c3e0dc0f8f755b40aff0402cb74c1ff6baec1d3"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:eab7b040a8a873020113ba814b7db7fa935235e4cbaf8f3da17671baa1024863"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5d61df4a05476ff891cff0030329fee4088d40e4dc9b013fac01bc3c745542c2"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46533e6792e1410f9801d09fd40cbbff3f3518d1b501d6c3c5b218f427f6ff08"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c1b90407ced992331dd6d4f1355819ea1c274cc1ee4d5b7046c6761f9ec11829"}, - {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a2fd04ae4971b914e54fe459dd7edbbd3f2ba875d69e057d5e3c8e8cac094935"}, - {file = "aiohttp-3.11.18-cp39-cp39-win32.whl", hash = "sha256:b2f317d1678002eee6fe85670039fb34a757972284614638f82b903a03feacdc"}, - {file = "aiohttp-3.11.18-cp39-cp39-win_amd64.whl", hash = "sha256:5e7007b8d1d09bce37b54111f593d173691c530b80f27c6493b928dabed9e6ef"}, - {file = "aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6"}, + {file = "aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad"}, + {file = "aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3"}, + {file = "aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd"}, + {file = "aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5"}, + {file = "aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf"}, + {file = "aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3"}, + {file = "aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd"}, + {file = "aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c"}, + {file = "aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8"}, + {file = "aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122"}, + {file = "aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce"}, ] [package.dependencies] -aiohappyeyeballs = ">=2.3.0" +aiohappyeyeballs = ">=2.5.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" @@ -141,7 +146,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiomultiprocess" @@ -272,18 +277,18 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "astroid" -version = "3.3.9" +version = "3.3.10" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, - {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, + {file = "astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb"}, + {file = "astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce"}, ] [package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [[package]] name = "async-timeout" @@ -320,14 +325,14 @@ tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" a [[package]] name = "authlib" -version = "1.5.2" +version = "1.6.0" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "authlib-1.5.2-py2.py3-none-any.whl", hash = "sha256:8804dd4402ac5e4a0435ac49e0b6e19e395357cfa632a3f624dcb4f6df13b4b1"}, - {file = "authlib-1.5.2.tar.gz", hash = "sha256:fe85ec7e50c5f86f1e2603518bb3b4f632985eb4a355e52256530790e326c512"}, + {file = "authlib-1.6.0-py2.py3-none-any.whl", hash = "sha256:91685589498f79e8655e8a8947431ad6288831d643f11c55c2143ffcc738048d"}, + {file = "authlib-1.6.0.tar.gz", hash = "sha256:4367d32031b7af175ad3a323d571dc7257b7099d55978087ceae4a0d88cd3210"}, ] [package.dependencies] @@ -335,24 +340,24 @@ cryptography = "*" [[package]] name = "aws-sam-translator" -version = "1.97.0" +version = "1.99.0" description = "AWS SAM Translator is a library that transform SAM templates into AWS CloudFormation templates" optional = false python-versions = "!=4.0,<=4.0,>=3.8" groups = ["dev"] files = [ - {file = "aws_sam_translator-1.97.0-py3-none-any.whl", hash = "sha256:305701ab49eb546fd720b3682e99cadcd43539f4ddb8395ea03c90c9e14d3325"}, - {file = "aws_sam_translator-1.97.0.tar.gz", hash = "sha256:6f7ec94de0a9b220dd1f1a0bf7e2df95dd44a85592301ee830744da2f209b7e6"}, + {file = "aws_sam_translator-1.99.0-py3-none-any.whl", hash = "sha256:b1997e09da876342655eb568e66098280ffd137213009f0136b57f4e7694c98c"}, + {file = "aws_sam_translator-1.99.0.tar.gz", hash = "sha256:be326054a7ee2f535fcd914db85e5d50bdf4054313c14888af69b6de3187cdf8"}, ] [package.dependencies] -boto3 = ">=1.19.5,<2.dev0" +boto3 = ">=1.34.0,<2.0.0" jsonschema = ">=3.2,<5" pydantic = ">=1.8,<1.10.15 || >1.10.15,<1.10.17 || >1.10.17,<3" typing_extensions = ">=4.4" [package.extras] -dev = ["black (==24.3.0)", "boto3 (>=1.23,<2)", "boto3-stubs[appconfig,serverlessrepo] (>=1.19.5,<2.dev0)", "coverage (>=5.3,<8)", "dateparser (>=1.1,<2.0)", "mypy (>=1.3.0,<1.4.0)", "parameterized (>=0.7,<1.0)", "pytest (>=6.2,<8)", "pytest-cov (>=2.10,<5)", "pytest-env (>=0.6,<1)", "pytest-rerunfailures (>=9.1,<12)", "pytest-xdist (>=2.5,<4)", "pyyaml (>=6.0,<7.0)", "requests (>=2.28,<3.0)", "ruamel.yaml (==0.17.21)", "ruff (>=0.4.5,<0.5.0)", "tenacity (>=8.0,<9.0)", "types-PyYAML (>=6.0,<7.0)", "types-jsonschema (>=3.2,<4.0)"] +dev = ["black (==24.3.0)", "boto3 (>=1.34.0,<2.0.0)", "boto3-stubs[appconfig,serverlessrepo] (>=1.34.0,<2.0.0)", "cloudformation-cli (>=0.2.39,<0.3.0)", "coverage (>=5.3,<8)", "dateparser (>=1.1,<2.0)", "mypy (>=1.3.0,<1.4.0)", "parameterized (>=0.7,<1.0)", "pytest (>=6.2,<8)", "pytest-cov (>=2.10,<5)", "pytest-env (>=0.6,<1)", "pytest-rerunfailures (>=9.1,<12)", "pytest-xdist (>=2.5,<4)", "pyyaml (>=6.0,<7.0)", "requests (>=2.28,<3.0)", "ruamel.yaml (==0.17.21)", "ruff (>=0.4.5,<0.5.0)", "tenacity (>=9.0,<10.0)", "types-PyYAML (>=6.0,<7.0)", "types-jsonschema (>=3.2,<4.0)"] [[package]] name = "aws-xray-sdk" @@ -396,14 +401,14 @@ files = [ [[package]] name = "azure-core" -version = "1.33.0" +version = "1.34.0" description = "Microsoft Azure Core Library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "azure_core-1.33.0-py3-none-any.whl", hash = "sha256:9b5b6d0223a1d38c37500e6971118c1e0f13f54951e6893968b38910bc9cda8f"}, - {file = "azure_core-1.33.0.tar.gz", hash = "sha256:f367aa07b5e3005fec2c1e184b882b0b039910733907d001c20fb08ebb8c0eb9"}, + {file = "azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6"}, + {file = "azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece"}, ] [package.dependencies] @@ -1084,14 +1089,14 @@ files = [ [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main", "dev", "docs"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] [[package]] @@ -1189,14 +1194,14 @@ files = [ [[package]] name = "cfn-lint" -version = "1.34.1" +version = "1.36.1" description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "cfn_lint-1.34.1-py3-none-any.whl", hash = "sha256:2c21a908fa5d9b0c1caac2081c10261eaae7fce88364e4edc607717892f36d68"}, - {file = "cfn_lint-1.34.1.tar.gz", hash = "sha256:58f4352c370a710b72b389eda0acd6762e62f926e534d2eb1609d5326ded4bed"}, + {file = "cfn_lint-1.36.1-py3-none-any.whl", hash = "sha256:1f5eae322cabc9b73a717b83bf3a73709e2193fbf7fd399339e016a0838de7a3"}, + {file = "cfn_lint-1.36.1.tar.gz", hash = "sha256:ccb85398fb1d8d8d87edafe207ab6a133c4ab9aefd3ff93591b6ad8c3ccab291"}, ] [package.dependencies] @@ -1216,104 +1221,104 @@ sarif = ["jschema_to_python (>=1.2.3,<1.3.0)", "sarif-om (>=1.0.4,<1.1.0)"] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev", "docs"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] [[package]] @@ -1384,6 +1389,7 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["main", "dev", "docs"] +markers = "python_version < \"3.10\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -1392,6 +1398,22 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev", "docs"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "click-option-group" version = "0.5.7" @@ -1415,14 +1437,14 @@ test-cov = ["pytest", "pytest-cov"] [[package]] name = "click-plugins" -version = "1.1.1" +version = "1.1.1.2" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, - {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, + {file = "click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6"}, + {file = "click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261"}, ] [package.dependencies] @@ -1921,14 +1943,14 @@ files = [ [[package]] name = "durationpy" -version = "0.9" +version = "0.10" description = "Module for converting between datetime.timedelta and Go's Duration strings." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38"}, - {file = "durationpy-0.9.tar.gz", hash = "sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a"}, + {file = "durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286"}, + {file = "durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba"}, ] [[package]] @@ -1949,17 +1971,20 @@ idna = ">=2.0.0" [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] markers = "python_version <= \"3.10\"" files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] @@ -2053,116 +2078,116 @@ python-dateutil = ">=2.7" [[package]] name = "frozenlist" -version = "1.6.0" +version = "1.7.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, - {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, - {file = "frozenlist-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9799257237d0479736e2b4c01ff26b5c7f7694ac9692a426cb717f3dc02fff9b"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a7bb0fe1f7a70fb5c6f497dc32619db7d2cdd53164af30ade2f34673f8b1fc"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:36d2fc099229f1e4237f563b2a3e0ff7ccebc3999f729067ce4e64a97a7f2869"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f27a9f9a86dcf00708be82359db8de86b80d029814e6693259befe82bb58a106"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ecee69073312951244f11b8627e3700ec2bfe07ed24e3a685a5979f0412d24"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2c7d5aa19714b1b01a0f515d078a629e445e667b9da869a3cd0e6fe7dec78bd"}, - {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bbd454f0fb23b51cadc9bdba616c9678e4114b6f9fa372d462ff2ed9323ec8"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7daa508e75613809c7a57136dec4871a21bca3080b3a8fc347c50b187df4f00c"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89ffdb799154fd4d7b85c56d5fa9d9ad48946619e0eb95755723fffa11022d75"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:920b6bd77d209931e4c263223381d63f76828bec574440f29eb497cf3394c249"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d3ceb265249fb401702fce3792e6b44c1166b9319737d21495d3611028d95769"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52021b528f1571f98a7d4258c58aa8d4b1a96d4f01d00d51f1089f2e0323cb02"}, - {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f2ca7810b809ed0f1917293050163c7654cefc57a49f337d5cd9de717b8fad3"}, - {file = "frozenlist-1.6.0-cp310-cp310-win32.whl", hash = "sha256:0e6f8653acb82e15e5443dba415fb62a8732b68fe09936bb6d388c725b57f812"}, - {file = "frozenlist-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a39819a5a3e84304cd286e3dc62a549fe60985415851b3337b6f5cc91907f1"}, - {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d"}, - {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0"}, - {file = "frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff"}, - {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1"}, - {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e"}, - {file = "frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860"}, - {file = "frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603"}, - {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1"}, - {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29"}, - {file = "frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590"}, - {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046"}, - {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770"}, - {file = "frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc"}, - {file = "frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878"}, - {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e"}, - {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117"}, - {file = "frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f"}, - {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188"}, - {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e"}, - {file = "frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4"}, - {file = "frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd"}, - {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64"}, - {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91"}, - {file = "frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e"}, - {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911"}, - {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497"}, - {file = "frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f"}, - {file = "frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348"}, - {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:536a1236065c29980c15c7229fbb830dedf809708c10e159b8136534233545f0"}, - {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ed5e3a4462ff25ca84fb09e0fada8ea267df98a450340ead4c91b44857267d70"}, - {file = "frozenlist-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e19c0fc9f4f030fcae43b4cdec9e8ab83ffe30ec10c79a4a43a04d1af6c5e1ad"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c608f833897501dac548585312d73a7dca028bf3b8688f0d712b7acfaf7fb3"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0dbae96c225d584f834b8d3cc688825911960f003a85cb0fd20b6e5512468c42"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625170a91dd7261a1d1c2a0c1a353c9e55d21cd67d0852185a5fef86587e6f5f"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1db8b2fc7ee8a940b547a14c10e56560ad3ea6499dc6875c354e2335812f739d"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4da6fc43048b648275a220e3a61c33b7fff65d11bdd6dcb9d9c145ff708b804c"}, - {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef8e7e8f2f3820c5f175d70fdd199b79e417acf6c72c5d0aa8f63c9f721646f"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa733d123cc78245e9bb15f29b44ed9e5780dc6867cfc4e544717b91f980af3b"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ba7f8d97152b61f22d7f59491a781ba9b177dd9f318486c5fbc52cde2db12189"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:56a0b8dd6d0d3d971c91f1df75e824986667ccce91e20dca2023683814344791"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5c9e89bf19ca148efcc9e3c44fd4c09d5af85c8a7dd3dbd0da1cb83425ef4983"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1330f0a4376587face7637dfd245380a57fe21ae8f9d360c1c2ef8746c4195fa"}, - {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2187248203b59625566cac53572ec8c2647a140ee2738b4e36772930377a533c"}, - {file = "frozenlist-1.6.0-cp39-cp39-win32.whl", hash = "sha256:2b8cf4cfea847d6c12af06091561a89740f1f67f331c3fa8623391905e878530"}, - {file = "frozenlist-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:1255d5d64328c5a0d066ecb0f02034d086537925f1f04b50b1ae60d37afbf572"}, - {file = "frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191"}, - {file = "frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] [[package]] @@ -2219,14 +2244,14 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "google-api-core" -version = "2.24.2" +version = "2.25.1" description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, - {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, + {file = "google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7"}, + {file = "google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8"}, ] [package.dependencies] @@ -2237,10 +2262,10 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 requests = ">=2.18.0,<3.0.0" [package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\""] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] [[package]] name = "google-api-python-client" @@ -2263,14 +2288,14 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.39.0" +version = "2.40.3" description = "Google Authentication Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_auth-2.39.0-py2.py3-none-any.whl", hash = "sha256:0150b6711e97fb9f52fe599f55648950cc4540015565d8fbb31be2ad6e1548a2"}, - {file = "google_auth-2.39.0.tar.gz", hash = "sha256:73222d43cdc35a3aeacbfdcaf73142a97839f10de930550d89ebfe1d0a00cde7"}, + {file = "google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca"}, + {file = "google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77"}, ] [package.dependencies] @@ -2468,14 +2493,14 @@ files = [ [[package]] name = "iamdata" -version = "0.1.202505091" +version = "0.1.202506261" description = "IAM data for AWS actions, resources, and conditions based on IAM policy documents. Checked for updates daily." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "iamdata-0.1.202505091-py3-none-any.whl", hash = "sha256:006536cb6ee32c0567e04ba7582586596c67b599ab0ed10166efae4153acf120"}, - {file = "iamdata-0.1.202505091.tar.gz", hash = "sha256:b8538dde82282e89c5ca6e98662008e3c50e21252e3a3a532f0c1b7387488036"}, + {file = "iamdata-0.1.202506261-py3-none-any.whl", hash = "sha256:800817bce22c50efdbc5556b4849dcef89b8d42a56ee0b175fd7ac56e397f2d0"}, + {file = "iamdata-0.1.202506261.tar.gz", hash = "sha256:22e23aebb3fa103951a60f1025fe201f3501327aa522a1a7ce33c3873473da28"}, ] [[package]] @@ -2613,14 +2638,14 @@ files = [ [[package]] name = "joserfc" -version = "1.0.4" +version = "1.1.0" description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "joserfc-1.0.4-py3-none-any.whl", hash = "sha256:ecf3a5999f89d3a663485ab7c4f633541586d6f44e664ee760197299f39ed51b"}, - {file = "joserfc-1.0.4.tar.gz", hash = "sha256:dc3fc216cfcfc952d4c0d4b06c759a04711af0b667e5973adc47dbb1ba784127"}, + {file = "joserfc-1.1.0-py3-none-any.whl", hash = "sha256:9493512cfffb9bc3001e8f609fe0eb7e95b71f3d3b374ede93de94b4b6b520f5"}, + {file = "joserfc-1.1.0.tar.gz", hash = "sha256:a8f3442b04c233f742f7acde0d0dcd926414e9542a6337096b2b4e5f435f36c1"}, ] [package.dependencies] @@ -2712,14 +2737,14 @@ requests = ">=2.31.0,<3.0.0" [[package]] name = "jsonschema-specifications" -version = "2024.10.1" +version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, - {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, ] [package.dependencies] @@ -2812,33 +2837,32 @@ files = [ [[package]] name = "license-expression" -version = "30.4.1" +version = "30.4.3" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee"}, - {file = "license_expression-30.4.1.tar.gz", hash = "sha256:9f02105f9e0fcecba6a85dfbbed7d94ea1c3a70cf23ddbfb5adf3438a6f6fce0"}, + {file = "license_expression-30.4.3-py3-none-any.whl", hash = "sha256:fd3db53418133e0eef917606623bc125fbad3d1225ba8d23950999ee87c99280"}, + {file = "license_expression-30.4.3.tar.gz", hash = "sha256:49f439fea91c4d1a642f9f2902b58db1d42396c5e331045f41ce50df9b40b1f2"}, ] [package.dependencies] "boolean.py" = ">=4.0" [package.extras] -docs = ["Sphinx (>=5.0.2)", "doc8 (>=0.11.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-reredirects (>=0.1.2)", "sphinx-rtd-dark-mode (>=1.3.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-apidoc (>=0.4.0)"] -testing = ["black", "isort", "pytest (>=6,!=7.0.0)", "pytest-xdist (>=2)", "twine"] +dev = ["Sphinx (>=5.0.2)", "doc8 (>=0.11.2)", "pytest (>=7.0.1)", "pytest-xdist (>=2)", "ruff", "sphinx-autobuild", "sphinx-copybutton", "sphinx-reredirects (>=0.1.2)", "sphinx-rtd-dark-mode (>=1.3.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-apidoc (>=0.4.0)", "twine"] [[package]] name = "markdown" -version = "3.8" +version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" groups = ["main", "docs"] files = [ - {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, - {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, + {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, + {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, ] [package.dependencies] @@ -3321,14 +3345,14 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.32.0" +version = "1.32.3" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7"}, - {file = "msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2"}, + {file = "msal-1.32.3-py3-none-any.whl", hash = "sha256:b2798db57760b1961b142f027ffb7c8169536bf77316e99a0df5c4aaebb11569"}, + {file = "msal-1.32.3.tar.gz", hash = "sha256:5eea038689c78a5a70ca8ecbe1245458b55a857bd096efb6989c69ba15985d35"}, ] [package.dependencies] @@ -3359,14 +3383,14 @@ portalocker = ["portalocker (>=1.4,<4)"] [[package]] name = "msgraph-core" -version = "1.3.3" +version = "1.3.4" description = "Core component of the Microsoft Graph Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msgraph_core-1.3.3-py3-none-any.whl", hash = "sha256:9dbbc0c88e174c1d5da1c17d286965d6b26ebaf24996c7af64a39e2069006cf6"}, - {file = "msgraph_core-1.3.3.tar.gz", hash = "sha256:a3226b08b4cf9b6dbb16b868be21d5f82d8ee514ae8e46d9f0cad896159ef8d3"}, + {file = "msgraph_core-1.3.4-py3-none-any.whl", hash = "sha256:bce306ce193f8f96b3dd32af8b6ffd28eaaaf0db7560bfac8a334158e616c821"}, + {file = "msgraph_core-1.3.4.tar.gz", hash = "sha256:27e0d95344d29b6f05cf6ceb24a28bac363f19f18d6efaed7af6457e9fe4d6a9"}, ] [package.dependencies] @@ -3425,116 +3449,110 @@ async = ["aiodns ; python_version >= \"3.5\"", "aiohttp (>=3.0) ; python_version [[package]] name = "multidict" -version = "6.4.3" +version = "6.5.1" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, - {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, - {file = "multidict-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a7be07e5df178430621c716a63151165684d3e9958f2bbfcb644246162007ab7"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b128dbf1c939674a50dd0b28f12c244d90e5015e751a4f339a96c54f7275e291"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9cb19dfd83d35b6ff24a4022376ea6e45a2beba8ef3f0836b8a4b288b6ad685"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cf62f8e447ea2c1395afa289b332e49e13d07435369b6f4e41f887db65b40bf"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:909f7d43ff8f13d1adccb6a397094adc369d4da794407f8dd592c51cf0eae4b1"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bb8f8302fbc7122033df959e25777b0b7659b1fd6bcb9cb6bed76b5de67afef"}, - {file = "multidict-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b79471b4f21169ea25ebc37ed6f058040c578e50ade532e2066562597b8a9"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a7bd27f7ab3204f16967a6f899b3e8e9eb3362c0ab91f2ee659e0345445e0078"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:99592bd3162e9c664671fd14e578a33bfdba487ea64bcb41d281286d3c870ad7"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a62d78a1c9072949018cdb05d3c533924ef8ac9bcb06cbf96f6d14772c5cd451"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ccdde001578347e877ca4f629450973c510e88e8865d5aefbcb89b852ccc666"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:eccb67b0e78aa2e38a04c5ecc13bab325a43e5159a181a9d1a6723db913cbb3c"}, - {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b6fcf6054fc4114a27aa865f8840ef3d675f9316e81868e0ad5866184a6cba5"}, - {file = "multidict-6.4.3-cp310-cp310-win32.whl", hash = "sha256:f92c7f62d59373cd93bc9969d2da9b4b21f78283b1379ba012f7ee8127b3152e"}, - {file = "multidict-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:b57e28dbc031d13916b946719f213c494a517b442d7b48b29443e79610acd887"}, - {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd"}, - {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8"}, - {file = "multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7"}, - {file = "multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618"}, - {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7"}, - {file = "multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378"}, - {file = "multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589"}, - {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676"}, - {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1"}, - {file = "multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c"}, - {file = "multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713"}, - {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a"}, - {file = "multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124"}, - {file = "multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db"}, - {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474"}, - {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd"}, - {file = "multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0"}, - {file = "multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9"}, - {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8"}, - {file = "multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3"}, - {file = "multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5"}, - {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6"}, - {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c"}, - {file = "multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02"}, - {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4"}, - {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4"}, - {file = "multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5"}, - {file = "multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208"}, - {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5427a2679e95a642b7f8b0f761e660c845c8e6fe3141cddd6b62005bd133fc21"}, - {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24a8caa26521b9ad09732972927d7b45b66453e6ebd91a3c6a46d811eeb7349b"}, - {file = "multidict-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6b5a272bc7c36a2cd1b56ddc6bff02e9ce499f9f14ee4a45c45434ef083f2459"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf74dc5e212b8c75165b435c43eb0d5e81b6b300a938a4eb82827119115e840"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9f35de41aec4b323c71f54b0ca461ebf694fb48bec62f65221f52e0017955b39"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae93e0ff43b6f6892999af64097b18561691ffd835e21a8348a441e256592e1f"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3929269e9d7eff905d6971d8b8c85e7dbc72c18fb99c8eae6fe0a152f2e343"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6214fe1750adc2a1b801a199d64b5a67671bf76ebf24c730b157846d0e90d2"}, - {file = "multidict-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d79cf5c0c6284e90f72123f4a3e4add52d6c6ebb4a9054e88df15b8d08444c6"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2427370f4a255262928cd14533a70d9738dfacadb7563bc3b7f704cc2360fc4e"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:fbd8d737867912b6c5f99f56782b8cb81f978a97b4437a1c476de90a3e41c9a1"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0ee1bf613c448997f73fc4efb4ecebebb1c02268028dd4f11f011f02300cf1e8"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578568c4ba5f2b8abd956baf8b23790dbfdc953e87d5b110bce343b4a54fc9e7"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a059ad6b80de5b84b9fa02a39400319e62edd39d210b4e4f8c4f1243bdac4752"}, - {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dd53893675b729a965088aaadd6a1f326a72b83742b056c1065bdd2e2a42b4df"}, - {file = "multidict-6.4.3-cp39-cp39-win32.whl", hash = "sha256:abcfed2c4c139f25c2355e180bcc077a7cae91eefbb8b3927bb3f836c9586f1f"}, - {file = "multidict-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:b1b389ae17296dd739015d5ddb222ee99fd66adeae910de21ac950e00979d897"}, - {file = "multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9"}, - {file = "multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec"}, + {file = "multidict-6.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7b7d75cb5b90fa55700edbbdca12cd31f6b19c919e98712933c7a1c3c6c71b73"}, + {file = "multidict-6.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad32e43e028276612bf5bab762677e7d131d2df00106b53de2efb2b8a28d5bce"}, + {file = "multidict-6.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0499cbc67c1b02ba333781798560c5b1e7cd03e9273b678c92c6de1b1657fac9"}, + {file = "multidict-6.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c78fc6bc1dd7a139dab7ee9046f79a2082dce9360e3899b762615d564e2e857"}, + {file = "multidict-6.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f369d6619b24da4df4a02455fea8641fe8324fc0100a3e0dcebc5bf55fa903f3"}, + {file = "multidict-6.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:719af50a44ce9cf9ab15d829bf8cf146de486b4816284c17c3c9b9c9735abb8f"}, + {file = "multidict-6.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:199a0a9b3de8bbeb6881460d32b857dc7abec94448aeb6d607c336628c53580a"}, + {file = "multidict-6.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe09318a28b00c6f43180d0d889df1535e98fb2d93d25955d46945f8d5410d87"}, + {file = "multidict-6.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab94923ae54385ed480e4ab19f10269ee60f3eabd0b35e2a5d1ba6dbf3b0cc27"}, + {file = "multidict-6.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:de2b253a3a90e1fa55eef5f9b3146bb5c722bd3400747112c9963404a2f5b9cf"}, + {file = "multidict-6.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b3bd88c1bc1f749db6a1e1f01696c3498bc25596136eceebb45766d24a320b27"}, + {file = "multidict-6.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0ce8f0ea49e8f54203f7d80e083a7aa017dbcb6f2d76d674273e25144c8aa3d7"}, + {file = "multidict-6.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc62c8ac1b73ec704ed1a05be0267358fd5c99d1952f30448db1637336635cf8"}, + {file = "multidict-6.5.1-cp310-cp310-win32.whl", hash = "sha256:7a365a579fb3e067943d0278474e14c2244c252f460b401ccbf49f962e7b70fa"}, + {file = "multidict-6.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:4b299a2ffed33ad0733a9d47805b538d59465f8439bfea44df542cfb285c4db2"}, + {file = "multidict-6.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:ed98ac527278372251fbc8f5c6c41bdf64ded1db0e6e86f9b9622744306060f6"}, + {file = "multidict-6.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:153d7ff738d9b67b94418b112dc5a662d89d2fc26846a9e942f039089048c804"}, + {file = "multidict-6.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d784c0a1974f00d87f632d0fb6b1078baf7e15d2d2d1408af92f54d120f136e"}, + {file = "multidict-6.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dedf667cded1cdac5bfd3f3c2ff30010f484faccae4e871cc8a9316d2dc27363"}, + {file = "multidict-6.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7cbf407313236a79ce9b8af11808c29756cfb9c9a49a7f24bb1324537eec174b"}, + {file = "multidict-6.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2bf0068fe9abb0ebed1436a4e415117386951cf598eb8146ded4baf8e1ff6d1e"}, + {file = "multidict-6.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:195882f2f6272dacc88194ecd4de3608ad0ee29b161e541403b781a5f5dd346f"}, + {file = "multidict-6.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5776f9d2c3a1053f022f744af5f467c2f65b40d4cc00082bcf70e8c462c7dbad"}, + {file = "multidict-6.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a266373c604e49552d295d9f8ec4fd59bd364f2dd73eb18e7d36d5533b88f45"}, + {file = "multidict-6.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:79101d58094419b6e8d07e24946eba440136b9095590271cd6ccc4a90674a57d"}, + {file = "multidict-6.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:62eb76be8c20d9017a82b74965db93ddcf472b929b6b2b78c56972c73bacf2e4"}, + {file = "multidict-6.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:70c742357dd6207be30922207f8d59c91e2776ddbefa23830c55c09020e59f8a"}, + {file = "multidict-6.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:29eff1c9a905e298e9cd29f856f77485e58e59355f0ee323ac748203e002bbd3"}, + {file = "multidict-6.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:090e0b37fde199b58ea050c472c21dc8a3fbf285f42b862fe1ff02aab8942239"}, + {file = "multidict-6.5.1-cp311-cp311-win32.whl", hash = "sha256:6037beca8cb481307fb586ee0b73fae976a3e00d8f6ad7eb8af94a878a4893f0"}, + {file = "multidict-6.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:b632c1e4a2ff0bb4c1367d6c23871aa95dbd616bf4a847034732a142bb6eea94"}, + {file = "multidict-6.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:2ec3aa63f0c668f591d43195f8e555f803826dee34208c29ade9d63355f9e095"}, + {file = "multidict-6.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:48f95fe064f63d9601ef7a3dce2fc2a437d5fcc11bca960bc8be720330b13b6a"}, + {file = "multidict-6.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b7b6e1ce9b61f721417c68eeeb37599b769f3b631e6b25c21f50f8f619420b9"}, + {file = "multidict-6.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8b83b055889bda09fc866c0a652cdb6c36eeeafc2858259c9a7171fe82df5773"}, + {file = "multidict-6.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7bd4d655dc460c7aebb73b58ed1c074e85f7286105b012556cf0f25c6d1dba3"}, + {file = "multidict-6.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aa6dcf25ced31cdce10f004506dbc26129f28a911b32ed10e54453a0842a6173"}, + {file = "multidict-6.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:059fb556c3e6ce1a168496f92ef139ad839a47f898eaa512b1d43e5e05d78c6b"}, + {file = "multidict-6.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f97680c839dd9fa208e9584b1c2a5f1224bd01d31961f7f7d94984408c4a6b9e"}, + {file = "multidict-6.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7710c716243525cc05cd038c6e09f1807ee0fef2510a6e484450712c389c8d7f"}, + {file = "multidict-6.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:83eb172b4856ffff2814bdcf9c7792c0439302faab1b31376817b067b26cd8f5"}, + {file = "multidict-6.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:562d4714fa43f6ebc043a657535e4575e7d6141a818c9b3055f0868d29a1a41b"}, + {file = "multidict-6.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2d7def2fc47695c46a427b8f298fb5ace03d635c1fb17f30d6192c9a8fb69e70"}, + {file = "multidict-6.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:77bc8ab5c6bfe696eff564824e73a451fdeca22f3b960261750836cee02bcbfa"}, + {file = "multidict-6.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9eec51891d3c210948ead894ec1483d48748abec08db5ce9af52cc13fef37aee"}, + {file = "multidict-6.5.1-cp312-cp312-win32.whl", hash = "sha256:189f0c2bd1c0ae5509e453707d0e187e030c9e873a0116d1f32d1c870d0fc347"}, + {file = "multidict-6.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:e81f23b4b6f2a588f15d5cb554b2d8b482bb6044223d64b86bc7079cae9ebaad"}, + {file = "multidict-6.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:79d13e06d5241f9c8479dfeaf0f7cce8f453a4a302c9a0b1fa9b1a6869ff7757"}, + {file = "multidict-6.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:98011312f36d1e496f15454a95578d1212bc2ffc25650a8484752b06d304fd9b"}, + {file = "multidict-6.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bae589fb902b47bd94e6f539b34eefe55a1736099f616f614ec1544a43f95b05"}, + {file = "multidict-6.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6eb3bf26cd94eb306e4bc776d0964cc67a7967e4ad9299309f0ff5beec3c62be"}, + {file = "multidict-6.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5e1a5a99c72d1531501406fcc06b6bf699ebd079dacd6807bb43fc0ff260e5c"}, + {file = "multidict-6.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38755bcba18720cb2338bea23a5afcff234445ee75fa11518f6130e22f2ab970"}, + {file = "multidict-6.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f42fef9bcba3c32fd4e4a23c5757fc807d218b249573aaffa8634879f95feb73"}, + {file = "multidict-6.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:071b962f4cc87469cda90c7cc1c077b76496878b39851d7417a3d994e27fe2c6"}, + {file = "multidict-6.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:627ba4b7ce7c0115981f0fd91921f5d101dfb9972622178aeef84ccce1c2bbf3"}, + {file = "multidict-6.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05dcaed3e5e54f0d0f99a39762b0195274b75016cbf246f600900305581cf1a2"}, + {file = "multidict-6.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:11f5ecf3e741a18c578d118ad257c5588ca33cc7c46d51c0487d7ae76f072c32"}, + {file = "multidict-6.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b948eb625411c20b15088fca862c51a39140b9cf7875b5fb47a72bb249fa2f42"}, + {file = "multidict-6.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc993a96dfc8300befd03d03df46efdb1d8d5a46911b014e956a4443035f470d"}, + {file = "multidict-6.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2d333380f22d35a56c6461f4579cfe186e143cd0b010b9524ac027de2a34cd"}, + {file = "multidict-6.5.1-cp313-cp313-win32.whl", hash = "sha256:5891e3327e6a426ddd443c87339b967c84feb8c022dd425e0c025fa0fcd71e68"}, + {file = "multidict-6.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:fcdaa72261bff25fad93e7cb9bd7112bd4bac209148e698e380426489d8ed8a9"}, + {file = "multidict-6.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:84292145303f354a35558e601c665cdf87059d87b12777417e2e57ba3eb98903"}, + {file = "multidict-6.5.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f8316e58db799a1972afbc46770dfaaf20b0847003ab80de6fcb9861194faa3f"}, + {file = "multidict-6.5.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3468f0db187aca59eb56e0aa9f7c8c5427bcb844ad1c86557b4886aeb4484d8"}, + {file = "multidict-6.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:228533a5f99f1248cd79f6470779c424d63bc3e10d47c82511c65cc294458445"}, + {file = "multidict-6.5.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527076fdf5854901b1246c589af9a8a18b4a308375acb0020b585f696a10c794"}, + {file = "multidict-6.5.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9a17a17bad5c22f43e6a6b285dd9c16b1e8f8428202cd9bc22adaac68d0bbfed"}, + {file = "multidict-6.5.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:efd1951edab4a6cb65108d411867811f2b283f4b972337fb4269e40142f7f6a6"}, + {file = "multidict-6.5.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c07d5f38b39acb4f8f61a7aa4166d140ed628245ff0441630df15340532e3b3c"}, + {file = "multidict-6.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a6605dc74cd333be279e1fcb568ea24f7bdf1cf09f83a77360ce4dd32d67f14"}, + {file = "multidict-6.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d64e30ae9ba66ce303a567548a06d64455d97c5dff7052fe428d154274d7174"}, + {file = "multidict-6.5.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2fb5dde79a7f6d98ac5e26a4c9de77ccd2c5224a7ce89aeac6d99df7bbe06464"}, + {file = "multidict-6.5.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8a0d22e8b07cf620e9aeb1582340d00f0031e6a1f3e39d9c2dcbefa8691443b4"}, + {file = "multidict-6.5.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0120ed5cff2082c7a0ed62a8f80f4f6ac266010c722381816462f279bfa19487"}, + {file = "multidict-6.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3dea06ba27401c4b54317aa04791182dc9295e7aa623732dd459071a0e0f65db"}, + {file = "multidict-6.5.1-cp313-cp313t-win32.whl", hash = "sha256:93b21be44f3cfee3be68ed5cd8848a3c0420d76dbd12d74f7776bde6b29e5f33"}, + {file = "multidict-6.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c5c18f8646a520cc34d00f65f9f6f77782b8a8c59fd8de10713e0de7f470b5d0"}, + {file = "multidict-6.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb27128141474a1d545f0531b496c7c2f1c4beff50cb5a828f36eb62fef16c67"}, + {file = "multidict-6.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:279a37cb9d04097bf1c6308d7495cb4dfbd8fb538301bfe464266b045dfeb1cd"}, + {file = "multidict-6.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e63ac6adc668cfe52e29c00afe33c3b8dbec8e37b529aa83bf31ba4bad0c509"}, + {file = "multidict-6.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:36b138c6ec3aedaa975653ea90099efb22042bab31727dd4cd2921a64de46b25"}, + {file = "multidict-6.5.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:576a1887a5c5becbe4fb484d0bdf6ed8ec89e9c11770f8f3214fd127ba137b8b"}, + {file = "multidict-6.5.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a0f1890f9a05d038720a7c3b5d82467534495bcb6bbda929f6f0914977cc56d1"}, + {file = "multidict-6.5.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5125a9faed98738d7d6e23650bd8af70abb95628d011f57f70a4d8f349e6d073"}, + {file = "multidict-6.5.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e09d7852100bcc3e466e63478ee18c68cc4d2ca2a978f29b90d5e2ea814f7b3e"}, + {file = "multidict-6.5.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e615032b684a1d6faffe41d64cd896801bd3f2c1b642355e9b5d11fd8d40223e"}, + {file = "multidict-6.5.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70c0e51d55f9bc5e97de950c3b3e88f501b3ca2b3894f231f3957dd3985b4d54"}, + {file = "multidict-6.5.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a6523258f2eb24c91995ae64172c19cd73bacd5a7f2b0733676966c527ab08f8"}, + {file = "multidict-6.5.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5e8fefd7c062b0657af2480d789dcb347450d17c7bd20b02303c25f1f59a33a7"}, + {file = "multidict-6.5.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3d749b10cc6acb2c0814df881910ffd8d8ab1ec54493585579b4a75f89fe86d6"}, + {file = "multidict-6.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22b47f7e76ebea0b802df9ed08165b1e6ab52b140c7180c3e740e6205b3781b3"}, + {file = "multidict-6.5.1-cp39-cp39-win32.whl", hash = "sha256:cc80c7e8f297484c4511e887c244adec9a7ed3a76826cb8dbc6183b717a37d1f"}, + {file = "multidict-6.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:253e5c41fcc02e2956ab276b5a702f3972db1e87c080be55e87ca31a2f4f8012"}, + {file = "multidict-6.5.1-cp39-cp39-win_arm64.whl", hash = "sha256:a9e15dfe441aec31e0fa78f497aca83f0ad992ca58782cbaba8220e5a87608fc"}, + {file = "multidict-6.5.1-py3-none-any.whl", hash = "sha256:895354f4a38f53a1df2cc3fa2223fa714cff2b079a9f018a76cad35e7f0f044c"}, + {file = "multidict-6.5.1.tar.gz", hash = "sha256:a835ea8103f4723915d7d621529c80ef48db48ae0c818afcabe0f95aa1febc3a"}, ] [package.dependencies] @@ -3570,14 +3588,14 @@ files = [ [[package]] name = "narwhals" -version = "1.35.0" +version = "1.44.0" description = "Extremely lightweight compatibility layer between dataframe libraries" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "narwhals-1.35.0-py3-none-any.whl", hash = "sha256:7562af132fa3f8aaaf34dc96d7ec95bdca29d1c795e8fcf14e01edf1d32122bc"}, - {file = "narwhals-1.35.0.tar.gz", hash = "sha256:07477d18487fbc940243b69818a177ed7119b737910a8a254fb67688b48a7c96"}, + {file = "narwhals-1.44.0-py3-none-any.whl", hash = "sha256:a170ea0bab4cf1f323d9f8bf17f2d7042c3d73802bea321996b39bf075d57de5"}, + {file = "narwhals-1.44.0.tar.gz", hash = "sha256:8cf0616d4f6f21225b3b56fcde96ccab6d05023561a0f162402aa9b8c33ad31d"}, ] [package.extras] @@ -3586,10 +3604,11 @@ dask = ["dask[dataframe] (>=2024.8)"] duckdb = ["duckdb (>=1.0)"] ibis = ["ibis-framework (>=6.0.0)", "packaging", "pyarrow-hotfix", "rich"] modin = ["modin"] -pandas = ["pandas (>=0.25.3)"] +pandas = ["pandas (>=1.1.3)"] polars = ["polars (>=0.20.3)"] pyarrow = ["pyarrow (>=11.0.0)"] pyspark = ["pyspark (>=3.5.0)"] +pyspark-connect = ["pyspark[connect] (>=3.5.0)"] sqlframe = ["sqlframe (>=3.22.0)"] [[package]] @@ -3692,14 +3711,14 @@ files = [ [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, + {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, + {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, ] [package.extras] @@ -3744,52 +3763,52 @@ openapi-schema-validator = ">=0.6.0,<0.7.0" [[package]] name = "opentelemetry-api" -version = "1.32.1" +version = "1.34.1" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724"}, - {file = "opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb"}, + {file = "opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c"}, + {file = "opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3"}, ] [package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<8.7.0" +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-sdk" -version = "1.32.1" +version = "1.34.1" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17"}, - {file = "opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092"}, + {file = "opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e"}, + {file = "opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d"}, ] [package.dependencies] -opentelemetry-api = "1.32.1" -opentelemetry-semantic-conventions = "0.53b1" -typing-extensions = ">=3.7.4" +opentelemetry-api = "1.34.1" +opentelemetry-semantic-conventions = "0.55b1" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.53b1" +version = "0.55b1" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208"}, - {file = "opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992"}, + {file = "opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed"}, + {file = "opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3"}, ] [package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.32.1" +opentelemetry-api = "1.34.1" +typing-extensions = ">=4.5.0" [[package]] name = "orjson" @@ -4047,14 +4066,14 @@ setuptools = "*" [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" groups = ["dev", "docs"] files = [ - {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, - {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] @@ -4064,14 +4083,14 @@ type = ["mypy (>=1.14.1)"] [[package]] name = "plotly" -version = "6.0.1" +version = "6.1.2" description = "An open-source interactive data visualization library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "plotly-6.0.1-py3-none-any.whl", hash = "sha256:4714db20fea57a435692c548a4eb4fae454f7daddf15f8d8ba7e1045681d7768"}, - {file = "plotly-6.0.1.tar.gz", hash = "sha256:dd8400229872b6e3c964b099be699f8d00c489a974f2cfccfad5e8240873366b"}, + {file = "plotly-6.1.2-py3-none-any.whl", hash = "sha256:f1548a8ed9158d59e03d7fed548c7db5549f3130d9ae19293c8638c202648f6d"}, + {file = "plotly-6.1.2.tar.gz", hash = "sha256:4fdaa228926ba3e3a213f4d1713287e69dcad1a7e66cf2025bd7d7026d5014b4"}, ] [package.dependencies] @@ -4079,23 +4098,25 @@ narwhals = ">=1.15.1" packaging = "*" [package.extras] +dev = ["black (==25.1.0)"] express = ["numpy"] +kaleido = ["kaleido (==1.0.0rc13)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "ply" @@ -4168,110 +4189,110 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] [[package]] name = "propcache" -version = "0.3.1" +version = "0.3.2" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, - {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, - {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, - {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, - {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, - {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, - {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, - {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, - {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, - {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, - {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, - {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, - {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, - {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, - {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, - {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, - {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, ] [[package]] @@ -4732,14 +4753,14 @@ urllib3 = ">=1.26.0" [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["dev", "docs"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -4799,14 +4820,14 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.14.3" +version = "10.16" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, - {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, + {file = "pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2"}, + {file = "pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de"}, ] [package.dependencies] @@ -5130,14 +5151,14 @@ files = [ [[package]] name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, ] [package.dependencies] @@ -5365,19 +5386,16 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "retrying" -version = "1.3.4" +version = "1.4.0" description = "Retrying" optional = false -python-versions = "*" +python-versions = ">=3.6" groups = ["main"] files = [ - {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, - {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, + {file = "retrying-1.4.0-py3-none-any.whl", hash = "sha256:6509d829c70271937605bce361c8f76e91f9123d355d14df7dc6972b1518064a"}, + {file = "retrying-1.4.0.tar.gz", hash = "sha256:efa99c78bf4fbdbe6f0cba4101470fbc684b93d30ca45ffa1288443a9805172f"}, ] -[package.dependencies] -six = ">=1.7.0" - [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -5415,126 +5433,129 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.24.0" +version = "0.25.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, - {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, - {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, - {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, - {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, - {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, - {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, - {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, - {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, - {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, - {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, - {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, - {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, - {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, - {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, - {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, - {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, - {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, - {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, - {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, - {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, - {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, - {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, - {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, - {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, + {file = "rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9"}, + {file = "rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da"}, + {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380"}, + {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9"}, + {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54"}, + {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2"}, + {file = "rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24"}, + {file = "rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a"}, + {file = "rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d"}, + {file = "rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd"}, + {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65"}, + {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f"}, + {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d"}, + {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042"}, + {file = "rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc"}, + {file = "rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4"}, + {file = "rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4"}, + {file = "rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c"}, + {file = "rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea"}, + {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65"}, + {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c"}, + {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd"}, + {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb"}, + {file = "rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe"}, + {file = "rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192"}, + {file = "rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728"}, + {file = "rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559"}, + {file = "rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325"}, + {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295"}, + {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b"}, + {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98"}, + {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd"}, + {file = "rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31"}, + {file = "rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500"}, + {file = "rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5"}, + {file = "rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129"}, + {file = "rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194"}, + {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6"}, + {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78"}, + {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72"}, + {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66"}, + {file = "rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523"}, + {file = "rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763"}, + {file = "rpds_py-0.25.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ce4c8e485a3c59593f1a6f683cf0ea5ab1c1dc94d11eea5619e4fb5228b40fbd"}, + {file = "rpds_py-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8222acdb51a22929c3b2ddb236b69c59c72af4019d2cba961e2f9add9b6e634"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4593c4eae9b27d22df41cde518b4b9e4464d139e4322e2127daa9b5b981b76be"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd035756830c712b64725a76327ce80e82ed12ebab361d3a1cdc0f51ea21acb0"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:114a07e85f32b125404f28f2ed0ba431685151c037a26032b213c882f26eb908"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dec21e02e6cc932538b5203d3a8bd6aa1480c98c4914cb88eea064ecdbc6396a"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eab132f41bf792c7a0ea1578e55df3f3e7f61888e340779b06050a9a3f16e9"}, + {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c98f126c4fc697b84c423e387337d5b07e4a61e9feac494362a59fd7a2d9ed80"}, + {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e6a327af8ebf6baba1c10fadd04964c1965d375d318f4435d5f3f9651550f4a"}, + {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc120d1132cff853ff617754196d0ac0ae63befe7c8498bd67731ba368abe451"}, + {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:140f61d9bed7839446bdd44852e30195c8e520f81329b4201ceead4d64eb3a9f"}, + {file = "rpds_py-0.25.1-cp39-cp39-win32.whl", hash = "sha256:9c006f3aadeda131b438c3092124bd196b66312f0caa5823ef09585a669cf449"}, + {file = "rpds_py-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:a61d0b2c7c9a0ae45732a77844917b427ff16ad5464b4d4f5e4adb955f582890"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11"}, + {file = "rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf"}, + {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50f2c501a89c9a5f4e454b126193c5495b9fb441a75b298c60591d8a2eb92e1b"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d779b325cc8238227c47fbc53964c8cc9a941d5dbae87aa007a1f08f2f77b23"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:036ded36bedb727beeabc16dc1dad7cb154b3fa444e936a03b67a86dc6a5066e"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245550f5a1ac98504147cba96ffec8fabc22b610742e9150138e5d60774686d7"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff7c23ba0a88cb7b104281a99476cccadf29de2a0ef5ce864959a52675b1ca83"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e37caa8cdb3b7cf24786451a0bdb853f6347b8b92005eeb64225ae1db54d1c2b"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2f48ab00181600ee266a095fe815134eb456163f7d6699f525dee471f312cf"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e5fc7484fa7dce57e25063b0ec9638ff02a908304f861d81ea49273e43838c1"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d3c10228d6cf6fe2b63d2e7985e94f6916fa46940df46b70449e9ff9297bd3d1"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5d9e40f32745db28c1ef7aad23f6fc458dc1e29945bd6781060f0d15628b8ddf"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:35a8d1a24b5936b35c5003313bc177403d8bdef0f8b24f28b1c4a255f94ea992"}, + {file = "rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793"}, + {file = "rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3"}, ] [[package]] @@ -5554,18 +5575,18 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruamel-yaml" -version = "0.18.10" +version = "0.18.14" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, - {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, + {file = "ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2"}, + {file = "ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7"}, ] [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""} [package.extras] docs = ["mercurial (>5.7)", "ryd"] @@ -5586,6 +5607,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -5594,6 +5616,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -5602,6 +5625,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -5610,6 +5634,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -5618,6 +5643,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5758,14 +5784,14 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "79.0.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a"}, - {file = "setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] @@ -5914,14 +5940,14 @@ test = ["pyshacl", "pytest", "tzdata"] [[package]] name = "std-uritemplate" -version = "2.0.3" +version = "2.0.5" description = "std-uritemplate implementation for Python" optional = false python-versions = "<4.0,>=3.8" groups = ["main"] files = [ - {file = "std_uritemplate-2.0.3-py3-none-any.whl", hash = "sha256:434df26453bf68c6077879fed6609b2c39e2fc73080e74cd157269d5f8abdb3e"}, - {file = "std_uritemplate-2.0.3.tar.gz", hash = "sha256:ad4cb1d671bcf4a3608b3598c687be4b0929867c53a2d69c105989da6a5a2d4c"}, + {file = "std_uritemplate-2.0.5-py3-none-any.whl", hash = "sha256:0f5184f8e6f315a01f92cfbed335f62f087e453e79cd586b67a724211e686c28"}, + {file = "std_uritemplate-2.0.5.tar.gz", hash = "sha256:7703a886cce59d155c21b5acf1ad8d48db9f3322de98fa783a8396fbf35cbc06"}, ] [[package]] @@ -5941,14 +5967,14 @@ pbr = ">=2.0.0" [[package]] name = "sympy" -version = "1.13.3" +version = "1.14.0" description = "Computer algebra system (CAS) in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73"}, - {file = "sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9"}, + {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, + {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, ] [package.dependencies] @@ -6054,14 +6080,14 @@ files = [ [[package]] name = "tomlkit" -version = "0.13.2" +version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, + {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, + {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, ] [[package]] @@ -6088,14 +6114,14 @@ telegram = ["requests"] [[package]] name = "typer" -version = "0.15.2" +version = "0.16.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, - {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, + {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, + {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, ] [package.dependencies] @@ -6106,14 +6132,14 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, + {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, ] [[package]] @@ -6175,14 +6201,14 @@ files = [ [[package]] name = "uritemplate" -version = "4.1.1" +version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, - {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, + {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, + {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, ] [[package]] @@ -6431,14 +6457,14 @@ files = [ [[package]] name = "xlsxwriter" -version = "3.2.3" +version = "3.2.5" description = "A Python module for creating Excel XLSX files." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d"}, - {file = "xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5"}, + {file = "xlsxwriter-3.2.5-py3-none-any.whl", hash = "sha256:4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd"}, + {file = "xlsxwriter-3.2.5.tar.gz", hash = "sha256:7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe"}, ] [[package]] @@ -6455,116 +6481,116 @@ files = [ [[package]] name = "yarl" -version = "1.20.0" +version = "1.20.1" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f6670b9ae3daedb325fa55fbe31c22c8228f6e0b513772c2e1c623caa6ab22"}, - {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85a231fa250dfa3308f3c7896cc007a47bc76e9e8e8595c20b7426cac4884c62"}, - {file = "yarl-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a06701b647c9939d7019acdfa7ebbfbb78ba6aa05985bb195ad716ea759a569"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7595498d085becc8fb9203aa314b136ab0516c7abd97e7d74f7bb4eb95042abe"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af5607159085dcdb055d5678fc2d34949bd75ae6ea6b4381e784bbab1c3aa195"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95b50910e496567434cb77a577493c26bce0f31c8a305135f3bda6a2483b8e10"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b594113a301ad537766b4e16a5a6750fcbb1497dcc1bc8a4daae889e6402a634"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:083ce0393ea173cd37834eb84df15b6853b555d20c52703e21fbababa8c129d2"}, - {file = "yarl-1.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1a350a652bbbe12f666109fbddfdf049b3ff43696d18c9ab1531fbba1c977a"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb0caeac4a164aadce342f1597297ec0ce261ec4532bbc5a9ca8da5622f53867"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d88cc43e923f324203f6ec14434fa33b85c06d18d59c167a0637164863b8e995"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e52d6ed9ea8fd3abf4031325dc714aed5afcbfa19ee4a89898d663c9976eb487"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ce360ae48a5e9961d0c730cf891d40698a82804e85f6e74658fb175207a77cb2"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:06d06c9d5b5bc3eb56542ceeba6658d31f54cf401e8468512447834856fb0e61"}, - {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c27d98f4e5c4060582f44e58309c1e55134880558f1add7a87c1bc36ecfade19"}, - {file = "yarl-1.20.0-cp310-cp310-win32.whl", hash = "sha256:f4d3fa9b9f013f7050326e165c3279e22850d02ae544ace285674cb6174b5d6d"}, - {file = "yarl-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc906b636239631d42eb8a07df8359905da02704a868983265603887ed68c076"}, - {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3"}, - {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a"}, - {file = "yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2"}, - {file = "yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4"}, - {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5"}, - {file = "yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6"}, - {file = "yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb"}, - {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f"}, - {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e"}, - {file = "yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018"}, - {file = "yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1"}, - {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b"}, - {file = "yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64"}, - {file = "yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c"}, - {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f"}, - {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3"}, - {file = "yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0"}, - {file = "yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e"}, - {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384"}, - {file = "yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62"}, - {file = "yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c"}, - {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051"}, - {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d"}, - {file = "yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5"}, - {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd"}, - {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f"}, - {file = "yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac"}, - {file = "yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe"}, - {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:119bca25e63a7725b0c9d20ac67ca6d98fa40e5a894bd5d4686010ff73397914"}, - {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35d20fb919546995f1d8c9e41f485febd266f60e55383090010f272aca93edcc"}, - {file = "yarl-1.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:484e7a08f72683c0f160270566b4395ea5412b4359772b98659921411d32ad26"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d8a3d54a090e0fff5837cd3cc305dd8a07d3435a088ddb1f65e33b322f66a94"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0cf05ae2d3d87a8c9022f3885ac6dea2b751aefd66a4f200e408a61ae9b7f0d"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a884b8974729e3899d9287df46f015ce53f7282d8d3340fa0ed57536b440621c"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8d8aa8dd89ffb9a831fedbcb27d00ffd9f4842107d52dc9d57e64cb34073d5c"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4e88d6c3c8672f45a30867817e4537df1bbc6f882a91581faf1f6d9f0f1b5a"}, - {file = "yarl-1.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdb77efde644d6f1ad27be8a5d67c10b7f769804fff7a966ccb1da5a4de4b656"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4ba5e59f14bfe8d261a654278a0f6364feef64a794bd456a8c9e823071e5061c"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d0bf955b96ea44ad914bc792c26a0edcd71b4668b93cbcd60f5b0aeaaed06c64"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:27359776bc359ee6eaefe40cb19060238f31228799e43ebd3884e9c589e63b20"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:04d9c7a1dc0a26efb33e1acb56c8849bd57a693b85f44774356c92d610369efa"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:faa709b66ae0e24c8e5134033187a972d849d87ed0a12a0366bedcc6b5dc14a5"}, - {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:44869ee8538208fe5d9342ed62c11cc6a7a1af1b3d0bb79bb795101b6e77f6e0"}, - {file = "yarl-1.20.0-cp39-cp39-win32.whl", hash = "sha256:b7fa0cb9fd27ffb1211cde944b41f5c67ab1c13a13ebafe470b1e206b8459da8"}, - {file = "yarl-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4fad6e5189c847820288286732075f213eabf81be4d08d6cc309912e62be5b7"}, - {file = "yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124"}, - {file = "yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, ] [package.dependencies] @@ -6574,14 +6600,14 @@ propcache = ">=0.2.1" [[package]] name = "zipp" -version = "3.21.0" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main", "dev", "docs"] files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] markers = {dev = "python_version < \"3.10\"", docs = "python_version < \"3.10\""} @@ -6590,7 +6616,7 @@ check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \" cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 03652c8a61..944d7747f9 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,18 +2,30 @@ All notable changes to the **Prowler SDK** are documented in this file. -## [v5.8.0] (Prowler UNRELEASED) +## [v5.9.0] (Prowler UNRELEASED) ### Added -- Add `storage_geo_redundant_enabled` check for Azure provider. [(#7980)](https://github.com/prowler-cloud/prowler/pull/7980) -- Add `storage_cross_tenant_replication_disabled` check for Azure provider. [(#7977)](https://github.com/prowler-cloud/prowler/pull/7977) + +### Changed + +### Fixed +- fix(iam): detect wildcarded ARNs in sts:AssumeRole policy resources [(#8164)](https://github.com/prowler-cloud/prowler/pull/8164) + +--- + +## [v5.8.0] (Prowler v5.8.0) + +### Added + +- `storage_geo_redundant_enabled` check for Azure provider [(#7980)](https://github.com/prowler-cloud/prowler/pull/7980) +- `storage_cross_tenant_replication_disabled` check for Azure provider [(#7977)](https://github.com/prowler-cloud/prowler/pull/7977) - CIS 1.11 compliance framework for Kubernetes [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) - Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) - Weight for Prowler ThreatScore scoring [(#7795)](https://github.com/prowler-cloud/prowler/pull/7795) -- New check `entra_users_mfa_capable` for M365 provider [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) -- New check `admincenter_organization_customer_lockbox_enabled` for M365 provider [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) -- New check `admincenter_external_calendar_sharing_disabled` for M365 provider [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) -- a level for Prowler ThreatScore in the accordion in Dashboard [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) +- `entra_users_mfa_capable` check for M365 provider [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) +- `admincenter_organization_customer_lockbox_enabled` check for M365 provider [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) +- `admincenter_external_calendar_sharing_disabled` check for M365 provider [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) +- Level for Prowler ThreatScore in the accordion in Dashboard [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) - CIS 4.0 compliance framework for GCP [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) - `repository_has_codeowners_file` check for GitHub provider [(#7752)](https://github.com/prowler-cloud/prowler/pull/7752) - `repository_default_branch_requires_signed_commits` check for GitHub provider [(#7777)](https://github.com/prowler-cloud/prowler/pull/7777) @@ -31,20 +43,29 @@ All notable changes to the **Prowler SDK** are documented in this file. - Avoid bypassing IAM check using wildcards [(#7708)](https://github.com/prowler-cloud/prowler/pull/7708) - `storage_blob_versioning_is_enabled` new check for Azure provider [(#7927)](https://github.com/prowler-cloud/prowler/pull/7927) - New method to authenticate in AppInsights in check `app_function_application_insights_enabled` [(#7763)](https://github.com/prowler-cloud/prowler/pull/7763) -- ISO 27001 2022 for M365 provider. [(#7985)](https://github.com/prowler-cloud/prowler/pull/7985) +- ISO 27001 2022 for M365 provider [(#7985)](https://github.com/prowler-cloud/prowler/pull/7985) - `codebuild_project_uses_allowed_github_organizations` check for AWS provider [(#7595)](https://github.com/prowler-cloud/prowler/pull/7595) - IaC provider [(#7852)](https://github.com/prowler-cloud/prowler/pull/7852) - Azure Databricks service integration for Azure provider, including the `databricks_workspace_vnet_injection_enabled` check [(#8008)](https://github.com/prowler-cloud/prowler/pull/8008) -- Azure Databricks check `databricks_workspace_cmk_encryption_enabled` to ensure workspaces use customer-managed keys (CMK) for encryption at rest [(#8017)](https://github.com/prowler-cloud/prowler/pull/8017) -- Add `storage_account_default_to_entra_authorization_enabled` check for Azure provider. [(#7981)](https://github.com/prowler-cloud/prowler/pull/7981) +- `databricks_workspace_cmk_encryption_enabled` check for Azure provider [(#8017)](https://github.com/prowler-cloud/prowler/pull/8017) +- Appication auth for PowerShell in M365 provider [(#7992)](https://github.com/prowler-cloud/prowler/pull/7992) +- `storage_account_default_to_entra_authorization_enabled` check for Azure provider [(#7981)](https://github.com/prowler-cloud/prowler/pull/7981) - Improve overview page from Prowler Dashboard [(#8118)](https://github.com/prowler-cloud/prowler/pull/8118) -- `keyvault_ensure_public_network_access_disabled` check for Azure provider. [(#8072)](https://github.com/prowler-cloud/prowler/pull/8072) -- New check `monitor_alert_service_health_exists` for Azure provider [(#8067)](https://github.com/prowler-cloud/prowler/pull/8067) +- `keyvault_ensure_public_network_access_disabled` check for Azure provider [(#8072)](https://github.com/prowler-cloud/prowler/pull/8072) +- `monitor_alert_service_health_exists` check for Azure provider [(#8067)](https://github.com/prowler-cloud/prowler/pull/8067) - Replace `Domain.Read.All` with `Directory.Read.All` in Azure and M365 docs [(#8075)](https://github.com/prowler-cloud/prowler/pull/8075) - Refactor IaC provider to use Checkov as Python library [(#8093)](https://github.com/prowler-cloud/prowler/pull/8093) +- New check `codebuild_project_not_publicly_accessible` for AWS provider [(#8127)](https://github.com/prowler-cloud/prowler/pull/8127) ### Fixed - Consolidate Azure Storage file service properties to the account level, improving the accuracy of the `storage_ensure_file_shares_soft_delete_is_enabled` check [(#8087)](https://github.com/prowler-cloud/prowler/pull/8087) +- Migrate Azure VM service and managed disk logic to Pydantic models for better serialization and type safety, and update all related tests to use the new models and fix UUID handling [(#https://github.com/prowler-cloud/prowler/pull/8151)](https://github.com/prowler-cloud/prowler/pull/https://github.com/prowler-cloud/prowler/pull/8151) +- `organizations_scp_check_deny_regions` check to pass when SCP policies have no statements [(#8091)](https://github.com/prowler-cloud/prowler/pull/8091) +- Fix logic in VPC and ELBv2 checks [(#8077)](https://github.com/prowler-cloud/prowler/pull/8077) +- Retrieve correctly ECS Container insights settings [(#8097)](https://github.com/prowler-cloud/prowler/pull/8097) +- Fix correct handling for different accounts-dates in prowler dashboard compliance page [(#8108)](https://github.com/prowler-cloud/prowler/pull/8108) +- Handling of `block-project-ssh-keys` in GCP check `compute_instance_block_project_wide_ssh_keys_disabled` [(#8115)](https://github.com/prowler-cloud/prowler/pull/8115) +- Handle empty name in Azure Defender and GCP checks [(#8120)](https://github.com/prowler-cloud/prowler/pull/8120) ### Changed - Reworked `S3.test_connection` to match the AwsProvider logic [(#8088)](https://github.com/prowler-cloud/prowler/pull/8088) @@ -54,23 +75,11 @@ All notable changes to the **Prowler SDK** are documented in this file. --- -## [v5.7.6] (Prowler UNRELEASED) - -### Fixed -- `organizations_scp_check_deny_regions` check to pass when SCP policies have no statements [(#8091)](https://github.com/prowler-cloud/prowler/pull/8091) -- Fix logic in VPC and ELBv2 checks [(#8077)](https://github.com/prowler-cloud/prowler/pull/8077) -- Retrieve correctly ECS Container insights settings [(#8097)](https://github.com/prowler-cloud/prowler/pull/8097) -- Fix correct handling for different accounts-dates in prowler dashboard compliance page [(#8108)](https://github.com/prowler-cloud/prowler/pull/8108) -- Handling of `block-project-ssh-keys` in GCP check `compute_instance_block_project_wide_ssh_keys_disabled` [(#8115)](https://github.com/prowler-cloud/prowler/pull/8115) -- Handle empty name in Azure Defender and GCP checks [(#8120)](https://github.com/prowler-cloud/prowler/pull/8120) - ---- - ## [v5.7.5] (Prowler 5.7.5) ### Fixed - Use unified timestamp for all requirements [(#8059)](https://github.com/prowler-cloud/prowler/pull/8059) -- Add EKS to service without subservices. [(#7959)](https://github.com/prowler-cloud/prowler/pull/7959) +- Add EKS to service without subservices [(#7959)](https://github.com/prowler-cloud/prowler/pull/7959) - `apiserver_strong_ciphers_only` check for K8S provider [(#7952)](https://github.com/prowler-cloud/prowler/pull/7952) - Handle `0` at the start and end of account uids in Prowler Dashboard [(#7955)](https://github.com/prowler-cloud/prowler/pull/7955) - Typo in PCI 4.0 for K8S provider [(#7971)](https://github.com/prowler-cloud/prowler/pull/7971) diff --git a/prowler/config/config.py b/prowler/config/config.py index dbbe71db9e..3311f552a9 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -12,7 +12,7 @@ from prowler.lib.logger import logger timestamp = datetime.today() timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc) -prowler_version = "5.8.0" +prowler_version = "5.9.0" html_logo_url = "https://github.com/prowler-cloud/prowler/" square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png" aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png" diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index ef19bcba0d..1172e88e10 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -951,6 +951,7 @@ "aws": [ "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", @@ -1336,7 +1337,9 @@ "aws": [ "us-east-1" ], - "aws-cn": [], + "aws-cn": [ + "cn-northwest-1" + ], "aws-us-gov": [] } }, @@ -2785,6 +2788,9 @@ "regions": { "aws": [ "af-south-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", @@ -3461,6 +3467,7 @@ "aws": [ "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", @@ -3506,6 +3513,7 @@ "regions": { "aws": [ "ap-northeast-1", + "ap-northeast-2", "ap-northeast-3", "eu-west-1", "eu-west-2", @@ -4987,6 +4995,8 @@ "ap-south-1", "ap-southeast-1", "ap-southeast-2", + "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "eu-central-1", "eu-north-1", @@ -9901,7 +9911,10 @@ "cn-north-1", "cn-northwest-1" ], - "aws-us-gov": [] + "aws-us-gov": [ + "us-gov-east-1", + "us-gov-west-1" + ] } }, "schemas": { @@ -10319,17 +10332,20 @@ "ap-northeast-2", "ap-northeast-3", "ap-south-1", + "ap-south-2", "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", "eu-south-1", "eu-west-1", "eu-west-2", "eu-west-3", "il-central-1", + "me-central-1", "me-south-1", "sa-east-1", "us-east-1", @@ -10352,17 +10368,20 @@ "ap-northeast-2", "ap-northeast-3", "ap-south-1", + "ap-south-2", "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", "eu-south-1", "eu-west-1", "eu-west-2", "eu-west-3", "il-central-1", + "me-central-1", "me-south-1", "sa-east-1", "us-east-1", diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/__init__.py b/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.metadata.json new file mode 100644 index 0000000000..5d7427df8e --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "codebuild_project_not_publicly_accessible", + "CheckTitle": "Ensure AWS CodeBuild projects are not public", + "CheckType": [], + "ServiceName": "codebuild", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:codebuild:region:account-id:project:project-name", + "Severity": "high", + "ResourceType": "AwsCodeBuildProject", + "Description": "Check for CodeBuild projects ensuring that the project visibility is appropriate", + "Risk": "Public CodeBuild Project ensures all build logs and artifacts are available to the public. Environment variables, source code, and other sensitive information may have been output to the build logs and artifacts. You must be careful about what information is output to the build logs.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "NativeIaC": "", + "Terraform": "", + "CLI": "aws codebuild update-project --name --project-visibility PRIVATE", + "Other": "" + }, + "Recommendation": { + "Text": "Ensure that all CodeBuild projects are private to avoid fact gathering about builds from an Attacker.", + "Url": "https://docs.aws.amazon.com/codebuild/latest/userguide/public-builds.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.py b/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.py new file mode 100644 index 0000000000..6fd42e971c --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible.py @@ -0,0 +1,26 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client + + +class codebuild_project_not_publicly_accessible(Check): + def execute(self) -> List[Check_Report_AWS]: + findings = [] + + projects = codebuild_client.projects + for arn, project in projects.items(): + report = Check_Report_AWS(self.metadata(), resource=project) + report.resource_id = project.name + report.resource_arn = arn + report.region = project.region + report.status = "FAIL" + report.status_extended = f"CodeBuild project {project.name} is public." + + if project.project_visibility == "PRIVATE": + report.status = "PASS" + report.status_extended = f"CodeBuild project {project.name} is private." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/codebuild/codebuild_service.py b/prowler/providers/aws/services/codebuild/codebuild_service.py index c7210a67dd..475f578e94 100644 --- a/prowler/providers/aws/services/codebuild/codebuild_service.py +++ b/prowler/providers/aws/services/codebuild/codebuild_service.py @@ -121,6 +121,7 @@ class Codebuild(AWSService): ) project.tags = project_info.get("tags", []) project.service_role_arn = project_info.get("serviceRole", "") + project.project_visibility = project_info.get("projectVisibility", "") except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -222,6 +223,7 @@ class Project(BaseModel): s3_logs: Optional[s3Logs] cloudwatch_logs: Optional[CloudWatchLogs] tags: Optional[list] + project_visibility: Optional[str] = None class ExportConfig(BaseModel): diff --git a/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py b/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py index 881e362d22..48142c7068 100644 --- a/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py +++ b/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py @@ -5,6 +5,14 @@ from prowler.providers.aws.services.iam.iam_client import iam_client class iam_no_custom_policy_permissive_role_assumption(Check): def execute(self) -> Check_Report_AWS: findings = [] + + def resource_has_wildcard(resource): + if isinstance(resource, str): + return "*" in resource + if isinstance(resource, list): + return any("*" in r for r in resource) + return False + for policy in iam_client.policies: # Check only custom policies if policy.type == "Custom": @@ -12,6 +20,7 @@ class iam_no_custom_policy_permissive_role_assumption(Check): report.region = iam_client.region report.status = "PASS" report.status_extended = f"Custom Policy {policy.name} does not allow permissive STS Role assumption." + if policy.document: if not isinstance(policy.document["Statement"], list): policy_statements = [policy.document["Statement"]] @@ -19,30 +28,23 @@ class iam_no_custom_policy_permissive_role_assumption(Check): policy_statements = policy.document["Statement"] for statement in policy_statements: if ( - statement["Effect"] == "Allow" + statement.get("Effect") == "Allow" and "Action" in statement and "Resource" in statement - and "*" in statement["Resource"] + and resource_has_wildcard(statement["Resource"]) ): - if isinstance(statement["Action"], list): - for action in statement["Action"]: - if ( - action == "sts:AssumeRole" - or action == "sts:*" - or action == "*" - ): - report.status = "FAIL" - report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption." - break - else: - if ( - statement["Action"] == "sts:AssumeRole" - or statement["Action"] == "sts:*" - or statement["Action"] == "*" - ): + actions = ( + statement["Action"] + if isinstance(statement["Action"], list) + else [statement["Action"]] + ) + for action in actions: + if action in ["sts:AssumeRole", "sts:*", "*"]: report.status = "FAIL" report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption." - break + break + if report.status == "FAIL": + break findings.append(report) diff --git a/prowler/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks.py b/prowler/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks.py index 22735fa9b7..e771fc80e5 100644 --- a/prowler/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks.py +++ b/prowler/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks.py @@ -15,17 +15,17 @@ class vm_ensure_using_managed_disks(Check): using_managed_disks = ( True - if vm.storage_profile - and getattr(vm.storage_profile, "os_disk", False) - and getattr(vm.storage_profile.os_disk, "managed_disk", False) + if getattr( + getattr(getattr(vm, "storage_profile", None), "os_disk", None), + "managed_disk", + None, + ) else False ) - if using_managed_disks and getattr( - vm.storage_profile, "data_disks", False - ): + if using_managed_disks and getattr(vm, "storage_profile", None): for data_disk in vm.storage_profile.data_disks: - if not getattr(data_disk, "managed_disk", False): + if not getattr(data_disk, "managed_disk", None): using_managed_disks = False break diff --git a/prowler/providers/azure/services/vm/vm_service.py b/prowler/providers/azure/services/vm/vm_service.py index d5d0dde5c7..a30379d9bc 100644 --- a/prowler/providers/azure/services/vm/vm_service.py +++ b/prowler/providers/azure/services/vm/vm_service.py @@ -1,7 +1,9 @@ from dataclasses import dataclass +from enum import Enum from typing import List, Optional from azure.mgmt.compute import ComputeManagementClient +from pydantic import BaseModel from prowler.lib.logger import logger from prowler.providers.azure.azure_provider import AzureProvider @@ -30,24 +32,33 @@ class VirtualMachines(AzureService): if storage_profile else None ) - data_disks = [] + if storage_profile and getattr(storage_profile, "data_disks", []): data_disks = [ DataDisk( lun=data_disk.lun, name=data_disk.name, - managed_disk=data_disk.managed_disk, + managed_disk=ManagedDiskParameters( + id=( + getattr( + getattr(data_disk, "managed_disk", None), + "id", + None, + ) + if data_disk.managed_disk + else None + ) + ), ) for data_disk in getattr(storage_profile, "data_disks", []) - if data_disk ] extensions = [] if getattr(vm, "resources", []): extensions = [ VirtualMachineExtension(id=extension.id) - for extension in getattr(vm, "resources", []) + for extension in vm.resources if extension ] @@ -60,8 +71,17 @@ class VirtualMachines(AzureService): StorageProfile( os_disk=OSDisk( name=getattr(os_disk, "name", None), - managed_disk=getattr( - os_disk, "managed_disk", None + operating_system_type=getattr( + os_disk, "os_type", None + ), + managed_disk=ManagedDiskParameters( + id=getattr( + getattr( + os_disk, "managed_disk", None + ), + "id", + None, + ) ), ), data_disks=data_disks, @@ -130,32 +150,37 @@ class SecurityProfile: uefi_settings: Optional[UefiSettings] -@dataclass -class OSDisk: - name: Optional[str] - managed_disk: Optional[bool] +class OperatingSystemType(Enum): + WINDOWS = "Windows" + LINUX = "Linux" -@dataclass -class DataDisk: +class ManagedDiskParameters(BaseModel): + id: str + + +class OSDisk(BaseModel): + name: str + operating_system_type: OperatingSystemType + managed_disk: Optional[ManagedDiskParameters] + + +class DataDisk(BaseModel): lun: int name: str - managed_disk: bool + managed_disk: Optional[ManagedDiskParameters] -@dataclass -class StorageProfile: +class StorageProfile(BaseModel): os_disk: Optional[OSDisk] data_disks: List[DataDisk] -@dataclass -class VirtualMachineExtension: +class VirtualMachineExtension(BaseModel): id: str -@dataclass -class VirtualMachine: +class VirtualMachine(BaseModel): resource_id: str resource_name: str location: str @@ -164,8 +189,7 @@ class VirtualMachine: storage_profile: Optional[StorageProfile] = None -@dataclass -class Disk: +class Disk(BaseModel): resource_id: str resource_name: str vms_attached: list[str] diff --git a/prowler/providers/m365/exceptions/exceptions.py b/prowler/providers/m365/exceptions/exceptions.py index fe2b8b442c..7f9dae2c31 100644 --- a/prowler/providers/m365/exceptions/exceptions.py +++ b/prowler/providers/m365/exceptions/exceptions.py @@ -114,6 +114,18 @@ class M365BaseException(ProwlerException): "message": "The provided User does not belong to the specified tenant.", "remediation": "Check the User email domain and ensure it belongs to the specified tenant.", }, + (6027, "M365GraphConnectionError"): { + "message": "Failed to establish connection to Microsoft Graph API.", + "remediation": "Check your Microsoft Application credentials and ensure the app has proper permissions.", + }, + (6028, "M365TeamsConnectionError"): { + "message": "Failed to establish connection to Microsoft Teams API.", + "remediation": "Ensure the application has proper permission granted to access Microsoft Teams.", + }, + (6029, "M365ExchangeConnectionError"): { + "message": "Failed to establish connection to Exchange Online API.", + "remediation": "Ensure the application has proper permission granted to access Exchange Online.", + }, } def __init__(self, code, file=None, original_exception=None, message=None): @@ -324,3 +336,24 @@ class M365UserNotBelongingToTenantError(M365CredentialsError): super().__init__( 6026, file=file, original_exception=original_exception, message=message ) + + +class M365GraphConnectionError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6027, file=file, original_exception=original_exception, message=message + ) + + +class M365TeamsConnectionError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6028, file=file, original_exception=original_exception, message=message + ) + + +class M365ExchangeConnectionError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6029, file=file, original_exception=original_exception, message=message + ) diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index d09e3543f9..a9e3a0fdf9 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -6,6 +6,9 @@ import msal from prowler.lib.logger import logger from prowler.lib.powershell.powershell import PowerShellSession from prowler.providers.m365.exceptions.exceptions import ( + M365ExchangeConnectionError, + M365GraphConnectionError, + M365TeamsConnectionError, M365UserNotBelongingToTenantError, ) from prowler.providers.m365.models import M365Credentials, M365IdentityInfo @@ -65,21 +68,33 @@ class M365PowerShell(PowerShellSession): The credentials are sanitized to prevent command injection and stored securely in the PowerShell session. """ + # User Auth (Will be deprecated in September 2025) + if credentials.user and credentials.passwd: + credentials.encrypted_passwd = self.encrypt_password(credentials.passwd) - credentials.encrypted_passwd = self.encrypt_password(credentials.passwd) + # Sanitize user and password + sanitized_user = self.sanitize(credentials.user) + sanitized_encrypted_passwd = self.sanitize(credentials.encrypted_passwd) - # Sanitize user and password - sanitized_user = self.sanitize(credentials.user) - sanitized_encrypted_passwd = self.sanitize(credentials.encrypted_passwd) - - # Securely convert encrypted password to SecureString - self.execute(f'$user = "{sanitized_user}"') - self.execute( - f'$secureString = "{sanitized_encrypted_passwd}" | ConvertTo-SecureString' - ) - self.execute( - "$credential = New-Object System.Management.Automation.PSCredential ($user, $secureString)" - ) + # Securely convert encrypted password to SecureString + self.execute(f'$user = "{sanitized_user}"') + self.execute( + f'$secureString = "{sanitized_encrypted_passwd}" | ConvertTo-SecureString' + ) + self.execute( + "$credential = New-Object System.Management.Automation.PSCredential ($user, $secureString)" + ) + else: + # Application Auth + self.execute(f'$clientID = "{credentials.client_id}"') + self.execute(f'$clientSecret = "{credentials.client_secret}"') + self.execute(f'$tenantID = "{credentials.tenant_id}"') + self.execute( + '$graphtokenBody = @{ Grant_Type = "client_credentials"; Scope = "https://graph.microsoft.com/.default"; Client_Id = $clientID; Client_Secret = $clientSecret }' + ) + self.execute( + '$graphToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $graphtokenBody | Select-Object -ExpandProperty Access_Token' + ) def encrypt_password(self, password: str) -> str: """ @@ -129,46 +144,151 @@ class M365PowerShell(PowerShellSession): Returns: bool: True if credentials are valid and authentication succeeds, False otherwise. """ - self.execute( - f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString' # encrypted password already sanitized - ) - self.execute( - f'$credential = New-Object System.Management.Automation.PSCredential("{self.sanitize(credentials.user)}", $securePassword)' - ) + if credentials.user and credentials.passwd: + self.execute( + f'$securePassword = "{credentials.encrypted_passwd}" | ConvertTo-SecureString' # encrypted password already sanitized + ) + self.execute( + f'$credential = New-Object System.Management.Automation.PSCredential("{self.sanitize(credentials.user)}", $securePassword)' + ) - # Validate user belongs to tenant - user_domain = credentials.user.split("@")[1] - if not any( - user_domain.endswith(domain) - for domain in self.tenant_identity.tenant_domains - ): - raise M365UserNotBelongingToTenantError( + user_domain = credentials.user.split("@")[1] + if not any( + user_domain.endswith(domain) + for domain in self.tenant_identity.tenant_domains + ): + raise M365UserNotBelongingToTenantError( + file=os.path.basename(__file__), + message=f"The user domain {user_domain} does not match any of the tenant domains: {', '.join(self.tenant_identity.tenant_domains)}", + ) + + app = msal.ConfidentialClientApplication( + client_id=credentials.client_id, + client_credential=credentials.client_secret, + authority=f"https://login.microsoftonline.com/{credentials.tenant_id}", + ) + + # Validate credentials + result = app.acquire_token_by_username_password( + username=credentials.user, + password=credentials.passwd, + scopes=["https://graph.microsoft.com/.default"], + ) + + if result is None: + raise Exception( + "Unexpected error: Acquiring token in behalf of user did not return a result." + ) + + if "access_token" not in result: + raise Exception(f"MsGraph Error {result.get('error_description')}") + + return True + + else: + # Test Microsoft Graph connection + try: + logger.info("Testing Microsoft Graph connection...") + self.test_graph_connection() + logger.info("Microsoft Graph connection successful") + except Exception as e: + logger.error(f"Microsoft Graph connection failed: {e}") + raise M365GraphConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message="Check your Microsoft Application credentials and ensure the app has proper permissions", + ) + + # Test Microsoft Teams connection + try: + logger.info("Testing Microsoft Teams connection...") + self.test_teams_connection() + logger.info("Microsoft Teams connection successful") + except Exception as e: + logger.error(f"Microsoft Teams connection failed: {e}") + raise M365TeamsConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message="Ensure the application has proper permission granted to access Microsoft Teams.", + ) + + # Test Exchange Online connection + try: + logger.info("Testing Exchange Online connection...") + self.test_exchange_connection() + logger.info("Exchange Online connection successful") + except Exception as e: + logger.error(f"Exchange Online connection failed: {e}") + raise M365ExchangeConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message="Ensure the application has proper permission granted to access Exchange Online.", + ) + + return True + + def test_graph_connection(self) -> bool: + """Test Microsoft Graph API connection and raise exception if it fails.""" + try: + if self.execute("Write-Output $graphToken") == "": + raise M365GraphConnectionError( + file=os.path.basename(__file__), + message="Microsoft Graph token is empty or invalid.", + ) + return True + except Exception as e: + logger.error(f"Microsoft Graph connection failed: {e}") + raise M365GraphConnectionError( file=os.path.basename(__file__), - message=f"The user domain {user_domain} does not match any of the tenant domains: {', '.join(self.tenant_identity.tenant_domains)}", + original_exception=e, + message=f"Failed to connect to Microsoft Graph API: {str(e)}", ) - app = msal.ConfidentialClientApplication( - client_id=credentials.client_id, - client_credential=credentials.client_secret, - authority=f"https://login.microsoftonline.com/{credentials.tenant_id}", - ) - - # Validate credentials - result = app.acquire_token_by_username_password( - username=credentials.user, - password=credentials.passwd, - scopes=["https://graph.microsoft.com/.default"], - ) - - if result is None: - raise Exception( - "Unexpected error: Acquiring token in behalf of user did not return a result." + def test_teams_connection(self) -> bool: + """Test Microsoft Teams API connection and raise exception if it fails.""" + try: + self.execute( + '$teamstokenBody = @{ Grant_Type = "client_credentials"; Scope = "48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default"; Client_Id = $clientID; Client_Secret = $clientSecret }' + ) + self.execute( + '$teamsToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $teamstokenBody | Select-Object -ExpandProperty Access_Token' + ) + if self.execute("Write-Output $teamsToken") == "": + raise M365TeamsConnectionError( + file=os.path.basename(__file__), + message="Microsoft Teams token is empty or invalid.", + ) + return True + except Exception as e: + logger.error(f"Microsoft Teams connection failed: {e}") + raise M365TeamsConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message=f"Failed to connect to Microsoft Teams API: {str(e)}", ) - if "access_token" not in result: - raise Exception(f"MsGraph Error {result.get('error_description')}") - - return True + def test_exchange_connection(self) -> bool: + """Test Exchange Online API connection and raise exception if it fails.""" + try: + self.execute( + '$SecureSecret = ConvertTo-SecureString "$clientSecret" -AsPlainText -Force' + ) + self.execute( + '$exchangeToken = Get-MsalToken -clientID "$clientID" -tenantID "$tenantID" -clientSecret $SecureSecret -Scopes "https://outlook.office365.com/.default"' + ) + if self.execute("Write-Output $exchangeToken") == "": + raise M365ExchangeConnectionError( + file=os.path.basename(__file__), + message="Exchange Online token is empty or invalid.", + ) + return True + except Exception as e: + logger.error(f"Exchange Online connection failed: {e}") + raise M365ExchangeConnectionError( + file=os.path.basename(__file__), + original_exception=e, + message=f"Failed to connect to Exchange Online API: {str(e)}", + ) def connect_microsoft_teams(self) -> dict: """ @@ -182,7 +302,18 @@ class M365PowerShell(PowerShellSession): Note: This method requires the Microsoft Teams PowerShell module to be installed. """ - return self.execute("Connect-MicrosoftTeams -Credential $credential") + if self.execute("Write-Output $credential") != "": # User Auth + return self.execute("Connect-MicrosoftTeams -Credential $credential") + else: # Application Auth + self.execute( + '$teamstokenBody = @{ Grant_Type = "client_credentials"; Scope = "48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default"; Client_Id = $clientID; Client_Secret = $clientSecret }' + ) + self.execute( + '$teamsToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $teamstokenBody | Select-Object -ExpandProperty Access_Token' + ) + return self.execute( + 'Connect-MicrosoftTeams -AccessTokens @("$graphToken","$teamsToken")' + ) def get_teams_settings(self) -> dict: """ @@ -276,7 +407,18 @@ class M365PowerShell(PowerShellSession): Note: This method requires the Exchange Online PowerShell module to be installed. """ - return self.execute("Connect-ExchangeOnline -Credential $credential") + if self.execute("Write-Output $credential") != "": # User Auth + return self.execute("Connect-ExchangeOnline -Credential $credential") + else: # Application Auth + self.execute( + '$SecureSecret = ConvertTo-SecureString "$clientSecret" -AsPlainText -Force' + ) + self.execute( + '$exchangeToken = Get-MsalToken -clientID "$clientID" -tenantID "$tenantID" -clientSecret $SecureSecret -Scopes "https://outlook.office365.com/.default"' + ) + return self.execute( + 'Connect-ExchangeOnline -AccessToken $exchangeToken.AccessToken -Organization "$tenantID"' + ) def get_audit_log_config(self) -> dict: """ @@ -758,25 +900,20 @@ def initialize_m365_powershell_modules(): bool: True if all modules were successfully initialized, False otherwise """ - REQUIRED_MODULES = [ - "ExchangeOnlineManagement", - "MicrosoftTeams", - ] + REQUIRED_MODULES = ["ExchangeOnlineManagement", "MicrosoftTeams", "MSAL.PS"] pwsh = PowerShellSession() try: for module in REQUIRED_MODULES: try: # Check if module is already installed - result = pwsh.execute( - f"Get-Module -ListAvailable -Name {module}", timeout=5 - ) + result = pwsh.execute(f"Get-Module -ListAvailable {module}", timeout=5) # Install module if not installed if not result: install_result = pwsh.execute( - f'Install-Module -Name "{module}" -Force -AllowClobber -Scope CurrentUser', - timeout=30, + f'Install-Module "{module}" -Force -AllowClobber -Scope CurrentUser', + timeout=60, ) if install_result: logger.warning( @@ -786,7 +923,7 @@ def initialize_m365_powershell_modules(): logger.info(f"Successfully installed module {module}") # Import module - pwsh.execute(f'Import-Module -Name "{module}" -Force', timeout=1) + pwsh.execute(f'Import-Module "{module}" -Force', timeout=1) except Exception as error: logger.error(f"Failed to initialize module {module}: {str(error)}") diff --git a/prowler/providers/m365/m365_provider.py b/prowler/providers/m365/m365_provider.py index ea6cd5a5c0..80c65dd3d5 100644 --- a/prowler/providers/m365/m365_provider.py +++ b/prowler/providers/m365/m365_provider.py @@ -43,9 +43,7 @@ from prowler.providers.m365.exceptions.exceptions import ( M365NotTenantIdButClientIdAndClientSecretError, M365NotValidClientIdError, M365NotValidClientSecretError, - M365NotValidPasswordError, M365NotValidTenantIdError, - M365NotValidUserError, M365SetUpRegionConfigError, M365SetUpSessionError, M365TenantIdAndClientIdNotBelongingToClientSecretError, @@ -172,7 +170,7 @@ class M365Provider(Provider): # Get the dict from the static credentials m365_credentials = None - if tenant_id and client_id and client_secret and user and password: + if tenant_id and client_id and client_secret: m365_credentials = self.validate_static_credentials( tenant_id=tenant_id, client_id=client_id, @@ -204,6 +202,7 @@ class M365Provider(Provider): # Set up PowerShell session credentials self._credentials = self.setup_powershell( env_auth=env_auth, + sp_env_auth=sp_env_auth, m365_credentials=m365_credentials, identity=self.identity, init_modules=init_modules, @@ -378,6 +377,7 @@ class M365Provider(Provider): @staticmethod def setup_powershell( env_auth: bool = False, + sp_env_auth: bool = False, m365_credentials: dict = {}, identity: M365IdentityInfo = None, init_modules: bool = False, @@ -392,12 +392,13 @@ class M365Provider(Provider): If env_auth is True, retrieves from environment variables. If False, returns empty credentials. """ + logger.info("M365 provider: Setting up PowerShell session...") credentials = None if m365_credentials: credentials = M365Credentials( - user=m365_credentials.get("user", ""), - passwd=m365_credentials.get("password", ""), + user=m365_credentials.get("user", None), + passwd=m365_credentials.get("password", None), client_id=m365_credentials.get("client_id", ""), client_secret=m365_credentials.get("client_secret", ""), tenant_id=m365_credentials.get("tenant_id", ""), @@ -418,6 +419,7 @@ class M365Provider(Provider): file=os.path.basename(__file__), message="Missing M365_USER or M365_PASSWORD environment variables required for credentials authentication.", ) + credentials = M365Credentials( client_id=client_id, client_secret=client_secret, @@ -427,14 +429,25 @@ class M365Provider(Provider): passwd=m365_password, ) + elif sp_env_auth: + client_id = getenv("AZURE_CLIENT_ID") + client_secret = getenv("AZURE_CLIENT_SECRET") + tenant_id = getenv("AZURE_TENANT_ID") + credentials = M365Credentials( + client_id=client_id, + client_secret=client_secret, + tenant_id=tenant_id, + tenant_domains=identity.tenant_domains, + ) + if credentials: - if identity: + if identity and credentials.user: identity.user = credentials.user test_session = M365PowerShell(credentials, identity) try: + if init_modules: + initialize_m365_powershell_modules() if test_session.test_credentials(credentials): - if init_modules: - initialize_m365_powershell_modules() return credentials raise M365UserCredentialsError( file=os.path.basename(__file__), @@ -523,6 +536,8 @@ class M365Provider(Provider): tenant_id=m365_credentials["tenant_id"], client_id=m365_credentials["client_id"], client_secret=m365_credentials["client_secret"], + user=m365_credentials["user"], + password=m365_credentials["password"], ) return credentials except ClientAuthenticationError as error: @@ -688,8 +703,8 @@ class M365Provider(Provider): tenant_id=tenant_id, client_id=client_id, client_secret=client_secret, - user="user", - password="password", + user=None, + password=None, ) else: m365_credentials = M365Provider.validate_static_credentials( @@ -739,17 +754,13 @@ class M365Provider(Provider): logger.info("M365 provider: Identity retrieved successfully") # Set up PowerShell credentials - if user and password: - M365Provider.setup_powershell( - env_auth, - m365_credentials, - identity, - ) - logger.info("M365 provider: Connection to PowerShell successful") - else: - logger.info( - "M365 provider: Connection to PowerShell has not been requested" - ) + M365Provider.setup_powershell( + env_auth, + sp_env_auth, + m365_credentials, + identity, + ) + logger.info("M365 provider: Connection to PowerShell successful") return Connection(is_connected=True) @@ -1030,20 +1041,6 @@ class M365Provider(Provider): message="The provided Client Secret is not valid.", ) - # Validate the User - if not user: - raise M365NotValidUserError( - file=os.path.basename(__file__), - message="The provided User is not valid.", - ) - - # Validate the Password - if not password: - raise M365NotValidPasswordError( - file=os.path.basename(__file__), - message="The provided Password is not valid.", - ) - try: M365Provider.verify_client(tenant_id, client_id, client_secret) return { diff --git a/prowler/providers/m365/models.py b/prowler/providers/m365/models.py index 6f9dc0e8c8..ff2cec2fd5 100644 --- a/prowler/providers/m365/models.py +++ b/prowler/providers/m365/models.py @@ -24,9 +24,9 @@ class M365RegionConfig(BaseModel): class M365Credentials(BaseModel): - user: str = "" - passwd: str = "" - encrypted_passwd: str = "" + user: Optional[str] = None + passwd: Optional[str] = None + encrypted_passwd: Optional[str] = None client_id: str = "" client_secret: str = "" tenant_id: str = "" diff --git a/prowler/providers/m365/services/exchange/exchange_service.py b/prowler/providers/m365/services/exchange/exchange_service.py index d8b0fbfa3a..07bbbe2ae6 100644 --- a/prowler/providers/m365/services/exchange/exchange_service.py +++ b/prowler/providers/m365/services/exchange/exchange_service.py @@ -260,7 +260,7 @@ class ExternalMailConfig(BaseModel): class TransportRule(BaseModel): name: str scl: Optional[int] - sender_domain_is: list[str] + sender_domain_is: Optional[list[str]] redirect_message_to: Optional[list[str]] diff --git a/pyproject.toml b/pyproject.toml index 17f8ce4df8..5d9d90c879 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"} name = "prowler" readme = "README.md" requires-python = ">3.9.1,<3.13" -version = "5.8.0" +version = "5.9.0" [project.scripts] prowler = "prowler.__main__:prowler" diff --git a/tests/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/__init__.py b/tests/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible_test.py b/tests/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible_test.py new file mode 100644 index 0000000000..f94146d4fc --- /dev/null +++ b/tests/providers/aws/services/codebuild/codebuild_project_not_publicly_accessible/codebuild_project_not_publicly_accessible_test.py @@ -0,0 +1,176 @@ +from unittest import mock + +from prowler.providers.aws.services.codebuild.codebuild_service import Project + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_codebuild_project_not_publicly_accessible: + def test_project_public(self): + codebuild_client = mock.MagicMock + project_name = "test-project" + project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}" + codebuild_client.projects = { + project_arn: Project( + name=project_name, + arn=project_arn, + region="eu-west-1", + project_visibility="PUBLIC", + tags=[], + ) + } + + with ( + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_service.Codebuild", + codebuild_client, + ), + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client", + codebuild_client, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import ( + codebuild_project_not_publicly_accessible, + ) + + check = codebuild_project_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} is public." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION + + def test_project_private(self): + codebuild_client = mock.MagicMock + project_name = "test-project" + project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}" + codebuild_client.projects = { + project_arn: Project( + name=project_name, + arn=project_arn, + region="eu-west-1", + project_visibility="PRIVATE", + tags=[], + ) + } + + with ( + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_service.Codebuild", + codebuild_client, + ), + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client", + codebuild_client, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import ( + codebuild_project_not_publicly_accessible, + ) + + check = codebuild_project_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} is private." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION + + def test_project_no_visibility_set(self): + codebuild_client = mock.MagicMock + project_name = "test-project" + project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}" + codebuild_client.projects = { + project_arn: Project( + name=project_name, + arn=project_arn, + region="eu-west-1", + project_visibility=None, + tags=[], + ) + } + + with ( + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_service.Codebuild", + codebuild_client, + ), + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client", + codebuild_client, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import ( + codebuild_project_not_publicly_accessible, + ) + + check = codebuild_project_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} is public." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION + + def test_project_empty_visibility(self): + codebuild_client = mock.MagicMock + project_name = "test-project" + project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}" + codebuild_client.projects = { + project_arn: Project( + name=project_name, + arn=project_arn, + region="eu-west-1", + project_visibility="", + tags=[], + ) + } + + with ( + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_service.Codebuild", + codebuild_client, + ), + mock.patch( + "prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client", + codebuild_client, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import ( + codebuild_project_not_publicly_accessible, + ) + + check = codebuild_project_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} is public." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION diff --git a/tests/providers/aws/services/codebuild/codebuild_service_test.py b/tests/providers/aws/services/codebuild/codebuild_service_test.py index f35c55ed58..9884b3a09f 100644 --- a/tests/providers/aws/services/codebuild/codebuild_service_test.py +++ b/tests/providers/aws/services/codebuild/codebuild_service_test.py @@ -28,6 +28,7 @@ build_id = "test:93f838a7-cd20-48ae-90e5-c10fbbc78ca6" last_invoked_time = datetime.now() - timedelta(days=2) bitbucket_url = "https://bitbucket.org/example/repo.git" secondary_bitbucket_url = "https://bitbucket.org/example/secondary-repo.git" +project_visibility = "PRIVATE" report_group_arn = f"arn:{AWS_COMMERCIAL_PARTITION}:codebuild:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:report-group/{project_name}" @@ -71,6 +72,7 @@ def mock_make_api_call(self, operation_name, kwarg): }, }, "tags": [{"key": "Name", "value": project_name}], + "projectVisibility": project_visibility, } ] } @@ -152,6 +154,7 @@ class Test_Codebuild_Service: ) assert codebuild.projects[project_arn].tags[0]["key"] == "Name" assert codebuild.projects[project_arn].tags[0]["value"] == project_name + assert codebuild.projects[project_arn].project_visibility == project_visibility # Asserttions related with report groups assert len(codebuild.report_groups) == 1 assert isinstance(codebuild.report_groups, dict) diff --git a/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py b/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py index 78e3b38553..6a9576050c 100644 --- a/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py +++ b/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py @@ -232,3 +232,179 @@ class Test_iam_no_custom_policy_permissive_role_assumption: result[1].status_extended, ) assert result[1].resource_id == policy_name_permissive + + @mock_aws + def test_policy_resource_with_embedded_wildcard_in_arn(self): + iam_client = client("iam") + policy_name = "policy_with_wildcard_in_arn" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/eks-terraform-*", + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + assert result[0].resource_id == policy_name + + @mock_aws + def test_policy_resource_list_containing_wildcard(self): + iam_client = client("iam") + policy_name = "policy_with_resource_list" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": ["arn:aws:iam::123456789012:role/SomeRole", "*"], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + assert result[0].resource_id == policy_name + + @mock_aws + def test_policy_resource_list_with_arn_wildcards_should_fail(self): + iam_client = client("iam") + policy_name = "policy_list_wildcard_arn" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [ + "arn:aws:iam::123456789012:role/eks-admin", + "arn:aws:iam::*:role/eks-*", + ], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + + @mock_aws + def test_policy_resource_list_with_only_wildcarded_arns_should_fail(self): + iam_client = client("iam") + policy_name = "policy_list_scoped_wildcards_only" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [ + "arn:aws:iam::*:role/team-*", + "arn:aws:iam::*:role/dev-*", + ], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) diff --git a/tests/providers/azure/services/vm/vm_ensure_attached_disks_encrypted_with_cmk/vm_ensure_attached_disks_encrypted_with_cmk_test.py b/tests/providers/azure/services/vm/vm_ensure_attached_disks_encrypted_with_cmk/vm_ensure_attached_disks_encrypted_with_cmk_test.py index ccf6e8f175..1eb8da64c4 100644 --- a/tests/providers/azure/services/vm/vm_ensure_attached_disks_encrypted_with_cmk/vm_ensure_attached_disks_encrypted_with_cmk_test.py +++ b/tests/providers/azure/services/vm/vm_ensure_attached_disks_encrypted_with_cmk/vm_ensure_attached_disks_encrypted_with_cmk_test.py @@ -54,15 +54,15 @@ class Test_vm_ensure_attached_disks_encrypted_with_cmk: assert len(result) == 0 def test_vm_subscription_one_disk_attached_encrypt_pk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { disk_id: Disk( resource_id=resource_id, resource_name="test-disk", - vms_attached=[uuid4()], + vms_attached=[str(uuid4())], encryption_type="EncryptionAtRestWithPlatformKey", location="location", ) @@ -97,15 +97,15 @@ class Test_vm_ensure_attached_disks_encrypted_with_cmk: ) def test_vm_subscription_one_disk_attached_encrypt_cmk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { disk_id: Disk( resource_id=resource_id, resource_name="test-disk", - vms_attached=[uuid4()], + vms_attached=[str(uuid4())], encryption_type="EncryptionAtRestWithCustomerKey", location="location", ) @@ -140,24 +140,24 @@ class Test_vm_ensure_attached_disks_encrypted_with_cmk: ) def test_vm_subscription_two_disk_attached_encrypt_cmk_and_pk(self): - disk_id_1 = uuid4() - resource_id_1 = uuid4() - disk_id_2 = uuid4() - resource_id_2 = uuid4() + disk_id_1 = str(uuid4()) + resource_id_1 = str(uuid4()) + disk_id_2 = str(uuid4()) + resource_id_2 = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { disk_id_1: Disk( resource_id=resource_id_1, resource_name="test-disk", - vms_attached=[uuid4()], + vms_attached=[str(uuid4())], encryption_type="EncryptionAtRestWithPlatformKey", location="location", ), disk_id_2: Disk( resource_id=resource_id_2, resource_name="test-disk-2", - vms_attached=[uuid4(), uuid4()], + vms_attached=[str(uuid4()), str(uuid4())], encryption_type="EncryptionAtRestWithCustomerKey", location="location2", ), @@ -200,8 +200,8 @@ class Test_vm_ensure_attached_disks_encrypted_with_cmk: ) def test_vm_unattached_disk_encrypt_cmk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { @@ -209,8 +209,8 @@ class Test_vm_ensure_attached_disks_encrypted_with_cmk: resource_id=resource_id, resource_name="test-disk", vms_attached=[], - location="location", encryption_type="EncryptionAtRestWithCustomerKey", + location="location", ) } } diff --git a/tests/providers/azure/services/vm/vm_ensure_unattached_disks_encrypted_with_cmk/vm_ensure_unattached_disks_encrypted_with_cmk_test.py b/tests/providers/azure/services/vm/vm_ensure_unattached_disks_encrypted_with_cmk/vm_ensure_unattached_disks_encrypted_with_cmk_test.py index 44ec78b4ed..78d7920666 100644 --- a/tests/providers/azure/services/vm/vm_ensure_unattached_disks_encrypted_with_cmk/vm_ensure_unattached_disks_encrypted_with_cmk_test.py +++ b/tests/providers/azure/services/vm/vm_ensure_unattached_disks_encrypted_with_cmk/vm_ensure_unattached_disks_encrypted_with_cmk_test.py @@ -54,8 +54,8 @@ class Test_vm_ensure_unattached_disks_encrypted_with_cmk: assert len(result) == 0 def test_vm_one_unattached_disk_encrypt_pk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { @@ -97,8 +97,8 @@ class Test_vm_ensure_unattached_disks_encrypted_with_cmk: ) def test_vm_one_unattached_disk_encrypt_cmk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { @@ -140,10 +140,10 @@ class Test_vm_ensure_unattached_disks_encrypted_with_cmk: ) def test_vm_subscription_two_unattached_disk_encrypt_cmk_and_pk(self): - disk_id_1 = uuid4() - resource_id_1 = uuid4() - disk_id_2 = uuid4() - resource_id_2 = uuid4() + disk_id_1 = str(uuid4()) + resource_id_1 = str(uuid4()) + disk_id_2 = str(uuid4()) + resource_id_2 = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { @@ -200,17 +200,17 @@ class Test_vm_ensure_unattached_disks_encrypted_with_cmk: ) def test_vm_attached_disk_encrypt_cmk(self): - disk_id = uuid4() - resource_id = uuid4() + disk_id = str(uuid4()) + resource_id = str(uuid4()) vm_client = mock.MagicMock vm_client.disks = { AZURE_SUBSCRIPTION_ID: { disk_id: Disk( resource_id=resource_id, resource_name="test-disk", - location="location", - vms_attached=[uuid4()], + vms_attached=[str(uuid4())], encryption_type="EncryptionAtRestWithCustomerKey", + location="location", ) } } diff --git a/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py b/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py index dbc04dd53f..b73075337e 100644 --- a/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py +++ b/tests/providers/azure/services/vm/vm_ensure_using_managed_disks/vm_ensure_using_managed_disks_test.py @@ -1,7 +1,15 @@ from unittest import mock from uuid import uuid4 -from prowler.providers.azure.services.vm.vm_service import VirtualMachine +from prowler.providers.azure.services.vm.vm_service import ( + DataDisk, + ManagedDiskParameters, + OSDisk, + SecurityProfile, + StorageProfile, + UefiSettings, + VirtualMachine, +) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, set_mocked_azure_provider, @@ -62,18 +70,19 @@ class Test_vm_ensure_using_managed_disks: resource_id=vm_id, resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=True, v_tpm_enabled=True, ), ), extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", - managed_disk=mock.MagicMock(id="managed_disk_id"), + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters(id="managed_disk_id"), ), data_disks=[], ), @@ -117,17 +126,18 @@ class Test_vm_ensure_using_managed_disks: resource_id=vm_id, resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=True, v_tpm_enabled=True, ), ), extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", managed_disk=None, ), data_disks=[], @@ -172,20 +182,23 @@ class Test_vm_ensure_using_managed_disks: resource_id=vm_id, resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=True, v_tpm_enabled=True, ), ), extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", - managed_disk=mock.MagicMock(id="managed_disk_id"), + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters(id="managed_disk_id"), ), - data_disks=[mock.MagicMock(managed_disk=None)], + data_disks=[ + DataDisk(lun=0, name="data_disk_1", managed_disk=None) + ], ), ) } diff --git a/tests/providers/azure/services/vm/vm_service_test.py b/tests/providers/azure/services/vm/vm_service_test.py index 6e0f53e2e5..78aaccda2a 100644 --- a/tests/providers/azure/services/vm/vm_service_test.py +++ b/tests/providers/azure/services/vm/vm_service_test.py @@ -1,10 +1,13 @@ -from unittest import mock from unittest.mock import patch -from azure.mgmt.compute.models import ManagedDiskParameters, OSDisk, StorageProfile - from prowler.providers.azure.services.vm.vm_service import ( Disk, + ManagedDiskParameters, + OperatingSystemType, + OSDisk, + SecurityProfile, + StorageProfile, + UefiSettings, VirtualMachine, VirtualMachines, ) @@ -21,9 +24,9 @@ def mock_vm_get_virtual_machines(_): resource_id="/subscriptions/resource_id", resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=True, v_tpm_enabled=True, ), @@ -31,7 +34,8 @@ def mock_vm_get_virtual_machines(_): extensions=[], storage_profile=StorageProfile( os_disk=OSDisk( - create_option="FromImage", + name="os_disk_name", + operating_system_type=OperatingSystemType.LINUX, managed_disk=ManagedDiskParameters(id="managed_disk_id"), ), data_disks=[], @@ -49,7 +53,7 @@ def mock_vm_get_virtual_machines_with_none(_): resource_name="VMWithNoneValues", location="location", security_profile=None, - extensions=None, + extensions=[], storage_profile=None, ), "vm_id-2": VirtualMachine( @@ -57,10 +61,10 @@ def mock_vm_get_virtual_machines_with_none(_): resource_name="VMWithPartialNone", location="location", security_profile=None, - extensions=None, + extensions=[], storage_profile=StorageProfile( os_disk=None, - data_disks=None, + data_disks=[], ), ), } @@ -180,5 +184,5 @@ class Test_VirtualMachines_NoneCases: virtual_machines = VirtualMachines(set_mocked_azure_provider()) vm_2 = virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID]["vm_id-2"] assert vm_2.storage_profile.os_disk is None - assert vm_2.storage_profile.data_disks is None + assert vm_2.storage_profile.data_disks == [] assert vm_2.resource_name == "VMWithPartialNone" diff --git a/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py b/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py index 05e96a5c1d..83ab63acce 100644 --- a/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py +++ b/tests/providers/azure/services/vm/vm_trusted_launch_enabled/vm_trusted_launch_enabled_test.py @@ -1,6 +1,14 @@ from unittest import mock from uuid import uuid4 +from prowler.providers.azure.services.vm.vm_service import ( + ManagedDiskParameters, + OSDisk, + SecurityProfile, + StorageProfile, + UefiSettings, + VirtualMachine, +) from tests.providers.azure.azure_fixtures import ( AZURE_SUBSCRIPTION_ID, set_mocked_azure_provider, @@ -63,7 +71,6 @@ class Test_vm_trusted_launch_enabled: new=vm_client, ), ): - from prowler.providers.azure.services.vm.vm_service import VirtualMachine from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( vm_trusted_launch_enabled, ) @@ -74,18 +81,21 @@ class Test_vm_trusted_launch_enabled: resource_id=vm_id, resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=True, v_tpm_enabled=True, ), ), extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", - managed_disk=mock.MagicMock(id="managed_disk_id"), + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters( + id="managed_disk_id" + ), ), data_disks=[], ), @@ -117,7 +127,6 @@ class Test_vm_trusted_launch_enabled: new=vm_client, ), ): - from prowler.providers.azure.services.vm.vm_service import VirtualMachine from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( vm_trusted_launch_enabled, ) @@ -128,18 +137,21 @@ class Test_vm_trusted_launch_enabled: resource_id=vm_id, resource_name="VMTest", location="location", - security_profile=mock.MagicMock( + security_profile=SecurityProfile( security_type="TrustedLaunch", - uefi_settings=mock.MagicMock( + uefi_settings=UefiSettings( secure_boot_enabled=False, v_tpm_enabled=False, ), ), extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", - managed_disk=mock.MagicMock(id="managed_disk_id"), + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters( + id="managed_disk_id" + ), ), data_disks=[], ), @@ -172,7 +184,6 @@ class Test_vm_trusted_launch_enabled: new=vm_client, ), ): - from prowler.providers.azure.services.vm.vm_service import VirtualMachine from prowler.providers.azure.services.vm.vm_trusted_launch_enabled.vm_trusted_launch_enabled import ( vm_trusted_launch_enabled, ) @@ -185,10 +196,13 @@ class Test_vm_trusted_launch_enabled: location="location", security_profile=None, extensions=[], - storage_profile=mock.MagicMock( - os_disk=mock.MagicMock( - create_option="FromImage", - managed_disk=mock.MagicMock(id="managed_disk_id"), + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters( + id="managed_disk_id" + ), ), data_disks=[], ), diff --git a/tests/providers/m365/lib/powershell/m365_powershell_test.py b/tests/providers/m365/lib/powershell/m365_powershell_test.py index 1fa4753dad..f02f283d1f 100644 --- a/tests/providers/m365/lib/powershell/m365_powershell_test.py +++ b/tests/providers/m365/lib/powershell/m365_powershell_test.py @@ -4,6 +4,9 @@ import pytest from prowler.lib.powershell.powershell import PowerShellSession from prowler.providers.m365.exceptions.exceptions import ( + M365ExchangeConnectionError, + M365GraphConnectionError, + M365TeamsConnectionError, M365UserNotBelongingToTenantError, ) from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell @@ -97,7 +100,7 @@ class Testm365PowerShell: session.init_credential(credentials) # Verify encrypt_password was called - session.encrypt_password.assert_called_once_with(credentials.passwd) + session.encrypt_password.assert_any_call(credentials.passwd) # Verify execute was called with the correct commands session.execute.assert_any_call(f'$user = "{credentials.user}"') @@ -167,6 +170,37 @@ class Testm365PowerShell: ) session.close() + @patch("subprocess.Popen") + def test_test_credentials_application_auth(self, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + with patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365Credentials", + autospec=True, + ) as mock_creds: + credentials = mock_creds.return_value + credentials.user = "" + credentials.passwd = "" + credentials.encrypted_passwd = "" + credentials.client_id = "test_client_id" + credentials.client_secret = "test_client_secret" + credentials.tenant_id = "test_tenant_id" + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="contoso.onmicrosoft.com", + tenant_domains=["contoso.onmicrosoft.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + session.execute = MagicMock(return_value="sometoken") + + result = session.test_credentials(credentials) + assert result is True + session.execute.assert_any_call("Write-Output $graphToken") + session.close() + @patch("subprocess.Popen") @patch("msal.ConfidentialClientApplication") def test_test_credentials_user_not_belonging_to_tenant(self, mock_msal, mock_popen): @@ -514,12 +548,13 @@ class Testm365PowerShell: # Verify successful initialization assert result is True # Verify that execute was called for each module - assert mock_execute_obj.call_count == 6 # 2 modules * 3 commands each + assert mock_execute_obj.call_count == 9 # 3 modules * 3 commands each # Verify success messages were logged mock_info.assert_any_call( "Successfully installed module ExchangeOnlineManagement" ) mock_info.assert_any_call("Successfully installed module MicrosoftTeams") + mock_info.assert_any_call("Successfully installed module MSAL.PS") @patch("subprocess.Popen") def test_initialize_m365_powershell_modules_failure(self, mock_popen): @@ -582,11 +617,12 @@ class Testm365PowerShell: main() # Verify all info messages were logged in the correct order - assert mock_info.call_count == 3 + assert mock_info.call_count == 4 mock_info.assert_has_calls( [ call("Successfully installed module ExchangeOnlineManagement"), call("Successfully installed module MicrosoftTeams"), + call("Successfully installed module MSAL.PS"), call("M365 PowerShell modules initialized successfully"), ] ) @@ -629,6 +665,260 @@ class Testm365PowerShell: # Verify no info messages were logged mock_info.assert_not_called() + @patch("subprocess.Popen") + def test_test_graph_connection_success(self, mock_popen): + """Test test_graph_connection when token is valid""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return a valid token + session.execute = MagicMock(return_value="valid_token") + + result = session.test_graph_connection() + + assert result is True + session.execute.assert_called_once_with("Write-Output $graphToken") + session.close() + + @patch("subprocess.Popen") + def test_test_graph_connection_empty_token(self, mock_popen): + """Test test_graph_connection when token is empty""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return empty token + session.execute = MagicMock(return_value="") + + with pytest.raises(M365GraphConnectionError) as exc_info: + session.test_graph_connection() + + assert "Microsoft Graph token is empty or invalid" in str(exc_info.value) + session.execute.assert_called_once_with("Write-Output $graphToken") + session.close() + + @patch("subprocess.Popen") + def test_test_graph_connection_exception(self, mock_popen): + """Test test_graph_connection when an exception occurs""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to raise an exception + session.execute = MagicMock(side_effect=Exception("PowerShell error")) + + with pytest.raises(M365GraphConnectionError) as exc_info: + session.test_graph_connection() + + assert "Failed to connect to Microsoft Graph API: PowerShell error" in str( + exc_info.value + ) + session.close() + + @patch("subprocess.Popen") + def test_test_teams_connection_success(self, mock_popen): + """Test test_teams_connection when token is valid""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return valid responses + def mock_execute(command, *args, **kwargs): + if "Write-Output $teamsToken" in command: + return "valid_teams_token" + return None + + session.execute = MagicMock(side_effect=mock_execute) + + result = session.test_teams_connection() + + assert result is True + # Verify all expected PowerShell commands were called + assert session.execute.call_count == 3 + session.close() + + @patch("subprocess.Popen") + def test_test_teams_connection_empty_token(self, mock_popen): + """Test test_teams_connection when token is empty""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return empty token when checking + def mock_execute(command, *args, **kwargs): + if "Write-Output $teamsToken" in command: + return "" + return None + + session.execute = MagicMock(side_effect=mock_execute) + + with pytest.raises(M365TeamsConnectionError) as exc_info: + session.test_teams_connection() + + assert "Microsoft Teams token is empty or invalid" in str(exc_info.value) + session.close() + + @patch("subprocess.Popen") + def test_test_teams_connection_exception(self, mock_popen): + """Test test_teams_connection when an exception occurs""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to raise an exception + session.execute = MagicMock(side_effect=Exception("Teams API error")) + + with pytest.raises(M365TeamsConnectionError) as exc_info: + session.test_teams_connection() + + assert "Failed to connect to Microsoft Teams API: Teams API error" in str( + exc_info.value + ) + session.close() + + @patch("subprocess.Popen") + def test_test_exchange_connection_success(self, mock_popen): + """Test test_exchange_connection when token is valid""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return valid responses + def mock_execute(command, *args, **kwargs): + if "Write-Output $exchangeToken" in command: + return "valid_exchange_token" + return None + + session.execute = MagicMock(side_effect=mock_execute) + + result = session.test_exchange_connection() + + assert result is True + # Verify all expected PowerShell commands were called + assert session.execute.call_count == 3 + session.close() + + @patch("subprocess.Popen") + def test_test_exchange_connection_empty_token(self, mock_popen): + """Test test_exchange_connection when token is empty""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to return empty token when checking + def mock_execute(command, *args, **kwargs): + if "Write-Output $exchangeToken" in command: + return "" + return None + + session.execute = MagicMock(side_effect=mock_execute) + + with pytest.raises(M365ExchangeConnectionError) as exc_info: + session.test_exchange_connection() + + assert "Exchange Online token is empty or invalid" in str(exc_info.value) + session.close() + + @patch("subprocess.Popen") + def test_test_exchange_connection_exception(self, mock_popen): + """Test test_exchange_connection when an exception occurs""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + identity = M365IdentityInfo( + identity_id="test_id", + identity_type="Application", + tenant_id="test_tenant", + tenant_domain="example.com", + tenant_domains=["example.com"], + location="test_location", + ) + session = M365PowerShell(credentials, identity) + + # Mock execute to raise an exception + session.execute = MagicMock(side_effect=Exception("Exchange API error")) + + with pytest.raises(M365ExchangeConnectionError) as exc_info: + session.test_exchange_connection() + + assert "Failed to connect to Exchange Online API: Exchange API error" in str( + exc_info.value + ) + session.close() + @patch("subprocess.Popen") def test_encrypt_password(self, mock_popen): credentials = M365Credentials(user="test@example.com", passwd="test_password") diff --git a/tests/providers/m365/m365_provider_test.py b/tests/providers/m365/m365_provider_test.py index 457173dc44..0f629d21a9 100644 --- a/tests/providers/m365/m365_provider_test.py +++ b/tests/providers/m365/m365_provider_test.py @@ -76,6 +76,16 @@ class TestM365Provider: location=LOCATION, ), ), + patch( + "prowler.providers.m365.m365_provider.M365Provider.setup_powershell", + return_value=M365Credentials( + client_id=CLIENT_ID, + tenant_id=TENANT_ID, + client_secret=CLIENT_SECRET, + user="", + passwd="", + ), + ), ): m365_provider = M365Provider( sp_env_auth=True, @@ -490,27 +500,6 @@ class TestM365Provider: assert result.user == credentials_dict["user"] assert result.passwd == credentials_dict["password"] - def test_setup_powershell_invalid_env_credentials(self): - credentials = None - - with patch( - "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell" - ) as mock_powershell: - mock_session = MagicMock() - mock_session.test_credentials.return_value = False - mock_powershell.return_value = mock_session - - with pytest.raises(M365MissingEnvironmentCredentialsError) as exc_info: - M365Provider.setup_powershell( - env_auth=True, m365_credentials=credentials - ) - - assert ( - "Missing M365_USER or M365_PASSWORD environment variables required for credentials authentication" - in str(exc_info.value) - ) - mock_session.test_credentials.assert_not_called() - def test_test_connection_user_not_belonging_to_tenant( self, ): @@ -572,28 +561,6 @@ class TestM365Provider: ) assert "The provided Client Secret is not valid." in str(exception.value) - def test_validate_static_credentials_missing_user(self): - with pytest.raises(M365NotValidUserError) as exception: - M365Provider.validate_static_credentials( - tenant_id="12345678-1234-5678-1234-567812345678", - client_id="12345678-1234-5678-1234-567812345678", - client_secret="test_secret", - user="", - password="test_password", - ) - assert "The provided User is not valid." in str(exception.value) - - def test_validate_static_credentials_missing_password(self): - with pytest.raises(M365NotValidPasswordError) as exception: - M365Provider.validate_static_credentials( - tenant_id="12345678-1234-5678-1234-567812345678", - client_id="12345678-1234-5678-1234-567812345678", - client_secret="test_secret", - user="test@example.com", - password="", - ) - assert "The provided Password is not valid." in str(exception.value) - def test_validate_arguments_missing_env_credentials(self): with pytest.raises(M365MissingEnvironmentCredentialsError) as exception: M365Provider.validate_arguments( diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 917c9aaf81..65d61384c5 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -2,7 +2,15 @@ All notable changes to the **Prowler UI** are documented in this file. -## [v1.8.0] (Prowler v5.8.0) – Not released +## [v1.9.0] (Prowler v5.9.0) – UNRELEASED + +### 🚀 Added +### 🔄 Changed +### 🐞 Fixed + +--- + +## [v1.8.0] (Prowler v5.8.0) ### 🚀 Added @@ -15,13 +23,13 @@ All notable changes to the **Prowler UI** are documented in this file. - Compliance detail view: CIS [(#7913)](https://github.com/prowler-cloud/prowler/pull/7913) - Compliance detail view: AWS Well-Architected Framework [(#7925)](https://github.com/prowler-cloud/prowler/pull/7925) - Compliance detail view: KISA [(#7965)](https://github.com/prowler-cloud/prowler/pull/7965) -- Compliance detail view: ProwlerThreatScore [(#7966)](https://github.com/prowler-cloud/prowler/pull/7966) +- Compliance detail view: ProwlerThreatScore [(#7979)](https://github.com/prowler-cloud/prowler/pull/7979) - Compliance detail view: Generic (rest of the compliances) [(#7990)](https://github.com/prowler-cloud/prowler/pull/7990) - Compliance detail view: MITRE ATTACK [(#8002)](https://github.com/prowler-cloud/prowler/pull/8002) -- Improve `Scan ID` filter by adding more context and enhancing the UI/UX [(#7979)](https://github.com/prowler-cloud/prowler/pull/7979) +- Improve `Scan ID` filter by adding more context and enhancing the UI/UX [(#8046)](https://github.com/prowler-cloud/prowler/pull/8046) - Lighthouse chat interface [(#7878)](https://github.com/prowler-cloud/prowler/pull/7878) - Google Tag Manager integration [(#8058)](https://github.com/prowler-cloud/prowler/pull/8058) -- SAML login integration [(#8094)](https://github.com/prowler-cloud/prowler/pull/8094) + ### 🔄 Changed @@ -29,8 +37,11 @@ All notable changes to the **Prowler UI** are documented in this file. - Aligned Next.js version to `v14.2.29` across Prowler and Cloud environments for consistency and improved maintainability [(#7962)](https://github.com/prowler-cloud/prowler/pull/7962) - Refactor credentials forms with reusable components and error handling [(#7988)](https://github.com/prowler-cloud/prowler/pull/7988) - Updated the provider details section in Scan and Findings detail pages [(#7968)](https://github.com/prowler-cloud/prowler/pull/7968) +- Make user and password fields optional but mutually required for M365 cloud provider [(#8044)](https://github.com/prowler-cloud/prowler/pull/8044) - Improve filter behaviour and relationships between filters in findings page [(#8046)](https://github.com/prowler-cloud/prowler/pull/8046) - Set filters panel to be always open by default [(#8085)](https://github.com/prowler-cloud/prowler/pull/8085) +- Updated "Sign in"/"Sign up" capitalization for consistency [(#8136)](https://github.com/prowler-cloud/prowler/pull/8136) +- Duplicate API base URL as an env var to make it accessible in client components [(#8131)](https://github.com/prowler-cloud/prowler/pull/8131) ### 🐞 Fixed diff --git a/ui/Dockerfile b/ui/Dockerfile index a4c4d793f5..da920e52fe 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -30,6 +30,8 @@ ARG NEXT_PUBLIC_PROWLER_RELEASE_VERSION ENV NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${NEXT_PUBLIC_PROWLER_RELEASE_VERSION} ARG NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID ENV NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=${NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID} +ARG NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} RUN \ if [ -f package-lock.json ]; then npm run build; \ diff --git a/ui/actions/integrations/saml.ts b/ui/actions/integrations/saml.ts index 2d645111ce..daa27f596f 100644 --- a/ui/actions/integrations/saml.ts +++ b/ui/actions/integrations/saml.ts @@ -1,20 +1,9 @@ "use server"; import { revalidatePath } from "next/cache"; -import { z } from "zod"; import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper"; - -const samlConfigFormSchema = z.object({ - email_domain: z - .string() - .trim() - .min(1, { message: "Email domain is required" }), - metadata_xml: z - .string() - .trim() - .min(1, { message: "Metadata XML is required" }), -}); +import { samlConfigFormSchema } from "@/types/formSchemas"; export const createSamlConfig = async (_prevState: any, formData: FormData) => { const headers = await getAuthHeaders({ contentType: true }); @@ -156,6 +145,39 @@ export const getSamlConfig = async () => { } }; +export const deleteSamlConfig = async (id: string) => { + const headers = await getAuthHeaders({ contentType: true }); + + try { + const url = new URL(`${apiBaseUrl}/saml-config/${id}`); + const response = await fetch(url.toString(), { + method: "DELETE", + headers, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.errors?.[0]?.detail || + `Failed to delete SAML config: ${response.statusText}`, + ); + } + + revalidatePath("/integrations"); + return { success: "SAML configuration deleted successfully!" }; + } catch (error) { + console.error("Error deleting SAML config:", error); + return { + errors: { + general: + error instanceof Error + ? error.message + : "Error deleting SAML configuration. Please try again.", + }, + }; + } +}; + export const initiateSamlAuth = async (email: string) => { try { const response = await fetch(`${apiBaseUrl}/auth/saml/initiate/`, { diff --git a/ui/actions/scans/scans.ts b/ui/actions/scans/scans.ts index b3115ea0e9..6d37fed0d7 100644 --- a/ui/actions/scans/scans.ts +++ b/ui/actions/scans/scans.ts @@ -16,6 +16,8 @@ export const getScans = async ({ sort = "", filters = {}, pageSize = 10, + fields = {}, + include = "", }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -27,6 +29,12 @@ export const getScans = async ({ if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); + if (include) url.searchParams.append("include", include); + + // Handle fields parameters + Object.entries(fields).forEach(([key, value]) => { + url.searchParams.append(`fields[${key}]`, String(value)); + }); // Handle multiple filters Object.entries(filters).forEach(([key, value]) => { diff --git a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx index 17636db9d4..93e1c48842 100644 --- a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx +++ b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx @@ -115,7 +115,9 @@ export default async function ComplianceDetail({ > {selectedScanId && selectedScan && (
- +
+ +
)} diff --git a/ui/app/(prowler)/compliance/page.tsx b/ui/app/(prowler)/compliance/page.tsx index 6a8326f334..5a1e7fcca9 100644 --- a/ui/app/(prowler)/compliance/page.tsx +++ b/ui/app/(prowler)/compliance/page.tsx @@ -3,7 +3,6 @@ import { Suspense } from "react"; import { getCompliancesOverview } from "@/actions/compliances"; import { getComplianceOverviewMetadataInfo } from "@/actions/compliances"; -import { getProvider } from "@/actions/providers"; import { getScans } from "@/actions/scans"; import { ComplianceCard, @@ -36,34 +35,41 @@ export default async function Compliance({ "filter[state]": "completed", }, pageSize: 50, + fields: { + scans: "name,completed_at,provider", + }, + include: "provider", }); if (!scansData?.data) { return ; } - // Expand scans with provider information - only include scans with valid provider - const expandedScansData: ExpandedScanData[] = await Promise.all( - scansData.data - .filter((scan: ScanProps) => scan.relationships?.provider?.data?.id) - .map(async (scan: ScanProps) => { - const providerId = scan.relationships!.provider!.data!.id; + // Process scans with provider information from included data + const expandedScansData: ExpandedScanData[] = scansData.data + .filter((scan: ScanProps) => scan.relationships?.provider?.data?.id) + .map((scan: ScanProps) => { + const providerId = scan.relationships!.provider!.data!.id; - const formData = new FormData(); - formData.append("id", providerId); + // Find the provider data in the included array + const providerData = scansData.included?.find( + (item: any) => item.type === "providers" && item.id === providerId, + ); - const providerData = await getProvider(formData); + if (!providerData) { + return null; + } - return { - ...scan, - providerInfo: { - provider: providerData.data.attributes.provider, - uid: providerData.data.attributes.uid, - alias: providerData.data.attributes.alias, - }, - }; - }), - ); + return { + ...scan, + providerInfo: { + provider: providerData.attributes.provider, + uid: providerData.attributes.uid, + alias: providerData.attributes.alias, + }, + }; + }) + .filter(Boolean) as ExpandedScanData[]; const selectedScanId = searchParams.scanId || expandedScansData[0]?.id || null; @@ -140,7 +146,7 @@ const SSRComplianceGrid = async ({ query, }); - const type = compliancesData?.data?.[0]?.type; + const type = compliancesData?.data?.type; // Check if the response contains no data if ( diff --git a/ui/app/(prowler)/profile/page.tsx b/ui/app/(prowler)/profile/page.tsx index 2e73bc0342..bc8b371fe2 100644 --- a/ui/app/(prowler)/profile/page.tsx +++ b/ui/app/(prowler)/profile/page.tsx @@ -1,10 +1,10 @@ import React, { Suspense } from "react"; -import { getSamlConfig } from "@/actions/integrations/saml"; +// import { getSamlConfig } from "@/actions/integrations/saml"; import { getAllTenants } from "@/actions/users/tenants"; import { getUserInfo } from "@/actions/users/users"; import { getUserMemberships } from "@/actions/users/users"; -import { SamlIntegrationCard } from "@/components/integrations/saml-integration-card"; +// import { SamlIntegrationCard } from "@/components/integrations/saml-integration-card"; import { ContentLayout } from "@/components/ui"; import { UserBasicInfoCard } from "@/components/users/profile"; import { MembershipsCard } from "@/components/users/profile/memberships-card"; @@ -24,7 +24,7 @@ export default async function Profile() { } const SSRDataUser = async () => { - const samlConfig = await getSamlConfig(); + // const samlConfig = await getSamlConfig(); const userProfile = await getUserInfo(); if (!userProfile?.data) { return null; @@ -84,11 +84,10 @@ const SSRDataUser = async () => { /> -
- {samlConfig.data?.length > 0 && ( - - )} -
+ {/* TODO after v5.8: Add SAML SSO back in */} + {/*
+ +
*/} ); }; diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 9a6214bf59..e753463a4e 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -1,7 +1,7 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; -import { getProvider, getProviders } from "@/actions/providers"; +import { getProviders } from "@/actions/providers"; import { getScans, getScansByState } from "@/actions/scans"; import { AutoRefresh, @@ -45,13 +45,9 @@ export default async function Scans({ connected: provider.attributes.connection.connected, })) || []; - const providersCountConnected = await getProviders({ - filters: { "filter[connected]": true }, - pageSize: 50, - }); - const thereIsNoProviders = !providersCountConnected?.data; + const thereIsNoProviders = !providersData?.data; - const thereIsNoProvidersConnected = providersCountConnected?.data?.every( + const thereIsNoProvidersConnected = providersData?.data?.every( (provider: ProviderProps) => !provider.attributes.connection.connected, ); @@ -123,34 +119,43 @@ const SSRDataTableScans = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - // Fetch scans data - const scansData = await getScans({ query, page, sort, filters, pageSize }); + // Fetch scans data with provider information included + const scansData = await getScans({ + query, + page, + sort, + filters, + pageSize, + include: "provider", + }); - // Handle expanded scans data - const expandedScansData = await Promise.all( - scansData?.data?.map(async (scan: any) => { + // Process scans with provider information from included data + const expandedScansData = + scansData?.data?.map((scan: any) => { const providerId = scan.relationships?.provider?.data?.id; if (!providerId) { return { ...scan, providerInfo: null }; } - const formData = new FormData(); - formData.append("id", providerId); + // Find the provider data in the included array + const providerData = scansData.included?.find( + (item: any) => item.type === "providers" && item.id === providerId, + ); - const providerData = await getProvider(formData); - - if (providerData?.data) { - const { provider, uid, alias } = providerData.data.attributes; - return { - ...scan, - providerInfo: { provider, uid, alias }, - }; + if (!providerData) { + return { ...scan, providerInfo: null }; } - return { ...scan, providerInfo: null }; - }) || [], - ); + return { + ...scan, + providerInfo: { + provider: providerData.attributes.provider, + uid: providerData.attributes.uid, + alias: providerData.attributes.alias, + }, + }; + }) || []; return ( {type === "sign-in" ? isSamlMode - ? "Sign In with SAML SSO" - : "Sign In" - : "Sign Up"} + ? "Sign in with SAML SSO" + : "Sign in" + : "Sign up"}

@@ -218,7 +218,7 @@ export const AuthForm = ({ isInvalid={!!form.formState.errors.email} showFormMessage={type !== "sign-in"} /> - {!isSamlMode && type === "sign-in" && ( + {!isSamlMode && ( Loading ) : ( - {type === "sign-in" ? "Log In" : "Sign Up"} + {type === "sign-in" ? "Log in" : "Sign up"} )} @@ -401,7 +401,8 @@ export const AuthForm = ({ )} - + */} )} {type === "sign-in" ? (

Need to create an account?  - Sign Up + Sign up

) : (

Already have an account?  - Log In + Log in

)} diff --git a/ui/components/compliance/compliance-charts/bar-chart.tsx b/ui/components/compliance/compliance-charts/bar-chart.tsx index 2b0add1160..a556799091 100644 --- a/ui/components/compliance/compliance-charts/bar-chart.tsx +++ b/ui/components/compliance/compliance-charts/bar-chart.tsx @@ -44,7 +44,7 @@ interface FailedSectionsListProps { const title = (

- Failed Sections (Top 5) + Top Failed Sections

); diff --git a/ui/components/integrations/forms/saml-config-form.tsx b/ui/components/integrations/forms/saml-config-form.tsx index 579bf17075..ecea01343f 100644 --- a/ui/components/integrations/forms/saml-config-form.tsx +++ b/ui/components/integrations/forms/saml-config-form.tsx @@ -1,7 +1,9 @@ "use client"; +import Link from "next/link"; import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { useFormState } from "react-dom"; +import { z } from "zod"; import { createSamlConfig, updateSamlConfig } from "@/actions/integrations"; import { AddIcon } from "@/components/icons"; @@ -9,26 +11,56 @@ import { useToast } from "@/components/ui"; import { CustomButton, CustomServerInput } from "@/components/ui/custom"; import { SnippetChip } from "@/components/ui/entities"; import { FormButtons } from "@/components/ui/form"; +import { apiBaseUrl } from "@/lib"; export const SamlConfigForm = ({ setIsOpen, - id, + samlConfig, }: { setIsOpen: Dispatch>; - id: string; + samlConfig?: any; }) => { const [state, formAction, isPending] = useFormState( - id ? updateSamlConfig : createSamlConfig, + samlConfig?.id ? updateSamlConfig : createSamlConfig, null, ); - const [emailDomain, setEmailDomain] = useState(""); - const [uploadedFile, setUploadedFile] = useState<{ - name: string; - uploaded: boolean; - }>({ name: "", uploaded: false }); + const [emailDomain, setEmailDomain] = useState( + samlConfig?.attributes?.email_domain || "", + ); + const [uploadedFile, setUploadedFile] = useState(null); + const [clientErrors, setClientErrors] = useState<{ + email_domain?: string | null; + metadata_xml?: string | null; + }>({}); const formRef = useRef(null); const { toast } = useToast(); + // Client-side validation function + const validateFields = (email: string, hasFile: boolean) => { + // Validar cada campo por separado para poder limpiarlos individualmente + const emailValidation = z + .string() + .trim() + .min(1, { message: "Email domain is required" }) + .safeParse(email); + const metadataValidation = z + .string() + .trim() + .min(1, { message: "Metadata XML is required" }) + .safeParse(hasFile ? "dummy_xml_content" : ""); + + const newErrors = { + email_domain: emailValidation.success + ? null + : emailValidation.error.issues[0]?.message, + metadata_xml: metadataValidation.success + ? null + : metadataValidation.error.issues[0]?.message, + }; + + setClientErrors(newErrors); + }; + useEffect(() => { if (state?.success) { toast({ @@ -48,7 +80,8 @@ export const SamlConfigForm = ({ const handleFileUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) { - setUploadedFile({ name: "", uploaded: false }); + setUploadedFile(null); + validateFields(emailDomain, false); return; } @@ -66,7 +99,8 @@ export const SamlConfigForm = ({ }); // Clear the file input event.target.value = ""; - setUploadedFile({ name: "", uploaded: false }); + setUploadedFile(null); + validateFields(emailDomain, false); return; } @@ -84,7 +118,8 @@ export const SamlConfigForm = ({ }); // Clear the file input event.target.value = ""; - setUploadedFile({ name: "", uploaded: false }); + setUploadedFile(null); + validateFields(emailDomain, false); return; } @@ -96,8 +131,8 @@ export const SamlConfigForm = ({ xmlInput.value = content; } - // Update file state - setUploadedFile({ name: file.name, uploaded: true }); + setUploadedFile(file); + validateFields(emailDomain, true); toast({ title: "File uploaded successfully", @@ -113,89 +148,44 @@ export const SamlConfigForm = ({ }); // Clear the file input event.target.value = ""; - setUploadedFile({ name: "", uploaded: false }); + setUploadedFile(null); + validateFields(emailDomain, false); }; reader.readAsText(file); }; const acsUrl = emailDomain - ? `https://app.prowler.pro/saml/sp/consume/${emailDomain}` - : "https://app.prowler.pro/saml/sp/consume/your-domain.com"; + ? `${apiBaseUrl}/accounts/saml/${emailDomain}/acs/` + : `${apiBaseUrl}/accounts/saml/your-domain.com/acs/`; return ( -
- -
- ) => { - setEmailDomain(e.target.value); - }} - /> - -
- - Metadata XML File * - - { - const fileInput = document.getElementById( - "metadata_xml_file", - ) as HTMLInputElement; - if (fileInput) { - fileInput.click(); - } - }} - startContent={} - className={`h-10 justify-start rounded-medium border-2 text-default-500 ${ - state?.errors?.metadata_xml - ? "border-red-500" - : uploadedFile.uploaded - ? "border-green-500 bg-green-50 dark:bg-green-900/20" - : "border-default-200" - }`} - > - - {uploadedFile.uploaded ? ( - - {uploadedFile.name} - - ) : ( - "Choose File" - )} - - - - - -

- Upload your Identity Provider's SAML metadata XML file -

- - {state?.errors?.metadata_xml} - -
-
+ + + ) => { + const newValue = e.target.value; + setEmailDomain(newValue); + validateFields(newValue, !!uploadedFile); + }} + />

@@ -229,11 +219,9 @@ export const SamlConfigForm = ({ Name ID Format: - + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +

@@ -250,13 +238,82 @@ export const SamlConfigForm = ({ Note: The userType attribute will be used to assign the user's role. If the role does not exist, one will be created with minimal permissions. You can assign permissions to - roles on the Roles page. + roles on the{" "} + + Roles + {" "} + page.

+
+ + Metadata XML File * + + { + const fileInput = document.getElementById( + "metadata_xml_file", + ) as HTMLInputElement; + if (fileInput) { + fileInput.click(); + } + }} + startContent={} + className={`h-10 justify-start rounded-medium border-2 text-default-500 ${ + ( + clientErrors.metadata_xml === null + ? undefined + : clientErrors.metadata_xml || state?.errors?.metadata_xml + ) + ? "border-red-500" + : uploadedFile + ? "border-green-500 bg-green-50 dark:bg-green-900/20" + : "border-default-200" + }`} + > + + {uploadedFile ? ( + + {uploadedFile.name} + + ) : ( + "Choose File" + )} + + - + + +

+ Upload your Identity Provider's SAML metadata XML file +

+ + {(() => { + const finalError = + clientErrors.metadata_xml === null + ? undefined + : clientErrors.metadata_xml || state?.errors?.metadata_xml; + return finalError; + })()} + +
+ ); }; diff --git a/ui/components/integrations/saml-integration-card.tsx b/ui/components/integrations/saml-integration-card.tsx index 2c7640b767..ae3eb6f60d 100644 --- a/ui/components/integrations/saml-integration-card.tsx +++ b/ui/components/integrations/saml-integration-card.tsx @@ -1,15 +1,51 @@ "use client"; import { Card, CardBody, CardHeader } from "@nextui-org/react"; -import { CheckIcon } from "lucide-react"; +import { Link } from "@nextui-org/react"; +import { CheckIcon, Trash2Icon } from "lucide-react"; import { useState } from "react"; +import { deleteSamlConfig } from "@/actions/integrations"; +import { useToast } from "@/components/ui"; import { CustomAlertModal, CustomButton } from "@/components/ui/custom"; import { SamlConfigForm } from "./forms"; -export const SamlIntegrationCard = ({ id }: { id: string }) => { +export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { const [isSamlModalOpen, setIsSamlModalOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const { toast } = useToast(); + const id = samlConfig?.id; + + const handleRemoveSaml = async () => { + if (!id) return; + + setIsDeleting(true); + try { + const result = await deleteSamlConfig(id); + + if (result.success) { + toast({ + title: "SAML configuration removed", + description: result.success, + }); + } else if (result.errors?.general) { + toast({ + variant: "destructive", + title: "Error removing SAML configuration", + description: result.errors.general, + }); + } + } catch (error) { + toast({ + variant: "destructive", + title: "Error", + description: "Failed to remove SAML configuration. Please try again.", + }); + } finally { + setIsDeleting(false); + } + }; return ( <> @@ -18,7 +54,10 @@ export const SamlIntegrationCard = ({ id }: { id: string }) => { onOpenChange={setIsSamlModalOpen} title="Configure SAML SSO" > - + @@ -29,9 +68,21 @@ export const SamlIntegrationCard = ({ id }: { id: string }) => { {id && }

- {id - ? "SAML Single Sign-On is enabled for this organization" - : "Configure SAML Single Sign-On for secure authentication"} + {id ? ( + "SAML Single Sign-On is enabled for this organization" + ) : ( + <> + Configure SAML Single Sign-On for secure authentication.{" "} + + Read the docs + + + )}

@@ -43,14 +94,29 @@ export const SamlIntegrationCard = ({ id }: { id: string }) => { {id ? "Enabled" : "Disabled"} - setIsSamlModalOpen(true)} - > - {id ? "Update" : "Enable"} - +
+ setIsSamlModalOpen(true)} + > + {id ? "Update" : "Enable"} + + {id && ( + : null} + onPress={handleRemoveSaml} + > + Remove + + )} +
diff --git a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx index a34b8ff166..5fa8f2f9a1 100644 --- a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx @@ -1,5 +1,7 @@ +import Link from "next/link"; import { Control } from "react-hook-form"; +import { InfoIcon } from "@/components/icons"; import { CustomInput } from "@/components/ui/custom"; import { M365Credentials } from "@/types"; @@ -51,6 +53,39 @@ export const M365CredentialsForm = ({ isRequired isInvalid={!!control._formState.errors.tenant_id} /> +

+ {" "} + User and password authentication is being deprecated due to + Microsoft's on-going MFA enforcement across all tenants (see{" "} + + Microsoft docs + + ). +

+ +
+ +

+ By September 2025, MFA will be mandatory. +

+
+

+ Due to that change, you must only{" "} + + use application authentication + {" "} + to maintain all Prowler M365 scan capabilities. +

0 && - deselectedProviders.length === 0 && - !isManualDeselection.current - ) { - const providerTypes = currentProviders - .map(getProviderType) - .filter((type): type is ProviderType => type !== null); - const selectedProviderTypes = Array.from(new Set(providerTypes)); + // // Handle provider selection logic + // if ( + // currentProviders.length > 0 && + // deselectedProviders.length === 0 && + // !isManualDeselection.current + // ) { + // const providerTypes = currentProviders + // .map(getProviderType) + // .filter((type): type is ProviderType => type !== null); + // const selectedProviderTypes = Array.from(new Set(providerTypes)); - if ( - selectedProviderTypes.length > 0 && - currentProviderTypes.length === 0 - ) { - updateFilter(FilterType.PROVIDER_TYPE, selectedProviderTypes); - } - } + // if ( + // selectedProviderTypes.length > 0 && + // currentProviderTypes.length === 0 + // ) { + // updateFilter(FilterType.PROVIDER_TYPE, selectedProviderTypes); + // } + // } // Update available providers if (currentProviderTypes.length > 0) { diff --git a/ui/lib/helper.ts b/ui/lib/helper.ts index 69ba6d36b4..1ec700e4d3 100644 --- a/ui/lib/helper.ts +++ b/ui/lib/helper.ts @@ -5,7 +5,7 @@ import { useToast } from "@/components/ui"; import { AuthSocialProvider, MetaDataProps, PermissionInfo } from "@/types"; export const baseUrl = process.env.AUTH_URL || "http://localhost:3000"; -export const apiBaseUrl = process.env.API_BASE_URL; +export const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; /** * Extracts a form value from a FormData object diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index 2c0f6ee3de..674d263386 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -192,17 +192,8 @@ export const buildUpdateSecretConfig = ( formData: FormData, providerType: ProviderType, ) => { - // Reuse the same secret building logic as add, but only return the secret + // Reuse the same secret building logic as add const { secret } = buildSecretConfig(formData, providerType); - - // Handle special case for M365 password field inconsistency - if (providerType === "m365") { - return { - ...secret, - password: formData.get(ProviderCredentialFields.PASSWORD), - }; - } - return secret; }; diff --git a/ui/package-lock.json b/ui/package-lock.json index b6885ee7fe..e6c93b63bb 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -394,9 +394,9 @@ } }, "node_modules/@eslint/eslintrc/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -526,9 +526,9 @@ } }, "node_modules/@humanwhocodes/config-array/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -8732,9 +8732,9 @@ } }, "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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -10567,9 +10567,9 @@ } }, "node_modules/eslint-plugin-import/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -10649,9 +10649,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -10691,9 +10691,9 @@ } }, "node_modules/eslint-plugin-node/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -10796,9 +10796,9 @@ } }, "node_modules/eslint-plugin-react/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -10962,9 +10962,9 @@ } }, "node_modules/eslint/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -15587,9 +15587,9 @@ } }, "node_modules/rimraf/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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 32a58f9336..d190def6c3 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -309,3 +309,14 @@ export const editUserFormSchema = () => userId: z.string(), role: z.string().optional(), }); + +export const samlConfigFormSchema = z.object({ + email_domain: z + .string() + .trim() + .min(1, { message: "Email domain is required" }), + metadata_xml: z + .string() + .trim() + .min(1, { message: "Metadata XML is required" }), +}); diff --git a/ui/types/scans.ts b/ui/types/scans.ts index 0166294572..e23cc4d6b0 100644 --- a/ui/types/scans.ts +++ b/ui/types/scans.ts @@ -67,3 +67,27 @@ export interface ExpandedScanData extends ScanProps { alias: string; }; } + +export interface ScansApiResponse { + links: { + first: string; + last: string; + next: string | null; + prev: string | null; + }; + data: ScanProps[]; + included?: Array<{ + type: string; + id: string; + attributes: any; + relationships?: any; + }>; + meta: { + pagination: { + page: number; + pages: number; + count: number; + }; + version: string; + }; +}