diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index 1d4733d10a..49df46c8e5 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -145,7 +145,7 @@ jobs: working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | - poetry run safety check --ignore 70612,66963 + poetry run safety check --ignore 70612,66963,74429 - name: Vulture working-directory: ./api diff --git a/.github/workflows/sdk-bump-version.yml b/.github/workflows/sdk-bump-version.yml new file mode 100644 index 0000000000..bc28afa1e4 --- /dev/null +++ b/.github/workflows/sdk-bump-version.yml @@ -0,0 +1,94 @@ +name: SDK - Bump Version + +on: + release: + types: [published] + + +env: + PROWLER_VERSION: ${{ github.event.release.tag_name }} + BASE_BRANCH: master + +jobs: + bump-version: + name: Bump Version + if: github.repository == 'prowler-cloud/prowler' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Prowler version + shell: bash + run: | + if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + MAJOR_VERSION=${BASH_REMATCH[1]} + MINOR_VERSION=${BASH_REMATCH[2]} + FIX_VERSION=${BASH_REMATCH[3]} + + if (( MAJOR_VERSION == 5 )); then + if (( FIX_VERSION == 0 )); then + echo "Minor Release: $PROWLER_VERSION" + + BUMP_VERSION_TO=${MAJOR_VERSION}.$((MINOR_VERSION + 1)).${FIX_VERSION} + echo "BUMP_VERSION_TO=${BUMP_VERSION_TO}" >> "${GITHUB_ENV}" + + TARGET_BRANCH=${BASE_BRANCH} + echo "TARGET_BRANCH=${TARGET_BRANCH}" >> "${GITHUB_ENV}" + + echo "Bumping to next minor version: ${BUMP_VERSION_TO} in branch ${TARGET_BRANCH}" + else + echo "Patch Release: $PROWLER_VERSION" + + BUMP_VERSION_TO=${MAJOR_VERSION}.${MINOR_VERSION}.$((FIX_VERSION + 1)) + echo "BUMP_VERSION_TO=${BUMP_VERSION_TO}" >> "${GITHUB_ENV}" + + TARGET_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION} + echo "TARGET_BRANCH=${TARGET_BRANCH}" >> "${GITHUB_ENV}" + + echo "Bumping to next patch version: ${BUMP_VERSION_TO} in branch ${TARGET_BRANCH}" + fi + else + echo "Releasing another Prowler major version, aborting..." + exit 1 + fi + else + echo "Invalid version syntax: '$PROWLER_VERSION' (must be N.N.N)" >&2 + exit 1 + fi + + - name: Bump versions in files + run: | + echo "Using PROWLER_VERSION=$PROWLER_VERSION" + echo "Using BUMP_VERSION_TO=$BUMP_VERSION_TO" + + set -e + + echo "Bumping version in pyproject.toml ..." + sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${BUMP_VERSION_TO}\"|" pyproject.toml + + echo "Bumping version in prowler/config/config.py ..." + sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${BUMP_VERSION_TO}\"|" prowler/config/config.py + + echo "Bumping version in .env ..." + sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${PROWLER_VERSION}|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${BUMP_VERSION_TO}|" .env + + git --no-pager diff + + + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + author: prowler-bot <179230569+prowler-bot@users.noreply.github.com> + token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} + base: ${{ env.TARGET_BRANCH }} + commit-message: "chore(release): Bump version to v${{ env.BUMP_VERSION_TO }}" + branch: "version-bump-to-v${{ env.BUMP_VERSION_TO }}" + title: "chore(release): Bump version to v${{ env.BUMP_VERSION_TO }}" + body: | + ### Description + + Bump Prowler version to v${{ env.BUMP_VERSION_TO }} + + ### License + + By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a5d011e23d..0382dd901d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -115,7 +115,7 @@ repos: - id: safety name: safety description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities" - entry: bash -c 'safety check --ignore 70612,66963' + entry: bash -c 'safety check --ignore 70612,66963,74429' language: system - id: vulture diff --git a/Dockerfile b/Dockerfile index 1c19358a3f..0e9fb89a03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,43 @@ -FROM python:3.12.10-alpine3.20 +FROM python:3.12.10-slim-bookworm AS build LABEL maintainer="https://github.com/prowler-cloud/prowler" +LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler" -# Update system dependencies and install essential tools -#hadolint ignore=DL3018 -RUN apk --no-cache upgrade && apk --no-cache add curl git gcc python3-dev musl-dev linux-headers +ARG POWERSHELL_VERSION=7.5.0 + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends wget libicu72 \ + && rm -rf /var/lib/apt/lists/* + +# Install PowerShell +RUN ARCH=$(uname -m) && \ + if [ "$ARCH" = "x86_64" ]; then \ + wget --progress=dot:giga https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-x64.tar.gz -O /tmp/powershell.tar.gz ; \ + elif [ "$ARCH" = "aarch64" ]; then \ + wget --progress=dot:giga https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-arm64.tar.gz -O /tmp/powershell.tar.gz ; \ + else \ + echo "Unsupported architecture: $ARCH" && exit 1 ; \ + fi && \ + mkdir -p /opt/microsoft/powershell/7 && \ + tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \ + chmod +x /opt/microsoft/powershell/7/pwsh && \ + ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \ + rm /tmp/powershell.tar.gz + +# Add prowler user +RUN addgroup --gid 1000 prowler && \ + adduser --uid 1000 --gid 1000 --disabled-password --gecos "" prowler -# Create non-root user -RUN mkdir -p /home/prowler && \ - echo 'prowler:x:1000:1000:prowler:/home/prowler:' > /etc/passwd && \ - echo 'prowler:x:1000:' > /etc/group && \ - chown -R prowler:prowler /home/prowler USER prowler -# Copy necessary files WORKDIR /home/prowler + +# Copy necessary files COPY prowler/ /home/prowler/prowler/ COPY dashboard/ /home/prowler/dashboard/ COPY pyproject.toml /home/prowler COPY README.md /home/prowler/ +COPY prowler/providers/m365/lib/powershell/m365_powershell.py /home/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py # Install Python dependencies ENV HOME='/home/prowler' @@ -34,6 +53,9 @@ RUN pip install --no-cache-dir --upgrade pip && \ RUN poetry install --compile && \ rm -rf ~/.cache/pip +# Install PowerShell modules +RUN poetry run python prowler/providers/m365/lib/powershell/m365_powershell.py + # Remove deprecated dash dependencies RUN pip uninstall dash-html-components -y && \ pip uninstall dash-core-components -y diff --git a/README.md b/README.md index addd596590..fa89204448 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe | GCP | 79 | 13 | 7 | 3 | | Azure | 140 | 18 | 8 | 3 | | Kubernetes | 83 | 7 | 4 | 7 | -| M365 | 5 | 2 | 1 | 0 | +| M365 | 44 | 2 | 1 | 0 | | NHN (Unofficial) | 6 | 2 | 1 | 0 | > You can list the checks, services, compliance frameworks and categories with `prowler --list-checks`, `prowler --list-services`, `prowler --list-compliance` and `prowler --list-categories`. diff --git a/api/.pre-commit-config.yaml b/api/.pre-commit-config.yaml index 6859567eb5..1cd04529c3 100644 --- a/api/.pre-commit-config.yaml +++ b/api/.pre-commit-config.yaml @@ -80,7 +80,7 @@ repos: - id: safety name: safety description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities" - entry: bash -c 'poetry run safety check --ignore 70612,66963' + entry: bash -c 'poetry run safety check --ignore 70612,66963,74429' language: system - id: vulture diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 44879c662a..84aedf43f7 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the **Prowler API** are documented in this file. +## [v1.7.0] (UNRELEASED) + +### Added + +- Added M365 as a new provider [(#7563)](https://github.com/prowler-cloud/prowler/pull/7563). + +--- + ## [v1.6.0] (Prowler v5.5.0) ### Added diff --git a/api/Dockerfile b/api/Dockerfile index 3569d003e7..8291b14295 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,13 +1,33 @@ -FROM python:3.12.8-alpine3.20 AS build +FROM python:3.12.10-slim-bookworm AS build LABEL maintainer="https://github.com/prowler-cloud/api" -# hadolint ignore=DL3018 -RUN apk --no-cache add gcc python3-dev musl-dev linux-headers curl-dev +ARG POWERSHELL_VERSION=7.5.0 +ENV POWERSHELL_VERSION=${POWERSHELL_VERSION} + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends wget libicu72 \ + && rm -rf /var/lib/apt/lists/* + +# Install PowerShell +RUN ARCH=$(uname -m) && \ + if [ "$ARCH" = "x86_64" ]; then \ + wget --progress=dot:giga https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-x64.tar.gz -O /tmp/powershell.tar.gz ; \ + elif [ "$ARCH" = "aarch64" ]; then \ + wget --progress=dot:giga https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-arm64.tar.gz -O /tmp/powershell.tar.gz ; \ + else \ + echo "Unsupported architecture: $ARCH" && exit 1 ; \ + fi && \ + mkdir -p /opt/microsoft/powershell/7 && \ + tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \ + chmod +x /opt/microsoft/powershell/7/pwsh && \ + ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \ + rm /tmp/powershell.tar.gz + +# Add prowler user +RUN addgroup --gid 1000 prowler && \ + adduser --uid 1000 --gid 1000 --disabled-password --gecos "" prowler -RUN apk --no-cache upgrade && \ - addgroup -g 1000 prowler && \ - adduser -D -u 1000 -G prowler prowler USER prowler WORKDIR /home/prowler @@ -17,7 +37,7 @@ COPY pyproject.toml ./ RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir poetry -COPY src/backend/ ./backend/ +COPY src/backend/ ./backend/ ENV PATH="/home/prowler/.local/bin:$PATH" @@ -27,18 +47,13 @@ RUN poetry install --no-root && \ COPY docker-entrypoint.sh ./docker-entrypoint.sh +RUN poetry run python "$(poetry env info --path)/src/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py" + WORKDIR /home/prowler/backend # Development image -# hadolint ignore=DL3006 FROM build AS dev -USER 0 -# hadolint ignore=DL3018 -RUN apk --no-cache add curl vim - -USER prowler - ENTRYPOINT ["../docker-entrypoint.sh", "dev"] # Production image diff --git a/api/poetry.lock b/api/poetry.lock index 0e9af82b8c..975d527172 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -14,105 +14,105 @@ files = [ [[package]] name = "aiohappyeyeballs" -version = "2.4.6" +version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1"}, - {file = "aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0"}, + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] [[package]] name = "aiohttp" -version = "3.11.12" +version = "3.11.18" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa8a8caca81c0a3e765f19c6953416c58e2f4cc1b84829af01dd1c771bb2f91f"}, - {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ede78acde96ca57f6cf8ccb8a13fbaf569f6011b9a52f870c662d4dc8cd854"}, - {file = "aiohttp-3.11.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:584096938a001378484aa4ee54e05dc79c7b9dd933e271c744a97b3b6f644957"}, - {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392432a2dde22b86f70dd4a0e9671a349446c93965f261dbaecfaf28813e5c42"}, - {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88d385b8e7f3a870146bf5ea31786ef7463e99eb59e31db56e2315535d811f55"}, - {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b10a47e5390c4b30a0d58ee12581003be52eedd506862ab7f97da7a66805befb"}, - {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5263dcede17b6b0c41ef0c3ccce847d82a7da98709e75cf7efde3e9e3b5cae"}, - {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c5c7b8aa5443304c55c262c5693b108c35a3b61ef961f1e782dd52a2f559c7"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1c031a7572f62f66f1257db37ddab4cb98bfaf9b9434a3b4840bf3560f5e788"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e44eba534381dd2687be50cbd5f2daded21575242ecfdaf86bbeecbc38dae8e"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:145a73850926018ec1681e734cedcf2716d6a8697d90da11284043b745c286d5"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2c311e2f63e42c1bf86361d11e2c4a59f25d9e7aabdbdf53dc38b885c5435cdb"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ea756b5a7bac046d202a9a3889b9a92219f885481d78cd318db85b15cc0b7bcf"}, - {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:526c900397f3bbc2db9cb360ce9c35134c908961cdd0ac25b1ae6ffcaa2507ff"}, - {file = "aiohttp-3.11.12-cp310-cp310-win32.whl", hash = "sha256:b8d3bb96c147b39c02d3db086899679f31958c5d81c494ef0fc9ef5bb1359b3d"}, - {file = "aiohttp-3.11.12-cp310-cp310-win_amd64.whl", hash = "sha256:7fe3d65279bfbee8de0fb4f8c17fc4e893eed2dba21b2f680e930cc2b09075c5"}, - {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a2e00bf17da098d90d4145375f1d985a81605267e7f9377ff94e55c5d769eb"}, - {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b34508f1cd928ce915ed09682d11307ba4b37d0708d1f28e5774c07a7674cac9"}, - {file = "aiohttp-3.11.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:936d8a4f0f7081327014742cd51d320296b56aa6d324461a13724ab05f4b2933"}, - {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de1378f72def7dfb5dbd73d86c19eda0ea7b0a6873910cc37d57e80f10d64e1"}, - {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9d45dbb3aaec05cf01525ee1a7ac72de46a8c425cb75c003acd29f76b1ffe94"}, - {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:930ffa1925393381e1e0a9b82137fa7b34c92a019b521cf9f41263976666a0d6"}, - {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8340def6737118f5429a5df4e88f440746b791f8f1c4ce4ad8a595f42c980bd5"}, - {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4016e383f91f2814e48ed61e6bda7d24c4d7f2402c75dd28f7e1027ae44ea204"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c0600bcc1adfaaac321422d615939ef300df81e165f6522ad096b73439c0f58"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0450ada317a65383b7cce9576096150fdb97396dcfe559109b403c7242faffef"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:850ff6155371fd802a280f8d369d4e15d69434651b844bde566ce97ee2277420"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8fd12d0f989c6099e7b0f30dc6e0d1e05499f3337461f0b2b0dadea6c64b89df"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:76719dd521c20a58a6c256d058547b3a9595d1d885b830013366e27011ffe804"}, - {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fe431f2ed646a3b56142fc81d238abcbaff08548d6912acb0b19a0cadc146b"}, - {file = "aiohttp-3.11.12-cp311-cp311-win32.whl", hash = "sha256:e10c440d142fa8b32cfdb194caf60ceeceb3e49807072e0dc3a8887ea80e8c16"}, - {file = "aiohttp-3.11.12-cp311-cp311-win_amd64.whl", hash = "sha256:246067ba0cf5560cf42e775069c5d80a8989d14a7ded21af529a4e10e3e0f0e6"}, - {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250"}, - {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1"}, - {file = "aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c"}, - {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df"}, - {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259"}, - {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d"}, - {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e"}, - {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef"}, - {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9"}, - {file = "aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a"}, - {file = "aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802"}, - {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9"}, - {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c"}, - {file = "aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0"}, - {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2"}, - {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1"}, - {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7"}, - {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e"}, - {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a"}, - {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce"}, - {file = "aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f"}, - {file = "aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287"}, - {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c3623053b85b4296cd3925eeb725e386644fd5bc67250b3bb08b0f144803e7b"}, - {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67453e603cea8e85ed566b2700efa1f6916aefbc0c9fcb2e86aaffc08ec38e78"}, - {file = "aiohttp-3.11.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6130459189e61baac5a88c10019b21e1f0c6d00ebc770e9ce269475650ff7f73"}, - {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9060addfa4ff753b09392efe41e6af06ea5dd257829199747b9f15bfad819460"}, - {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34245498eeb9ae54c687a07ad7f160053911b5745e186afe2d0c0f2898a1ab8a"}, - {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dc0fba9a74b471c45ca1a3cb6e6913ebfae416678d90529d188886278e7f3f6"}, - {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a478aa11b328983c4444dacb947d4513cb371cd323f3845e53caeda6be5589d5"}, - {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c160a04283c8c6f55b5bf6d4cad59bb9c5b9c9cd08903841b25f1f7109ef1259"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:edb69b9589324bdc40961cdf0657815df674f1743a8d5ad9ab56a99e4833cfdd"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ee84c2a22a809c4f868153b178fe59e71423e1f3d6a8cd416134bb231fbf6d3"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bf4480a5438f80e0f1539e15a7eb8b5f97a26fe087e9828e2c0ec2be119a9f72"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b2732ef3bafc759f653a98881b5b9cdef0716d98f013d376ee8dfd7285abf1"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f752e80606b132140883bb262a457c475d219d7163d996dc9072434ffb0784c4"}, - {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab3247d58b393bda5b1c8f31c9edece7162fc13265334217785518dd770792b8"}, - {file = "aiohttp-3.11.12-cp39-cp39-win32.whl", hash = "sha256:0d5176f310a7fe6f65608213cc74f4228e4f4ce9fd10bcb2bb6da8fc66991462"}, - {file = "aiohttp-3.11.12-cp39-cp39-win_amd64.whl", hash = "sha256:74bd573dde27e58c760d9ca8615c41a57e719bff315c9adb6f2a4281a28e8798"}, - {file = "aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0"}, + {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"}, ] [package.dependencies] @@ -175,14 +175,14 @@ vine = ">=5.0.0,<6.0.0" [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -191,8 +191,8 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -237,34 +237,34 @@ files = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "authlib" -version = "1.4.1" +version = "1.5.2" 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.4.1-py2.py3-none-any.whl", hash = "sha256:edc29c3f6a3e72cd9e9f45fff67fc663a2c364022eb0371c003f22d5405915c1"}, - {file = "authlib-1.4.1.tar.gz", hash = "sha256:30ead9ea4993cdbab821dc6e01e818362f92da290c04c7f6a1940f86507a790d"}, + {file = "authlib-1.5.2-py2.py3-none-any.whl", hash = "sha256:8804dd4402ac5e4a0435ac49e0b6e19e395357cfa632a3f624dcb4f6df13b4b1"}, + {file = "authlib-1.5.2.tar.gz", hash = "sha256:fe85ec7e50c5f86f1e2603518bb3b4f632985eb4a355e52256530790e326c512"}, ] [package.dependencies] @@ -311,14 +311,14 @@ files = [ [[package]] name = "azure-core" -version = "1.32.0" +version = "1.33.0" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4"}, - {file = "azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5"}, + {file = "azure_core-1.33.0-py3-none-any.whl", hash = "sha256:9b5b6d0223a1d38c37500e6971118c1e0f13f54951e6893968b38910bc9cda8f"}, + {file = "azure_core-1.33.0.tar.gz", hash = "sha256:f367aa07b5e3005fec2c1e184b882b0b039910733907d001c20fb08ebb8c0eb9"}, ] [package.dependencies] @@ -328,17 +328,18 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["aiohttp (>=3.0)"] +tracing = ["opentelemetry-api (>=1.26,<2.0)"] [[package]] name = "azure-identity" -version = "1.19.0" +version = "1.21.0" description = "Microsoft Azure Identity Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure_identity-1.19.0-py3-none-any.whl", hash = "sha256:e3f6558c181692d7509f09de10cca527c7dce426776454fb97df512a46527e81"}, - {file = "azure_identity-1.19.0.tar.gz", hash = "sha256:500144dc18197d7019b81501165d4fa92225f03778f17d7ca8a2a180129a9c83"}, + {file = "azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9"}, + {file = "azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6"}, ] [package.dependencies] @@ -368,20 +369,21 @@ typing-extensions = ">=4.0.1" [[package]] name = "azure-mgmt-applicationinsights" -version = "4.0.0" +version = "4.1.0" description = "Microsoft Azure Application Insights Management Client Library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure-mgmt-applicationinsights-4.0.0.zip", hash = "sha256:50c3db05573e0cc2d56314a0556fb346ef05ec489ac000f4d720d92c6b647e06"}, - {file = "azure_mgmt_applicationinsights-4.0.0-py3-none-any.whl", hash = "sha256:2b1ffd9a0114974455795c73a3a5d17c849e32b961d707d2db393b99254b576f"}, + {file = "azure_mgmt_applicationinsights-4.1.0-py3-none-any.whl", hash = "sha256:9e71f29b01e505a773501451d12fd6a10482cf4b13e9ac2bff72f5380496d979"}, + {file = "azure_mgmt_applicationinsights-4.1.0.tar.gz", hash = "sha256:15531390f12ce3d767cd3f1949af36aa39077c145c952fec4d80303c86ec7b6c"}, ] [package.dependencies] -azure-common = ">=1.1,<2.0" -azure-mgmt-core = ">=1.3.2,<2.0.0" -isodate = ">=0.6.1,<1.0.0" +azure-common = ">=1.1" +azure-mgmt-core = ">=1.3.2" +isodate = ">=0.6.1" +typing-extensions = ">=4.6.0" [[package]] name = "azure-mgmt-authorization" @@ -420,31 +422,32 @@ typing-extensions = ">=4.6.0" [[package]] name = "azure-mgmt-containerregistry" -version = "10.3.0" +version = "12.0.0" description = "Microsoft Azure Container Registry Client Library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure-mgmt-containerregistry-10.3.0.tar.gz", hash = "sha256:ae21651855dfb19c42d91d6b3a965c6c611e23f8bc4bf7138835e652d2f918e3"}, - {file = "azure_mgmt_containerregistry-10.3.0-py3-none-any.whl", hash = "sha256:851e1c57f9bc4a3589c6b21fb627c11fd6cbb57a0388b7dfccd530ba3160805f"}, + {file = "azure_mgmt_containerregistry-12.0.0-py3-none-any.whl", hash = "sha256:464abd4d3d9ecc0456ed8f63a6b9b93afc2e3e194f2d34f26a758afb67ad3b5c"}, + {file = "azure_mgmt_containerregistry-12.0.0.tar.gz", hash = "sha256:f19f8faa7881deaf2b5015c0eb050a92e2380cd9d18dee33cdb5f27d44a06c03"}, ] [package.dependencies] -azure-common = ">=1.1,<2.0" -azure-mgmt-core = ">=1.3.2,<2.0.0" -isodate = ">=0.6.1,<1.0.0" +azure-common = ">=1.1" +azure-mgmt-core = ">=1.3.2" +isodate = ">=0.6.1" +typing-extensions = ">=4.6.0" [[package]] name = "azure-mgmt-containerservice" -version = "34.0.0" +version = "34.1.0" description = "Microsoft Azure Container Service Management Client Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure_mgmt_containerservice-34.0.0-py3-none-any.whl", hash = "sha256:34be8172241e3c2c444682407970a938f60e3b2bd06304eaae0a1ba641f2262d"}, - {file = "azure_mgmt_containerservice-34.0.0.tar.gz", hash = "sha256:822d07828b746a5ea5408a8b3770f41dc424d6c4c28de53c29611b62bef8aea3"}, + {file = "azure_mgmt_containerservice-34.1.0-py3-none-any.whl", hash = "sha256:1faa1714e0100c6ee4cfb8d2eadb1c270b548a84b0070c74e9fe646056a5cb12"}, + {file = "azure_mgmt_containerservice-34.1.0.tar.gz", hash = "sha256:637a6cf8f06636c016ad151d76f9c7ba75bd05d4334b3dd7837eb8b517f30dbe"}, ] [package.dependencies] @@ -558,14 +561,14 @@ msrest = ">=0.6.21" [[package]] name = "azure-mgmt-resource" -version = "23.2.0" +version = "23.3.0" description = "Microsoft Azure Resource Management Client Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure_mgmt_resource-23.2.0-py3-none-any.whl", hash = "sha256:7af2bca928ecd58e57ea7f7731d245f45e9d927036d82f1d30b96baa0c26b569"}, - {file = "azure_mgmt_resource-23.2.0.tar.gz", hash = "sha256:747b750df7af23ab30e53d3f36247ab0c16de1e267d666b1a5077c39a4292529"}, + {file = "azure_mgmt_resource-23.3.0-py3-none-any.whl", hash = "sha256:ab216ee28e29db6654b989746e0c85a1181f66653929d2cb6e48fba66d9af323"}, + {file = "azure_mgmt_resource-23.3.0.tar.gz", hash = "sha256:fc4f1fd8b6aad23f8af4ed1f913df5f5c92df117449dc354fea6802a2829fea4"}, ] [package.dependencies] @@ -627,20 +630,21 @@ msrest = ">=0.6.21" [[package]] name = "azure-mgmt-storage" -version = "21.2.1" +version = "22.1.1" description = "Microsoft Azure Storage Management Client Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure-mgmt-storage-21.2.1.tar.gz", hash = "sha256:503a7ff9c31254092b0656445f5728bfdfda2d09d46a82e97019eaa9a1ecec64"}, - {file = "azure_mgmt_storage-21.2.1-py3-none-any.whl", hash = "sha256:f97df1fa39cde9dbacf2cd96c9cba1fc196932185e24853e276f74b18a0bd031"}, + {file = "azure_mgmt_storage-22.1.1-py3-none-any.whl", hash = "sha256:a4a4064918dcfa4f1cbebada5bf064935d66f2a3647a2f46a1f1c9348736f5d9"}, + {file = "azure_mgmt_storage-22.1.1.tar.gz", hash = "sha256:25aaa5ae8c40c30e2f91f8aae6f52906b0557e947d5c1b9817d4ff9decc11340"}, ] [package.dependencies] azure-common = ">=1.1" azure-mgmt-core = ">=1.3.2" isodate = ">=0.6.1" +typing-extensions = ">=4.6.0" [[package]] name = "azure-mgmt-subscription" @@ -789,14 +793,14 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "cachetools" -version = "5.5.1" +version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, - {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, + {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, + {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] [[package]] @@ -1356,38 +1360,38 @@ files = [ [[package]] name = "debugpy" -version = "1.8.12" +version = "1.8.14" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a"}, - {file = "debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45"}, - {file = "debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c"}, - {file = "debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9"}, - {file = "debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5"}, - {file = "debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7"}, - {file = "debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb"}, - {file = "debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1"}, - {file = "debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498"}, - {file = "debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06"}, - {file = "debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d"}, - {file = "debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969"}, - {file = "debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f"}, - {file = "debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9"}, - {file = "debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180"}, - {file = "debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c"}, - {file = "debugpy-1.8.12-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:b0232cd42506d0c94f9328aaf0d1d0785f90f87ae72d9759df7e5051be039738"}, - {file = "debugpy-1.8.12-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af40506a59450f1315168d47a970db1a65aaab5df3833ac389d2899a5d63b3f"}, - {file = "debugpy-1.8.12-cp38-cp38-win32.whl", hash = "sha256:5cc45235fefac57f52680902b7d197fb2f3650112379a6fa9aa1b1c1d3ed3f02"}, - {file = "debugpy-1.8.12-cp38-cp38-win_amd64.whl", hash = "sha256:557cc55b51ab2f3371e238804ffc8510b6ef087673303890f57a24195d096e61"}, - {file = "debugpy-1.8.12-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:b5c6c967d02fee30e157ab5227706f965d5c37679c687b1e7bbc5d9e7128bd41"}, - {file = "debugpy-1.8.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a77f422f31f170c4b7e9ca58eae2a6c8e04da54121900651dfa8e66c29901a"}, - {file = "debugpy-1.8.12-cp39-cp39-win32.whl", hash = "sha256:a4042edef80364239f5b7b5764e55fd3ffd40c32cf6753da9bda4ff0ac466018"}, - {file = "debugpy-1.8.12-cp39-cp39-win_amd64.whl", hash = "sha256:f30b03b0f27608a0b26c75f0bb8a880c752c0e0b01090551b9d87c7d783e2069"}, - {file = "debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6"}, - {file = "debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce"}, + {file = "debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339"}, + {file = "debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79"}, + {file = "debugpy-1.8.14-cp310-cp310-win32.whl", hash = "sha256:c442f20577b38cc7a9aafecffe1094f78f07fb8423c3dddb384e6b8f49fd2987"}, + {file = "debugpy-1.8.14-cp310-cp310-win_amd64.whl", hash = "sha256:f117dedda6d969c5c9483e23f573b38f4e39412845c7bc487b6f2648df30fe84"}, + {file = "debugpy-1.8.14-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9"}, + {file = "debugpy-1.8.14-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2"}, + {file = "debugpy-1.8.14-cp311-cp311-win32.whl", hash = "sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2"}, + {file = "debugpy-1.8.14-cp311-cp311-win_amd64.whl", hash = "sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01"}, + {file = "debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84"}, + {file = "debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826"}, + {file = "debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f"}, + {file = "debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f"}, + {file = "debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f"}, + {file = "debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15"}, + {file = "debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e"}, + {file = "debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e"}, + {file = "debugpy-1.8.14-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:d5582bcbe42917bc6bbe5c12db1bffdf21f6bfc28d4554b738bf08d50dc0c8c3"}, + {file = "debugpy-1.8.14-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5349b7c3735b766a281873fbe32ca9cca343d4cc11ba4a743f84cb854339ff35"}, + {file = "debugpy-1.8.14-cp38-cp38-win32.whl", hash = "sha256:7118d462fe9724c887d355eef395fae68bc764fd862cdca94e70dcb9ade8a23d"}, + {file = "debugpy-1.8.14-cp38-cp38-win_amd64.whl", hash = "sha256:d235e4fa78af2de4e5609073972700523e372cf5601742449970110d565ca28c"}, + {file = "debugpy-1.8.14-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:413512d35ff52c2fb0fd2d65e69f373ffd24f0ecb1fac514c04a668599c5ce7f"}, + {file = "debugpy-1.8.14-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c9156f7524a0d70b7a7e22b2e311d8ba76a15496fb00730e46dcdeedb9e1eea"}, + {file = "debugpy-1.8.14-cp39-cp39-win32.whl", hash = "sha256:b44985f97cc3dd9d52c42eb59ee9d7ee0c4e7ecd62bca704891f997de4cef23d"}, + {file = "debugpy-1.8.14-cp39-cp39-win_amd64.whl", hash = "sha256:b1528cfee6c1b1c698eb10b6b096c598738a8238822d218173d21c3086de8123"}, + {file = "debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20"}, + {file = "debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322"}, ] [[package]] @@ -1430,14 +1434,14 @@ word-list = ["pyahocorasick"] [[package]] name = "dill" -version = "0.3.9" +version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, - {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, ] [package.extras] @@ -1511,39 +1515,39 @@ steam = ["python3-openid (>=3.0.8)"] [[package]] name = "django-celery-beat" -version = "2.7.0" +version = "2.8.0" description = "Database-backed Periodic Tasks." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "django_celery_beat-2.7.0-py3-none-any.whl", hash = "sha256:851c680d8fbf608ca5fecd5836622beea89fa017bc2b3f94a5b8c648c32d84b1"}, - {file = "django_celery_beat-2.7.0.tar.gz", hash = "sha256:8482034925e09b698c05ad61c36ed2a8dbc436724a3fe119215193a4ca6dc967"}, + {file = "django_celery_beat-2.8.0-py3-none-any.whl", hash = "sha256:f8fd2e1ffbfa8e570ab9439383b2cd15a6642b347662d0de79c62ba6f68d4b38"}, + {file = "django_celery_beat-2.8.0.tar.gz", hash = "sha256:955bfb3c4b8f1026a8d20144d0da39c941e1eb23acbaee9e12a7e7cc1f74959a"}, ] [package.dependencies] celery = ">=5.2.3,<6.0" cron-descriptor = ">=1.2.32" -Django = ">=2.2,<5.2" +Django = ">=2.2,<6.0" django-timezone-field = ">=5.0" python-crontab = ">=2.3.4" tzdata = "*" [[package]] name = "django-celery-results" -version = "2.5.1" +version = "2.6.0" description = "Celery result backends for Django." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "django_celery_results-2.5.1-py3-none-any.whl", hash = "sha256:0da4cd5ecc049333e4524a23fcfc3460dfae91aa0a60f1fae4b6b2889c254e01"}, - {file = "django_celery_results-2.5.1.tar.gz", hash = "sha256:3ecb7147f773f34d0381bac6246337ce4cf88a2ea7b82774ed48e518b67bb8fd"}, + {file = "django_celery_results-2.6.0-py3-none-any.whl", hash = "sha256:b9ccdca2695b98c7cbbb8dea742311ba9a92773d71d7b4944a676e69a7df1c73"}, + {file = "django_celery_results-2.6.0.tar.gz", hash = "sha256:9abcd836ae6b61063779244d8887a88fe80bbfaba143df36d3cb07034671277c"}, ] [package.dependencies] celery = ">=5.2.7,<6.0" -Django = ">=3.2.18" +Django = ">=3.2.25" [[package]] name = "django-cors-headers" @@ -1702,26 +1706,26 @@ openapi = ["pyyaml (>=5.4)", "uritemplate (>=3.0.1)"] [[package]] name = "djangorestframework-simplejwt" -version = "5.4.0" +version = "5.5.0" description = "A minimal JSON Web Token authentication plugin for Django REST Framework" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "djangorestframework_simplejwt-5.4.0-py3-none-any.whl", hash = "sha256:7aec953db9ed4163430c16d086eecb0f028f814ce6bba62b06c25919261e9077"}, - {file = "djangorestframework_simplejwt-5.4.0.tar.gz", hash = "sha256:cccecce1a0e1a4a240fae80da73e5fc23055bababb8b67de88fa47cd36822320"}, + {file = "djangorestframework_simplejwt-5.5.0-py3-none-any.whl", hash = "sha256:4ef6b38af20cdde4a4a51d1fd8e063cbbabb7b45f149cc885d38d905c5a62edb"}, + {file = "djangorestframework_simplejwt-5.5.0.tar.gz", hash = "sha256:474a1b737067e6462b3609627a392d13a4da8a08b1f0574104ac6d7b1406f90e"}, ] [package.dependencies] django = ">=4.2" djangorestframework = ">=3.14" -pyjwt = ">=1.7.1,<3" +pyjwt = ">=1.7.1,<2.10.0" [package.extras] crypto = ["cryptography (>=3.3.1)"] -dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "freezegun", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx_rtd_theme (>=0.1.9)", "tox", "twine", "wheel"] +dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "freezegun", "ipython", "pre-commit", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "pyupgrade", "ruff", "sphinx_rtd_theme (>=0.1.9)", "tox", "twine", "wheel", "yesqa"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx_rtd_theme (>=0.1.9)"] -lint = ["flake8", "isort", "pep8"] +lint = ["pre-commit", "pyupgrade", "ruff", "yesqa"] python-jose = ["python-jose (==3.3.0)"] test = ["cryptography", "freezegun", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] @@ -1792,18 +1796,20 @@ poetry = ["poetry"] [[package]] name = "drf-extensions" -version = "0.7.1" +version = "0.8.0" description = "Extensions for Django REST Framework" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "drf-extensions-0.7.1.tar.gz", hash = "sha256:90abfc11a2221e8daf4cd54457e41ed38cd71134678de9622e806193db027db1"}, - {file = "drf_extensions-0.7.1-py2.py3-none-any.whl", hash = "sha256:007910437e64aa1d5ad6fc47266a4ac4280e31761e6458eb30fcac7494ac7d4e"}, + {file = "drf_extensions-0.8.0-py2.py3-none-any.whl", hash = "sha256:ab7bd854c9061c27ab55233b66d758001e5c2d81aaa9d117cbbe1c9ea49c77ab"}, + {file = "drf_extensions-0.8.0.tar.gz", hash = "sha256:c3f27bca74a2def53e8454a5c7b327595195df51e121743120b2f51ef5a52aaa"}, ] [package.dependencies] -djangorestframework = ">=3.9.3" +Django = ">=2.2,<6.0" +djangorestframework = ">=3.10.3" +packaging = ">=24.1" [[package]] name = "drf-nested-routers" @@ -1964,124 +1970,136 @@ python-dateutil = ">=2.7" [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {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"}, ] [[package]] name = "google-api-core" -version = "2.24.1" +version = "2.24.2" description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_core-2.24.1-py3-none-any.whl", hash = "sha256:bc78d608f5a5bf853b80bd70a795f703294de656c096c0968320830a4bc280f1"}, - {file = "google_api_core-2.24.1.tar.gz", hash = "sha256:f8b36f5456ab0dd99a1b693a40a31d1e7757beea380ad1b38faaf8941eae9d8a"}, + {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"}, ] [package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" +google-auth = ">=2.14.1,<3.0.0" +googleapis-common-protos = ">=1.56.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" +requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] @@ -2091,14 +2109,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.161.0" +version = "2.163.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_python_client-2.161.0-py2.py3-none-any.whl", hash = "sha256:9476a5a4f200bae368140453df40f9cda36be53fa7d0e9a9aac4cdb859a26448"}, - {file = "google_api_python_client-2.161.0.tar.gz", hash = "sha256:324c0cce73e9ea0a0d2afd5937e01b7c2d6a4d7e2579cdb6c384f9699d6c9f37"}, + {file = "google_api_python_client-2.163.0-py2.py3-none-any.whl", hash = "sha256:080e8bc0669cb4c1fb8efb8da2f5b91a2625d8f0e7796cfad978f33f7016c6c4"}, + {file = "google_api_python_client-2.163.0.tar.gz", hash = "sha256:88dee87553a2d82176e2224648bf89272d536c8f04dcdda37ef0a71473886dd7"}, ] [package.dependencies] @@ -2110,14 +2128,14 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.38.0" +version = "2.39.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, - {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, + {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"}, ] [package.dependencies] @@ -2126,12 +2144,14 @@ pyasn1-modules = ">=0.2.1" rsa = ">=3.1.4,<5" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +requests = ["requests (>=2.20.0,<3.0.0)"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +urllib3 = ["packaging", "urllib3"] [[package]] name = "google-auth-httplib2" @@ -2151,32 +2171,32 @@ httplib2 = ">=0.19.0" [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, + {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] [package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" [package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "gprof2dot" -version = "2024.6.6" +version = "2025.4.14" description = "Generate a dot graph from the output of several profilers." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "gprof2dot-2024.6.6-py2.py3-none-any.whl", hash = "sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696"}, - {file = "gprof2dot-2024.6.6.tar.gz", hash = "sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab"}, + {file = "gprof2dot-2025.4.14-py3-none-any.whl", hash = "sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e"}, + {file = "gprof2dot-2025.4.14.tar.gz", hash = "sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce"}, ] [[package]] @@ -2257,14 +2277,14 @@ files = [ [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.8" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, + {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, ] [package.dependencies] @@ -2347,14 +2367,14 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] @@ -2366,7 +2386,7 @@ 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)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -2383,14 +2403,14 @@ files = [ [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -2501,19 +2521,19 @@ referencing = ">=0.31.0" [[package]] name = "kombu" -version = "5.4.2" +version = "5.5.3" description = "Messaging library for Python." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "kombu-5.4.2-py3-none-any.whl", hash = "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763"}, - {file = "kombu-5.4.2.tar.gz", hash = "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf"}, + {file = "kombu-5.5.3-py3-none-any.whl", hash = "sha256:5b0dbceb4edee50aa464f59469d34b97864be09111338cfb224a10b6a163909b"}, + {file = "kombu-5.5.3.tar.gz", hash = "sha256:021a0e11fcfcd9b0260ef1fb64088c0e92beb976eb59c1dfca7ddd4ad4562ea2"}, ] [package.dependencies] amqp = ">=5.1.1,<6.0.0" -tzdata = {version = "*", markers = "python_version >= \"3.9\""} +tzdata = {version = ">=2025.2", markers = "python_version >= \"3.9\""} vine = "5.1.0" [package.extras] @@ -2521,15 +2541,16 @@ azureservicebus = ["azure-servicebus (>=7.10.0)"] azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] confluentkafka = ["confluent-kafka (>=2.2.0)"] consul = ["python-consul2 (==0.1.5)"] +gcpubsub = ["google-cloud-monitoring (>=2.16.0)", "google-cloud-pubsub (>=2.18.4)", "grpcio (==1.67.0)", "protobuf (==4.25.5)"] librabbitmq = ["librabbitmq (>=2.0.0) ; python_version < \"3.11\""] mongodb = ["pymongo (>=4.1.1)"] msgpack = ["msgpack (==1.1.0)"] pyro = ["pyro4 (==4.82)"] qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] -redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2)"] -slmq = ["softlayer-messaging (>=1.0.3)"] +redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2,<=5.2.1)"] +slmq = ["softlayer_messaging (>=1.0.3)"] sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] -sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5) ; sys_platform != \"win32\" and platform_python_implementation == \"CPython\"", "urllib3 (>=1.26.16)"] +sqs = ["boto3 (>=1.26.143)", "urllib3 (>=1.26.16)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=2.8.0)"] @@ -2817,18 +2838,18 @@ microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" [[package]] name = "msal" -version = "1.31.1" +version = "1.32.0" 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.31.1-py3-none-any.whl", hash = "sha256:29d9882de247e96db01386496d59f29035e5e841bcac892e6d7bf4390bf6bd17"}, - {file = "msal-1.31.1.tar.gz", hash = "sha256:11b5e6a3f802ffd3a72107203e20c4eac6ef53401961b880af2835b723d80578"}, + {file = "msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7"}, + {file = "msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2"}, ] [package.dependencies] -cryptography = ">=2.5,<46" +cryptography = ">=2.5,<47" PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" @@ -2837,30 +2858,32 @@ broker = ["pymsalruntime (>=0.14,<0.18) ; python_version >= \"3.6\" and platform [[package]] name = "msal-extensions" -version = "1.2.0" +version = "1.3.1" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msal_extensions-1.2.0-py3-none-any.whl", hash = "sha256:cf5ba83a2113fa6dc011a254a72f1c223c88d7dfad74cc30617c4679a417704d"}, - {file = "msal_extensions-1.2.0.tar.gz", hash = "sha256:6f41b320bfd2933d631a215c91ca0dd3e67d84bd1a2f50ce917d5874ec646bef"}, + {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, + {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, ] [package.dependencies] msal = ">=1.29,<2" -portalocker = ">=1.4,<3" + +[package.extras] +portalocker = ["portalocker (>=1.4,<4)"] [[package]] name = "msgraph-core" -version = "1.3.1" +version = "1.3.3" description = "Core component of the Microsoft Graph Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msgraph_core-1.3.1-py3-none-any.whl", hash = "sha256:a6e3c19c36d43d00edfb49225647a357f8189351c226c3b7c020c5dfaa24171b"}, - {file = "msgraph_core-1.3.1.tar.gz", hash = "sha256:91c721b4c741d0e9a536e3c43e1cbd2254928b4d207e4f994e9cc7cb7a25bd74"}, + {file = "msgraph_core-1.3.3-py3-none-any.whl", hash = "sha256:9dbbc0c88e174c1d5da1c17d286965d6b26ebaf24996c7af64a39e2069006cf6"}, + {file = "msgraph_core-1.3.3.tar.gz", hash = "sha256:a3226b08b4cf9b6dbb16b868be21d5f82d8ee514ae8e46d9f0cad896159ef8d3"}, ] [package.dependencies] @@ -2874,26 +2897,23 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"] [[package]] name = "msgraph-sdk" -version = "1.18.0" +version = "1.23.0" description = "The Microsoft Graph Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msgraph_sdk-1.18.0-py3-none-any.whl", hash = "sha256:f09b015bb9d7690bc6f30c9a28f9a414107aaf06be4952c27b3653dcdf33f2a3"}, - {file = "msgraph_sdk-1.18.0.tar.gz", hash = "sha256:ef49166ada7b459b5a843ceb3d253c1ab99d8987ebf3112147eb6cbcaa101793"}, + {file = "msgraph_sdk-1.23.0-py3-none-any.whl", hash = "sha256:58e0047b4ca59fd82022c02cd73fec0170a3d84f3b76721e3db2a0314df9a58a"}, + {file = "msgraph_sdk-1.23.0.tar.gz", hash = "sha256:6dd1ba9a46f5f0ce8599fd9610133adbd9d1493941438b5d3632fce9e55ed607"}, ] [package.dependencies] azure-identity = ">=1.12.0" -microsoft-kiota-abstractions = ">=1.3.0,<2.0.0" -microsoft-kiota-authentication-azure = ">=1.0.0,<2.0.0" -microsoft-kiota-http = ">=1.0.0,<2.0.0" -microsoft-kiota-serialization-form = ">=0.1.0" -microsoft-kiota-serialization-json = ">=1.3.0,<2.0.0" -microsoft-kiota-serialization-multipart = ">=0.1.0" -microsoft-kiota-serialization-text = ">=1.0.0,<2.0.0" -msgraph_core = ">=1.0.0" +microsoft-kiota-serialization-form = ">=1.8.0,<2.0.0" +microsoft-kiota-serialization-json = ">=1.8.0,<2.0.0" +microsoft-kiota-serialization-multipart = ">=1.8.0,<2.0.0" +microsoft-kiota-serialization-text = ">=1.8.0,<2.0.0" +msgraph_core = ">=1.3.1" [package.extras] dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"] @@ -2922,104 +2942,116 @@ async = ["aiodns ; python_version >= \"3.5\"", "aiohttp (>=3.0) ; python_version [[package]] name = "multidict" -version = "6.1.0" +version = "6.4.3" description = "multidict implementation" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, + {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"}, ] [[package]] @@ -3071,42 +3103,39 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "narwhals" -version = "1.25.2" +version = "1.35.0" description = "Extremely lightweight compatibility layer between dataframe libraries" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "narwhals-1.25.2-py3-none-any.whl", hash = "sha256:e645f7fc1f8c0a3563a6cdcd0191586cdf88470ad90f0818abba7ceb6c181b00"}, - {file = "narwhals-1.25.2.tar.gz", hash = "sha256:37594746fc06fe4a588967a34a2974b1f3a7ad6ff1571b6e31ac5e58c9591000"}, + {file = "narwhals-1.35.0-py3-none-any.whl", hash = "sha256:7562af132fa3f8aaaf34dc96d7ec95bdca29d1c795e8fcf14e01edf1d32122bc"}, + {file = "narwhals-1.35.0.tar.gz", hash = "sha256:07477d18487fbc940243b69818a177ed7119b737910a8a254fb67688b48a7c96"}, ] [package.extras] -core = ["duckdb", "pandas", "polars", "pyarrow", "pyarrow-stubs"] cudf = ["cudf (>=24.10.0)"] dask = ["dask[dataframe] (>=2024.8)"] -dev = ["covdefaults", "hypothesis", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-randomly", "typing-extensions"] -docs = ["black", "duckdb", "jinja2", "markdown-exec[ansi]", "mkdocs", "mkdocs-autorefs", "mkdocs-material", "mkdocstrings[python]", "pandas", "polars (>=1.0.0)", "pyarrow"] duckdb = ["duckdb (>=1.0)"] -extra = ["scikit-learn"] ibis = ["ibis-framework (>=6.0.0)", "packaging", "pyarrow-hotfix", "rich"] modin = ["modin"] pandas = ["pandas (>=0.25.3)"] polars = ["polars (>=0.20.3)"] pyarrow = ["pyarrow (>=11.0.0)"] pyspark = ["pyspark (>=3.5.0)"] +sqlframe = ["sqlframe (>=3.22.0)"] [[package]] name = "nest-asyncio" @@ -3194,63 +3223,63 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "opentelemetry-api" -version = "1.30.0" +version = "1.32.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_api-1.30.0-py3-none-any.whl", hash = "sha256:d5f5284890d73fdf47f843dda3210edf37a38d66f44f2b5aedc1e89ed455dc09"}, - {file = "opentelemetry_api-1.30.0.tar.gz", hash = "sha256:375893400c1435bf623f7dfb3bcd44825fe6b56c34d0667c542ea8257b1a1240"}, + {file = "opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724"}, + {file = "opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.5.0" +importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-sdk" -version = "1.30.0" +version = "1.32.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.30.0-py3-none-any.whl", hash = "sha256:14fe7afc090caad881addb6926cec967129bd9260c4d33ae6a217359f6b61091"}, - {file = "opentelemetry_sdk-1.30.0.tar.gz", hash = "sha256:c9287a9e4a7614b9946e933a67168450b9ab35f08797eb9bc77d998fa480fa18"}, + {file = "opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17"}, + {file = "opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092"}, ] [package.dependencies] -opentelemetry-api = "1.30.0" -opentelemetry-semantic-conventions = "0.51b0" +opentelemetry-api = "1.32.1" +opentelemetry-semantic-conventions = "0.53b1" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.51b0" +version = "0.53b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.51b0-py3-none-any.whl", hash = "sha256:fdc777359418e8d06c86012c3dc92c88a6453ba662e941593adb062e48c2eeae"}, - {file = "opentelemetry_semantic_conventions-0.51b0.tar.gz", hash = "sha256:3fabf47f35d1fd9aebcdca7e6802d86bd5ebc3bc3408b7e3248dde6e87a18c47"}, + {file = "opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208"}, + {file = "opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.30.0" +opentelemetry-api = "1.32.1" [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -3356,31 +3385,31 @@ setuptools = "*" [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "plotly" -version = "6.0.0" -description = "An open-source, interactive data visualization library for Python" +version = "6.0.1" +description = "An open-source interactive data visualization library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "plotly-6.0.0-py3-none-any.whl", hash = "sha256:f708871c3a9349a68791ff943a5781b1ec04de7769ea69068adcd9202e57653a"}, - {file = "plotly-6.0.0.tar.gz", hash = "sha256:c4aad38b8c3d65e4a5e7dd308b084143b9025c2cc9d5317fc1f1d30958db87d3"}, + {file = "plotly-6.0.1-py3-none-any.whl", hash = "sha256:4714db20fea57a435692c548a4eb4fae454f7daddf15f8d8ba7e1045681d7768"}, + {file = "plotly-6.0.1.tar.gz", hash = "sha256:dd8400229872b6e3c964b099be699f8d00c489a974f2cfccfad5e8240873366b"}, ] [package.dependencies] @@ -3407,35 +3436,15 @@ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] -name = "portalocker" -version = "2.10.1" -description = "Wraps the portalocker recipe for easy usage" +name = "prompt-toolkit" +version = "3.0.51" +description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"}, - {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"}, -] - -[package.dependencies] -pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} - -[package.extras] -docs = ["sphinx (>=1.7.1)"] -redis = ["redis"] -tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.50" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, - {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -3443,138 +3452,152 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.2.1" +version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, - {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, - {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, - {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, - {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, - {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, - {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, - {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, - {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, - {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, - {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, - {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, - {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, + {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"}, ] [[package]] name = "proto-plus" -version = "1.26.0" +version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"}, - {file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"}, + {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, + {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, ] [package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" +protobuf = ">=3.19.0,<7.0.0" [package.extras] testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "5.29.3" +version = "6.30.2" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, - {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, - {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, - {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, - {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, - {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, - {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, - {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, - {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, + {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"}, ] [[package]] name = "prowler" -version = "5.4.0" +version = "5.6.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" @@ -3585,23 +3608,23 @@ develop = false [package.dependencies] alive-progress = "3.2.0" awsipranges = "0.3.3" -azure-identity = "1.19.0" +azure-identity = "1.21.0" azure-keyvault-keys = "4.10.0" -azure-mgmt-applicationinsights = "4.0.0" +azure-mgmt-applicationinsights = "4.1.0" azure-mgmt-authorization = "4.0.0" azure-mgmt-compute = "34.0.0" -azure-mgmt-containerregistry = "10.3.0" -azure-mgmt-containerservice = "34.0.0" +azure-mgmt-containerregistry = "12.0.0" +azure-mgmt-containerservice = "34.1.0" azure-mgmt-cosmosdb = "9.7.0" azure-mgmt-keyvault = "10.3.1" azure-mgmt-monitor = "6.0.2" azure-mgmt-network = "28.1.0" azure-mgmt-rdbms = "10.1.0" -azure-mgmt-resource = "23.2.0" +azure-mgmt-resource = "23.3.0" azure-mgmt-search = "9.1.0" azure-mgmt-security = "7.0.0" azure-mgmt-sql = "3.0.1" -azure-mgmt-storage = "21.2.1" +azure-mgmt-storage = "22.1.1" azure-mgmt-subscription = "3.1.1" azure-mgmt-web = "8.0.0" azure-storage-blob = "12.24.1" @@ -3612,29 +3635,29 @@ cryptography = "44.0.1" dash = "2.18.2" dash-bootstrap-components = "1.6.0" detect-secrets = "1.5.0" -google-api-python-client = "2.161.0" +google-api-python-client = "2.163.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" kubernetes = "32.0.1" microsoft-kiota-abstractions = "1.9.2" -msgraph-sdk = "1.18.0" +msgraph-sdk = "1.23.0" numpy = "2.0.2" pandas = "2.2.3" py-ocsf-models = "0.3.1" pydantic = "1.10.21" -python-dateutil = "^2.9.0.post0" +python-dateutil = ">=2.9.0.post0,<3.0.0" pytz = "2025.1" schema = "0.7.7" shodan = "1.31.0" slack-sdk = "3.34.0" tabulate = "0.9.0" -tzlocal = "5.2" +tzlocal = "5.3.1" [package.source] type = "git" url = "https://github.com/prowler-cloud/prowler.git" reference = "master" -resolved_reference = "931df361bf5ee287139112f485580241099094cb" +resolved_reference = "0edf19928269b6d42d1c463ab13b90d7c21a65e4" [[package]] name = "psutil" @@ -3779,29 +3802,29 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.4.1" +version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" +pyasn1 = ">=0.6.1,<0.7.0" [[package]] name = "pycodestyle" -version = "2.12.1" +version = "2.13.0" description = "Python style guide checker" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, - {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, + {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, + {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, ] [[package]] @@ -3819,44 +3842,44 @@ files = [ [[package]] name = "pycurl" -version = "7.45.4" +version = "7.45.6" description = "PycURL -- A Python Interface To The cURL library" optional = false python-versions = ">=3.5" groups = ["main"] markers = "sys_platform != \"win32\" and platform_python_implementation == \"CPython\"" files = [ - {file = "pycurl-7.45.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:247b4af8eab7d04137a7f1a98391930e04ea93dc669b64db5625070fe15f80a3"}, - {file = "pycurl-7.45.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:561f88697f7540634b1c750146f37bdc0da367b15f6b4ab2bb780871ee6ab005"}, - {file = "pycurl-7.45.4-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b485fdaf78553f0b8e1c2803bb7dcbe47a7b47594f846fc7e9d3b94d794cfc89"}, - {file = "pycurl-7.45.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e7ae49b88a5d57485fbabef004534225dfe04dc15716a61fae1a0c7f46f2279e"}, - {file = "pycurl-7.45.4-cp310-cp310-win32.whl", hash = "sha256:d14f954ecd21a070038d65ef1c6d1d3ab220f952ff703d48313123222097615c"}, - {file = "pycurl-7.45.4-cp310-cp310-win_amd64.whl", hash = "sha256:2548c3291a33c821f0f80bf9989fc43b5d90fb78b534a7015c8419b83c6f5803"}, - {file = "pycurl-7.45.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6c0e22052946bbfa25be67f9d1d6639eff10781c89f0cf6f3ff2099273d1bad"}, - {file = "pycurl-7.45.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acf25cfdaf914db21a2a6e9e274b6d95e3fa2b6018c38f2c58c94b5d8ac3d1b7"}, - {file = "pycurl-7.45.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a39f28f031885485325034918386be352036c220ca45625c7e286d3938eb579d"}, - {file = "pycurl-7.45.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:9940e3234c1ca3d30f27a2202d325dbc25291605c98e9585100a351cacd935e8"}, - {file = "pycurl-7.45.4-cp311-cp311-win32.whl", hash = "sha256:ffd3262f98b8997ad04940061d5ebd8bab2362169b9440939c397e24a4a135b0"}, - {file = "pycurl-7.45.4-cp311-cp311-win_amd64.whl", hash = "sha256:1324a859b50bdb0abdbd5620e42f74240d0b7daf2d5925fa303695d9fc3ece18"}, - {file = "pycurl-7.45.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:731c46e7c0acffaab19f7c2ecc3d9e7ee337500e87b260b4e0b9fae2d90fa133"}, - {file = "pycurl-7.45.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13eb1643ab0bf4fdc539a2cdf1021029b07095d3196c5cee5a4271af268d3d31"}, - {file = "pycurl-7.45.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:df5f94c051c5a163fa85064559ca94979575e2da26740ff91c078c50c541c465"}, - {file = "pycurl-7.45.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:688d09ba2c6a0d4a749d192c43422839d73c40c85143c50cc65c944258fe0ba8"}, - {file = "pycurl-7.45.4-cp312-cp312-win32.whl", hash = "sha256:236600bfe2cd72efe47333add621286667e8fa027dadf1247349afbf30333e95"}, - {file = "pycurl-7.45.4-cp312-cp312-win_amd64.whl", hash = "sha256:26745c6c5ebdccfe8a828ac3fd4e6da6f5d2245696604f04529eb7894a02f4db"}, - {file = "pycurl-7.45.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bd493ce598f1dc76c8e50043c47debec27c583fa313a836b2d3667640f875d5"}, - {file = "pycurl-7.45.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f25d52c97dbca6ebea786f0961b49c1998fa05178abf1964a977c825b3d8ae6"}, - {file = "pycurl-7.45.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:13c4b18f44637859f34639493efd297a08670f45e4eec34ab2dcba724e3cb5fc"}, - {file = "pycurl-7.45.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0470bff6cc24d8c2f63c80931aa239463800871609dafc6bcc9ca10f5a12a04e"}, - {file = "pycurl-7.45.4-cp313-cp313-win32.whl", hash = "sha256:3452459668bd01d646385482362b021834a31c036aa1c02acd88924ddeff7d0d"}, - {file = "pycurl-7.45.4-cp313-cp313-win_amd64.whl", hash = "sha256:fd167f73d34beb0cb8064334aee76d9bdd13167b30be6d5d36fb07d0c8223b71"}, - {file = "pycurl-7.45.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b0e38e3eb83b0c891f391853f798fc6a97cb5a86a4a731df0b6320e539ae54ae"}, - {file = "pycurl-7.45.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d192a48b3cec2e13ad432196b65c22e99620db92feae39c0476635354eff68c6"}, - {file = "pycurl-7.45.4-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:57971d7215fc6fdedcfc092f880a59f04f52fcaf2fd329151b931623d7b59a9c"}, - {file = "pycurl-7.45.4-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:73df3eb5940a7fbf4cf62f7271e9f23a8e9f80e352c838ee9a8448a70c01d3f5"}, - {file = "pycurl-7.45.4-cp39-cp39-win32.whl", hash = "sha256:587a4891039803b5f48392066f97b7cd5e7e9a166187abb5cb4b4806fdb8fbef"}, - {file = "pycurl-7.45.4-cp39-cp39-win_amd64.whl", hash = "sha256:caec8b634763351dd4e1b729a71542b1e2de885d39710ba8e7202817a381b453"}, - {file = "pycurl-7.45.4.tar.gz", hash = "sha256:32c8e237069273f4260b6ae13d1e0f99daae938977016021565dc6e11050e803"}, + {file = "pycurl-7.45.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c31b390f1e2cd4525828f1bb78c1f825c0aab5d1588228ed71b22c4784bdb593"}, + {file = "pycurl-7.45.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:942b352b69184cb26920db48e0c5cb95af39874b57dbe27318e60f1e68564e37"}, + {file = "pycurl-7.45.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3441ee77e830267aa6e2bb43b29fd5f8a6bd6122010c76a6f0bf84462e9ea9c7"}, + {file = "pycurl-7.45.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2a21e13278d7553a04b421676c458449f6c10509bebf04993f35154b06ee2b20"}, + {file = "pycurl-7.45.6-cp310-cp310-win32.whl", hash = "sha256:d0b5501d527901369aba307354530050f56cd102410f2a3bacd192dc12c645e3"}, + {file = "pycurl-7.45.6-cp310-cp310-win_amd64.whl", hash = "sha256:abe1b204a2f96f2eebeaf93411f03505b46d151ef6d9d89326e6dece7b3a008a"}, + {file = "pycurl-7.45.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f57ad26d6ab390391ad5030790e3f1a831c1ee54ad3bf969eb378f5957eeb0a"}, + {file = "pycurl-7.45.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6fd295f03c928da33a00f56c91765195155d2ac6f12878f6e467830b5dce5f5"}, + {file = "pycurl-7.45.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:334721ce1ccd71ff8e405470768b3d221b4393570ccc493fcbdbef4cd62e91ed"}, + {file = "pycurl-7.45.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0cd6b7794268c17f3c660162ed6381769ce0ad260331ef49191418dfc3a2d61a"}, + {file = "pycurl-7.45.6-cp311-cp311-win32.whl", hash = "sha256:357ea634395310085b9d5116226ac5ec218a6ceebf367c2451ebc8d63a6e9939"}, + {file = "pycurl-7.45.6-cp311-cp311-win_amd64.whl", hash = "sha256:878ae64484db18f8f10ba99bffc83fefb4fe8f5686448754f93ec32fa4e4ee93"}, + {file = "pycurl-7.45.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c872d4074360964697c39c1544fe8c91bfecbff27c1cdda1fee5498e5fdadcda"}, + {file = "pycurl-7.45.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56d1197eadd5774582b259cde4364357da71542758d8e917f91cc6ed7ed5b262"}, + {file = "pycurl-7.45.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8a99e56d2575aa74c48c0cd08852a65d5fc952798f76a34236256d5589bf5aa0"}, + {file = "pycurl-7.45.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c04230b9e9cfdca9cf3eb09a0bec6cf2f084640f1f1ca1929cca51411af85de2"}, + {file = "pycurl-7.45.6-cp312-cp312-win32.whl", hash = "sha256:ae893144b82d72d95c932ebdeb81fc7e9fde758e5ecd5dd10ad5b67f34a8b8ee"}, + {file = "pycurl-7.45.6-cp312-cp312-win_amd64.whl", hash = "sha256:56f841b6f2f7a8b2d3051b9ceebd478599dbea3c8d1de8fb9333c895d0c1eea5"}, + {file = "pycurl-7.45.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c09b7180799af70fc1d4eed580cfb1b9f34fda9081f73a3e3bc9a0e5a4c0e9b"}, + {file = "pycurl-7.45.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:361bf94b2a057c7290f9ab84e935793ca515121fc012f4b6bef6c3b5e4ea4397"}, + {file = "pycurl-7.45.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb9eff0c7794af972da769a887c87729f1bcd8869297b1c01a2732febbb75876"}, + {file = "pycurl-7.45.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:26839d43dc7fff6b80e0067f185cc1d0e9be2ae6e2e2361ae8488cead5901c04"}, + {file = "pycurl-7.45.6-cp313-cp313-win32.whl", hash = "sha256:a721c2696a71b1aa5ecf82e6d0ade64bc7211b7317f1c9c66e82f82e2264d8b4"}, + {file = "pycurl-7.45.6-cp313-cp313-win_amd64.whl", hash = "sha256:f0198ebcda8686b3a0c66d490a687fa5fd466f8ecc2f20a0ed0931579538ae3d"}, + {file = "pycurl-7.45.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a554a2813d415a7bb9a996a6298f3829f57e987635dcab9f1197b2dccd0ab3b2"}, + {file = "pycurl-7.45.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f721e3394e5bd7079802ec1819b19c5be4842012268cc45afcb3884efb31cf0"}, + {file = "pycurl-7.45.6-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:81005c0f681d31d5af694d1d3c18bbf1bed0bc8b2bb10fb7388cb1378ba9bd6a"}, + {file = "pycurl-7.45.6-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:3fc0b505c37c7c54d88ced27e1d9e3241130987c24bf1611d9bbd9a3e499e07c"}, + {file = "pycurl-7.45.6-cp39-cp39-win32.whl", hash = "sha256:1309fc0f558a80ca444a3a5b0bdb1572a4d72b195233f0e65413b4d4dd78809b"}, + {file = "pycurl-7.45.6-cp39-cp39-win_amd64.whl", hash = "sha256:2d1a49418b8b4c61f52e06d97b9c16142b425077bd997a123a2ba9ef82553203"}, + {file = "pycurl-7.45.6.tar.gz", hash = "sha256:2b73e66b22719ea48ac08a93fc88e57ef36d46d03cb09d972063c9aa86bb74e6"}, ] [[package]] @@ -3943,14 +3966,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, - {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, ] [package.dependencies] @@ -3992,14 +4015,14 @@ testutils = ["gitpython (>3)"] [[package]] name = "pyparsing" -version = "3.2.1" +version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, - {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, ] [package.extras] @@ -4099,14 +4122,14 @@ testing = ["Django", "django-configurations (>=2.0)"] [[package]] name = "pytest-docker-tools" -version = "3.1.3" +version = "3.1.9" description = "Docker integration tests for pytest" optional = false -python-versions = ">=3.7.0,<4.0.0" +python-versions = "<4.0.0,>=3.9.0" groups = ["main"] files = [ - {file = "pytest_docker_tools-3.1.3-py3-none-any.whl", hash = "sha256:63e659043160f41d89f94ea42616102594bcc85682aac394fcbc14f14cd1b189"}, - {file = "pytest_docker_tools-3.1.3.tar.gz", hash = "sha256:c7e28841839d67b3ac80ad7b345b953701d5ae61ffda97586114244292aeacc0"}, + {file = "pytest_docker_tools-3.1.9-py2.py3-none-any.whl", hash = "sha256:36f8e88d56d84ea177df68a175673681243dd991d2807fbf551d90f60341bfdb"}, + {file = "pytest_docker_tools-3.1.9.tar.gz", hash = "sha256:1b6a0cb633c20145731313335ef15bcf5571839c06726764e60cbe495324782b"}, ] [package.dependencies] @@ -4227,32 +4250,30 @@ files = [ [[package]] name = "pywin32" -version = "308" +version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] -markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -4426,14 +4447,14 @@ six = ">=1.7.0" [[package]] name = "rich" -version = "13.9.4" +version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["dev"] files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, ] [package.dependencies] @@ -4445,127 +4466,138 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.22.3" +version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, - {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, - {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, - {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, - {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, - {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, - {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, - {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, - {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, - {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, - {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, - {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, - {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, + {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"}, ] [[package]] name = "rsa" -version = "4.9" +version = "4.9.1" description = "Pure-Python RSA implementation" optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" groups = ["main"] files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, ] [package.dependencies] @@ -4757,14 +4789,14 @@ files = [ [[package]] name = "sentry-sdk" -version = "2.20.0" +version = "2.26.1" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "sentry_sdk-2.20.0-py2.py3-none-any.whl", hash = "sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364"}, - {file = "sentry_sdk-2.20.0.tar.gz", hash = "sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab"}, + {file = "sentry_sdk-2.26.1-py2.py3-none-any.whl", hash = "sha256:e99390e3f217d13ddcbaeaed08789f1ca614d663b345b9da42e35ad6b60d696a"}, + {file = "sentry_sdk-2.26.1.tar.gz", hash = "sha256:759e019c41551a21519a95e6cef6d91fb4af1054761923dadaee2e6eca9c02c7"}, ] [package.dependencies] @@ -4809,24 +4841,25 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] +statsig = ["statsig (>=0.55.3)"] tornado = ["tornado (>=6)"] unleash = ["UnleashClient (>=6.0.1)"] [[package]] name = "setuptools" -version = "75.8.0" +version = "79.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, - {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, + {file = "setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a"}, + {file = "setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] @@ -4933,14 +4966,14 @@ files = [ [[package]] name = "stevedore" -version = "5.4.0" +version = "5.4.1" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "stevedore-5.4.0-py3-none-any.whl", hash = "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857"}, - {file = "stevedore-5.4.0.tar.gz", hash = "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d"}, + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, ] [package.dependencies] @@ -4963,14 +4996,14 @@ widechars = ["wcwidth"] [[package]] name = "tenacity" -version = "9.0.0" +version = "9.1.2" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, - {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] [package.extras] @@ -4979,14 +5012,14 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tldextract" -version = "5.1.3" +version = "5.3.0" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tldextract-5.1.3-py3-none-any.whl", hash = "sha256:78de310cc2ca018692de5ddf320f9d6bd7c5cf857d0fd4f2175f0cdf4440ea75"}, - {file = "tldextract-5.1.3.tar.gz", hash = "sha256:d43c7284c23f5dc8a42fd0fee2abede2ff74cc622674e4cb07f514ab3330c338"}, + {file = "tldextract-5.3.0-py3-none-any.whl", hash = "sha256:f70f31d10b55c83993f55e91ecb7c5d84532a8972f22ec578ecfbe5ea2292db2"}, + {file = "tldextract-5.3.0.tar.gz", hash = "sha256:b3d2b70a1594a0ecfa6967d57251527d58e00bb5a91a74387baa0d87a0678609"}, ] [package.dependencies] @@ -5035,14 +5068,14 @@ telegram = ["requests"] [[package]] name = "typer" -version = "0.15.1" +version = "0.15.2" 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.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, - {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, + {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, + {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, ] [package.dependencies] @@ -5053,39 +5086,39 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] [[package]] name = "tzdata" -version = "2025.1" +version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main", "dev"] files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] markers = {dev = "sys_platform == \"win32\""} [[package]] name = "tzlocal" -version = "5.2" +version = "5.3.1" description = "tzinfo object for the local timezone" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, - {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, + {file = "tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"}, + {file = "tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd"}, ] [package.dependencies] @@ -5108,14 +5141,14 @@ files = [ [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] @@ -5298,112 +5331,134 @@ files = [ [[package]] name = "xlsxwriter" -version = "3.2.2" +version = "3.2.3" description = "A Python module for creating Excel XLSX files." optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "XlsxWriter-3.2.2-py3-none-any.whl", hash = "sha256:272ce861e7fa5e82a4a6ebc24511f2cb952fde3461f6c6e1a1e81d3272db1471"}, - {file = "xlsxwriter-3.2.2.tar.gz", hash = "sha256:befc7f92578a85fed261639fb6cde1fd51b79c5e854040847dde59d4317077dc"}, + {file = "XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d"}, + {file = "xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5"}, ] [[package]] name = "yarl" -version = "1.18.3" +version = "1.20.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {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"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -propcache = ">=0.2.0" +propcache = ">=0.2.1" [[package]] name = "zipp" @@ -5428,4 +5483,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "0da43d42c9cdb0b990cfd184ba776e3ce2a5ee72fbd076e980d7111c772d6000" +content-hash = "f3ede96fb76c14d02da37c1132b41a14fa4045787807446fac2cd1750be193e0" diff --git a/api/pyproject.toml b/api/pyproject.toml index d8e74d6260..974a369ca2 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -35,7 +35,7 @@ name = "prowler-api" package-mode = false # Needed for the SDK compatibility requires-python = ">=3.11,<3.13" -version = "1.6.0" +version = "1.7.0" [project.scripts] celery = "src.backend.config.settings.celery" @@ -46,6 +46,7 @@ coverage = "7.5.4" django-silk = "5.3.2" docker = "7.1.0" freezegun = "1.5.1" +marshmallow = ">=3.15.0,<4.0.0" mypy = "1.10.1" pylint = "3.2.5" pytest = "8.2.2" diff --git a/api/src/backend/api/migrations/0017_m365_provider.py b/api/src/backend/api/migrations/0017_m365_provider.py new file mode 100644 index 0000000000..62817560c5 --- /dev/null +++ b/api/src/backend/api/migrations/0017_m365_provider.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1.7 on 2025-04-16 08:47 + +from django.db import migrations + +import api.db_utils + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0016_finding_compliance_resource_details_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="provider", + name="provider", + field=api.db_utils.ProviderEnumField( + choices=[ + ("aws", "AWS"), + ("azure", "Azure"), + ("gcp", "GCP"), + ("kubernetes", "Kubernetes"), + ("m365", "M365"), + ], + default="aws", + ), + ), + migrations.RunSQL( + "ALTER TYPE provider ADD VALUE IF NOT EXISTS 'm365';", + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 69b7ead46a..01f88178f0 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -191,6 +191,7 @@ class Provider(RowLevelSecurityProtectedModel): AZURE = "azure", _("Azure") GCP = "gcp", _("GCP") KUBERNETES = "kubernetes", _("Kubernetes") + M365 = "m365", _("M365") @staticmethod def validate_aws_uid(value): @@ -214,6 +215,15 @@ class Provider(RowLevelSecurityProtectedModel): pointer="/data/attributes/uid", ) + @staticmethod + def validate_m365_uid(value): + if not re.match(r"^[a-zA-Z0-9-]+\.onmicrosoft\.com$", value): + raise ModelValidationError( + detail="M365 tenant ID must be a valid domain.", + code="m365-uid", + pointer="/data/attributes/uid", + ) + @staticmethod def validate_gcp_uid(value): if not re.match(r"^[a-z][a-z0-9-]{5,29}$", value): diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index c7862ac8f1..962ec773c2 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Prowler API - version: 1.6.0 + version: 1.7.0 description: |- Prowler API specification. @@ -83,11 +83,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -99,6 +101,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -106,6 +109,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -450,11 +454,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -466,6 +472,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -473,6 +480,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -962,11 +970,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -978,6 +988,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -985,6 +996,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -1395,11 +1407,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -1411,6 +1425,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -1418,6 +1433,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -2047,11 +2063,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -2063,6 +2081,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -2070,6 +2089,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -2204,11 +2224,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -2220,6 +2242,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -2227,6 +2250,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -2377,11 +2401,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -2393,6 +2419,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -2400,6 +2427,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -2863,11 +2891,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider__in] schema: @@ -3441,11 +3471,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -3457,6 +3489,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -3464,6 +3497,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -4167,11 +4201,13 @@ paths: - azure - gcp - kubernetes + - m365 description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 - in: query name: filter[provider_type__in] schema: @@ -4183,6 +4219,7 @@ paths: - azure - gcp - kubernetes + - m365 description: |- Multiple values may be separated by commas. @@ -4190,6 +4227,7 @@ paths: * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 explode: false style: form - in: query @@ -8388,6 +8426,33 @@ components: - client_id - client_secret - tenant_id + - type: object + title: M365 Static Credentials + properties: + client_id: + type: string + description: The Azure application (client) ID for authentication + in Azure AD. + client_secret: + type: string + description: The client secret associated with the application + (client) ID, providing secure access. + tenant_id: + type: string + description: The Azure tenant ID, representing the directory + where the application is registered. + user: + type: email + description: User microsoft email address. + encrypted_password: + type: string + description: User encrypted password. + required: + - client_id + - client_secret + - tenant_id + - user + - encrypted_password - type: object title: GCP Static Credentials properties: @@ -8855,12 +8920,14 @@ components: - azure - gcp - kubernetes + - m365 type: string description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 uid: type: string title: Unique identifier for the provider, set by the provider @@ -8967,12 +9034,14 @@ components: - azure - gcp - kubernetes + - m365 type: string description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 uid: type: string title: Unique identifier for the provider, set by the provider @@ -9010,12 +9079,14 @@ components: - azure - gcp - kubernetes + - m365 type: string description: |- * `aws` - AWS * `azure` - Azure * `gcp` - GCP * `kubernetes` - Kubernetes + * `m365` - M365 uid: type: string minLength: 3 @@ -9600,6 +9671,33 @@ components: - client_id - client_secret - tenant_id + - type: object + title: M365 Static Credentials + properties: + client_id: + type: string + description: The Azure application (client) ID for authentication + in Azure AD. + client_secret: + type: string + description: The client secret associated with the application + (client) ID, providing secure access. + tenant_id: + type: string + description: The Azure tenant ID, representing the directory where + the application is registered. + user: + type: email + description: User microsoft email address. + encrypted_password: + type: string + description: User encrypted password. + required: + - client_id + - client_secret + - tenant_id + - user + - encrypted_password - type: object title: GCP Static Credentials properties: @@ -9782,6 +9880,33 @@ components: - client_id - client_secret - tenant_id + - type: object + title: M365 Static Credentials + properties: + client_id: + type: string + description: The Azure application (client) ID for authentication + in Azure AD. + client_secret: + type: string + description: The client secret associated with the application + (client) ID, providing secure access. + tenant_id: + type: string + description: The Azure tenant ID, representing the directory + where the application is registered. + user: + type: email + description: User microsoft email address. + encrypted_password: + type: string + description: User encrypted password. + required: + - client_id + - client_secret + - tenant_id + - user + - encrypted_password - type: object title: GCP Static Credentials properties: @@ -9980,6 +10105,33 @@ components: - client_id - client_secret - tenant_id + - type: object + title: M365 Static Credentials + properties: + client_id: + type: string + description: The Azure application (client) ID for authentication + in Azure AD. + client_secret: + type: string + description: The client secret associated with the application + (client) ID, providing secure access. + tenant_id: + type: string + description: The Azure tenant ID, representing the directory where + the application is registered. + user: + type: email + description: User microsoft email address. + encrypted_password: + type: string + description: User encrypted password. + required: + - client_id + - client_secret + - tenant_id + - user + - encrypted_password - type: object title: GCP Static Credentials properties: diff --git a/api/src/backend/api/tests/test_utils.py b/api/src/backend/api/tests/test_utils.py index 02a97249c6..0777267484 100644 --- a/api/src/backend/api/tests/test_utils.py +++ b/api/src/backend/api/tests/test_utils.py @@ -19,6 +19,7 @@ from prowler.providers.aws.aws_provider import AwsProvider from prowler.providers.azure.azure_provider import AzureProvider from prowler.providers.gcp.gcp_provider import GcpProvider from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider +from prowler.providers.m365.m365_provider import M365Provider class TestMergeDicts: @@ -104,6 +105,7 @@ class TestReturnProwlerProvider: (Provider.ProviderChoices.GCP.value, GcpProvider), (Provider.ProviderChoices.AZURE.value, AzureProvider), (Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider), + (Provider.ProviderChoices.M365.value, M365Provider), ], ) def test_return_prowler_provider(self, provider_type, expected_provider): @@ -176,6 +178,10 @@ class TestGetProwlerProviderKwargs: Provider.ProviderChoices.KUBERNETES.value, {"context": "provider_uid"}, ), + ( + Provider.ProviderChoices.M365.value, + {}, + ), ], ) def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs): diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py index 31d009dba5..63581c266e 100644 --- a/api/src/backend/api/utils.py +++ b/api/src/backend/api/utils.py @@ -11,6 +11,7 @@ from prowler.providers.azure.azure_provider import AzureProvider from prowler.providers.common.models import Connection from prowler.providers.gcp.gcp_provider import GcpProvider from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider +from prowler.providers.m365.m365_provider import M365Provider class CustomOAuth2Client(OAuth2Client): @@ -51,14 +52,14 @@ def merge_dicts(default_dict: dict, replacement_dict: dict) -> dict: def return_prowler_provider( provider: Provider, -) -> [AwsProvider | AzureProvider | GcpProvider | KubernetesProvider]: +) -> [AwsProvider | AzureProvider | GcpProvider | KubernetesProvider | M365Provider]: """Return the Prowler provider class based on the given provider type. Args: provider (Provider): The provider object containing the provider type and associated secrets. Returns: - AwsProvider | AzureProvider | GcpProvider | KubernetesProvider: The corresponding provider class. + AwsProvider | AzureProvider | GcpProvider | KubernetesProvider | M365Provider: The corresponding provider class. Raises: ValueError: If the provider type specified in `provider.provider` is not supported. @@ -72,6 +73,8 @@ def return_prowler_provider( prowler_provider = AzureProvider case Provider.ProviderChoices.KUBERNETES.value: prowler_provider = KubernetesProvider + case Provider.ProviderChoices.M365.value: + prowler_provider = M365Provider case _: raise ValueError(f"Provider type {provider.provider} not supported") return prowler_provider @@ -104,15 +107,15 @@ def get_prowler_provider_kwargs(provider: Provider) -> dict: def initialize_prowler_provider( provider: Provider, -) -> AwsProvider | AzureProvider | GcpProvider | KubernetesProvider: +) -> AwsProvider | AzureProvider | GcpProvider | KubernetesProvider | M365Provider: """Initialize a Prowler provider instance based on the given provider type. Args: provider (Provider): The provider object containing the provider type and associated secrets. Returns: - AwsProvider | AzureProvider | GcpProvider | KubernetesProvider: An instance of the corresponding provider class - (`AwsProvider`, `AzureProvider`, `GcpProvider`, or `KubernetesProvider`) initialized with the + AwsProvider | AzureProvider | GcpProvider | KubernetesProvider | M365Provider: An instance of the corresponding provider class + (`AwsProvider`, `AzureProvider`, `GcpProvider`, `KubernetesProvider` or `M365Provider`) initialized with the provider's secrets. """ prowler_provider = return_prowler_provider(provider) @@ -130,10 +133,12 @@ def prowler_provider_connection_test(provider: Provider) -> Connection: Connection: A connection object representing the result of the connection test for the specified provider. """ prowler_provider = return_prowler_provider(provider) + try: prowler_provider_kwargs = provider.secret.secret except Provider.secret.RelatedObjectDoesNotExist as secret_error: return Connection(is_connected=False, error=secret_error) + return prowler_provider.test_connection( **prowler_provider_kwargs, provider_id=provider.uid, raise_on_exception=False ) diff --git a/api/src/backend/api/v1/serializer_utils/providers.py b/api/src/backend/api/v1/serializer_utils/providers.py new file mode 100644 index 0000000000..231e5dc1bb --- /dev/null +++ b/api/src/backend/api/v1/serializer_utils/providers.py @@ -0,0 +1,172 @@ +from drf_spectacular.utils import extend_schema_field +from rest_framework_json_api import serializers + + +@extend_schema_field( + { + "oneOf": [ + { + "type": "object", + "title": "AWS Static Credentials", + "properties": { + "aws_access_key_id": { + "type": "string", + "description": "The AWS access key ID. Required for environments where no IAM role is being " + "assumed and direct AWS access is needed.", + }, + "aws_secret_access_key": { + "type": "string", + "description": "The AWS secret access key. Must accompany 'aws_access_key_id' to authorize " + "access to AWS resources.", + }, + "aws_session_token": { + "type": "string", + "description": "The session token associated with temporary credentials. Only needed for " + "session-based or temporary AWS access.", + }, + }, + "required": ["aws_access_key_id", "aws_secret_access_key"], + }, + { + "type": "object", + "title": "AWS Assume Role", + "properties": { + "role_arn": { + "type": "string", + "description": "The Amazon Resource Name (ARN) of the role to assume. Required for AWS role " + "assumption.", + }, + "external_id": { + "type": "string", + "description": "An identifier to enhance security for role assumption.", + }, + "aws_access_key_id": { + "type": "string", + "description": "The AWS access key ID. Only required if the environment lacks pre-configured " + "AWS credentials.", + }, + "aws_secret_access_key": { + "type": "string", + "description": "The AWS secret access key. Required if 'aws_access_key_id' is provided or if " + "no AWS credentials are pre-configured.", + }, + "aws_session_token": { + "type": "string", + "description": "The session token for temporary credentials, if applicable.", + }, + "session_duration": { + "type": "integer", + "minimum": 900, + "maximum": 43200, + "default": 3600, + "description": "The duration (in seconds) for the role session.", + }, + "role_session_name": { + "type": "string", + "description": "An identifier for the role session, useful for tracking sessions in AWS logs. " + "The regex used to validate this parameter is a string of characters consisting of " + "upper- and lower-case alphanumeric characters with no spaces. You can also include " + "underscores or any of the following characters: =,.@-\n\n" + "Examples:\n" + "- MySession123\n" + "- User_Session-1\n" + "- Test.Session@2", + "pattern": "^[a-zA-Z0-9=,.@_-]+$", + }, + }, + "required": ["role_arn", "external_id"], + }, + { + "type": "object", + "title": "Azure Static Credentials", + "properties": { + "client_id": { + "type": "string", + "description": "The Azure application (client) ID for authentication in Azure AD.", + }, + "client_secret": { + "type": "string", + "description": "The client secret associated with the application (client) ID, providing " + "secure access.", + }, + "tenant_id": { + "type": "string", + "description": "The Azure tenant ID, representing the directory where the application is " + "registered.", + }, + }, + "required": ["client_id", "client_secret", "tenant_id"], + }, + { + "type": "object", + "title": "M365 Static Credentials", + "properties": { + "client_id": { + "type": "string", + "description": "The Azure application (client) ID for authentication in Azure AD.", + }, + "client_secret": { + "type": "string", + "description": "The client secret associated with the application (client) ID, providing " + "secure access.", + }, + "tenant_id": { + "type": "string", + "description": "The Azure tenant ID, representing the directory where the application is " + "registered.", + }, + "user": { + "type": "email", + "description": "User microsoft email address.", + }, + "encrypted_password": { + "type": "string", + "description": "User encrypted password.", + }, + }, + "required": [ + "client_id", + "client_secret", + "tenant_id", + "user", + "encrypted_password", + ], + }, + { + "type": "object", + "title": "GCP Static Credentials", + "properties": { + "client_id": { + "type": "string", + "description": "The client ID from Google Cloud, used to identify the application for GCP " + "access.", + }, + "client_secret": { + "type": "string", + "description": "The client secret associated with the GCP client ID, required for secure " + "access.", + }, + "refresh_token": { + "type": "string", + "description": "A refresh token that allows the application to obtain new access tokens for " + "extended use.", + }, + }, + "required": ["client_id", "client_secret", "refresh_token"], + }, + { + "type": "object", + "title": "Kubernetes Static Credentials", + "properties": { + "kubeconfig_content": { + "type": "string", + "description": "The content of the Kubernetes kubeconfig file, encoded as a string.", + } + }, + "required": ["kubeconfig_content"], + }, + ] + } +) +class ProviderSecretField(serializers.JSONField): + pass diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index ef470da141..c0440f3ef0 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -42,6 +42,7 @@ from api.v1.serializer_utils.integrations import ( IntegrationCredentialField, S3ConfigSerializer, ) +from api.v1.serializer_utils.providers import ProviderSecretField # Tokens @@ -1150,6 +1151,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer): serializer = GCPProviderSecret(data=secret) elif provider_type == Provider.ProviderChoices.KUBERNETES.value: serializer = KubernetesProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.M365.value: + serializer = M365ProviderSecret(data=secret) else: raise serializers.ValidationError( {"provider": f"Provider type not supported {provider_type}"} @@ -1189,6 +1192,17 @@ class AzureProviderSecret(serializers.Serializer): resource_name = "provider-secrets" +class M365ProviderSecret(serializers.Serializer): + client_id = serializers.CharField() + client_secret = serializers.CharField() + tenant_id = serializers.CharField() + user = serializers.EmailField() + encrypted_password = serializers.CharField() + + class Meta: + resource_name = "provider-secrets" + + class GCPProviderSecret(serializers.Serializer): client_id = serializers.CharField() client_secret = serializers.CharField() @@ -1220,141 +1234,6 @@ class AWSRoleAssumptionProviderSecret(serializers.Serializer): resource_name = "provider-secrets" -@extend_schema_field( - { - "oneOf": [ - { - "type": "object", - "title": "AWS Static Credentials", - "properties": { - "aws_access_key_id": { - "type": "string", - "description": "The AWS access key ID. Required for environments where no IAM role is being " - "assumed and direct AWS access is needed.", - }, - "aws_secret_access_key": { - "type": "string", - "description": "The AWS secret access key. Must accompany 'aws_access_key_id' to authorize " - "access to AWS resources.", - }, - "aws_session_token": { - "type": "string", - "description": "The session token associated with temporary credentials. Only needed for " - "session-based or temporary AWS access.", - }, - }, - "required": ["aws_access_key_id", "aws_secret_access_key"], - }, - { - "type": "object", - "title": "AWS Assume Role", - "properties": { - "role_arn": { - "type": "string", - "description": "The Amazon Resource Name (ARN) of the role to assume. Required for AWS role " - "assumption.", - }, - "external_id": { - "type": "string", - "description": "An identifier to enhance security for role assumption.", - }, - "aws_access_key_id": { - "type": "string", - "description": "The AWS access key ID. Only required if the environment lacks pre-configured " - "AWS credentials.", - }, - "aws_secret_access_key": { - "type": "string", - "description": "The AWS secret access key. Required if 'aws_access_key_id' is provided or if " - "no AWS credentials are pre-configured.", - }, - "aws_session_token": { - "type": "string", - "description": "The session token for temporary credentials, if applicable.", - }, - "session_duration": { - "type": "integer", - "minimum": 900, - "maximum": 43200, - "default": 3600, - "description": "The duration (in seconds) for the role session.", - }, - "role_session_name": { - "type": "string", - "description": "An identifier for the role session, useful for tracking sessions in AWS logs. " - "The regex used to validate this parameter is a string of characters consisting of " - "upper- and lower-case alphanumeric characters with no spaces. You can also include " - "underscores or any of the following characters: =,.@-\n\n" - "Examples:\n" - "- MySession123\n" - "- User_Session-1\n" - "- Test.Session@2", - "pattern": "^[a-zA-Z0-9=,.@_-]+$", - }, - }, - "required": ["role_arn", "external_id"], - }, - { - "type": "object", - "title": "Azure Static Credentials", - "properties": { - "client_id": { - "type": "string", - "description": "The Azure application (client) ID for authentication in Azure AD.", - }, - "client_secret": { - "type": "string", - "description": "The client secret associated with the application (client) ID, providing " - "secure access.", - }, - "tenant_id": { - "type": "string", - "description": "The Azure tenant ID, representing the directory where the application is " - "registered.", - }, - }, - "required": ["client_id", "client_secret", "tenant_id"], - }, - { - "type": "object", - "title": "GCP Static Credentials", - "properties": { - "client_id": { - "type": "string", - "description": "The client ID from Google Cloud, used to identify the application for GCP " - "access.", - }, - "client_secret": { - "type": "string", - "description": "The client secret associated with the GCP client ID, required for secure " - "access.", - }, - "refresh_token": { - "type": "string", - "description": "A refresh token that allows the application to obtain new access tokens for " - "extended use.", - }, - }, - "required": ["client_id", "client_secret", "refresh_token"], - }, - { - "type": "object", - "title": "Kubernetes Static Credentials", - "properties": { - "kubeconfig_content": { - "type": "string", - "description": "The content of the Kubernetes kubeconfig file, encoded as a string.", - } - }, - "required": ["kubeconfig_content"], - }, - ] - } -) -class ProviderSecretField(serializers.JSONField): - pass - - class ProviderSecretSerializer(RLSSerializer): """ Serializer for the ProviderSecret model. diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index ec2dd281e2..8b0fbe0af0 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -248,7 +248,7 @@ class SchemaView(SpectacularAPIView): def get(self, request, *args, **kwargs): spectacular_settings.TITLE = "Prowler API" - spectacular_settings.VERSION = "1.6.0" + spectacular_settings.VERSION = "1.7.0" spectacular_settings.DESCRIPTION = ( "Prowler API specification.\n\nThis file is auto-generated." ) diff --git a/api/tests/performance/__init__.py b/api/tests/performance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/performance/benchmark.py b/api/tests/performance/benchmark.py new file mode 100644 index 0000000000..1df7f81645 --- /dev/null +++ b/api/tests/performance/benchmark.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +import argparse +import re +import subprocess +import sys +from pathlib import Path + +import matplotlib.pyplot as plt +import pandas as pd + +plt.style.use("ggplot") + + +def run_locust( + locust_file: str, + host: str, + users: int, + hatch_rate: int, + run_time: str, + csv_prefix: Path, +) -> Path: + artifacts_dir = Path("artifacts") + artifacts_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "locust", + "-f", + f"scenarios/{locust_file}", + "--headless", + "-u", + str(users), + "-r", + str(hatch_rate), + "-t", + run_time, + "--host", + host, + "--csv", + str(artifacts_dir / csv_prefix.name), + ] + print(f"Running Locust: {' '.join(cmd)}") + process = subprocess.run(cmd) + if process.returncode: + sys.exit("Locust execution failed") + + stats_file = artifacts_dir / f"{csv_prefix.stem}_stats.csv" + if not stats_file.exists(): + sys.exit(f"Stats CSV not found: {stats_file}") + return stats_file + + +def load_percentiles(csv_path: Path) -> pd.DataFrame: + df = pd.read_csv(csv_path) + mapping = {"50%": "p50", "75%": "p75", "90%": "p90", "95%": "p95"} + available = [col for col in mapping if col in df.columns] + renamed = {col: mapping[col] for col in available} + df = df.rename(columns=renamed).set_index("Name")[renamed.values()] + return df.drop(index=["Aggregated"], errors="ignore") + + +def sanitize_label(label: str) -> str: + text = re.sub(r"[^\w]+", "_", label.strip().lower()) + return text.strip("_") + + +def plot_multi_comparison(metrics: dict[str, pd.DataFrame]) -> None: + common = sorted(set.intersection(*(set(df.index) for df in metrics.values()))) + percentiles = list(next(iter(metrics.values())).columns) + groups = len(metrics) + width = 0.8 / groups + + for endpoint in common: + fig, ax = plt.subplots(figsize=(10, 5), dpi=100) + for idx, (label, df) in enumerate(metrics.items()): + series = df.loc[endpoint] + positions = [ + i + (idx - groups / 2) * width + width / 2 + for i in range(len(percentiles)) + ] + bars = ax.bar(positions, series.values, width, label=label) + for bar in bars: + height = bar.get_height() + ax.annotate( + f"{int(height)}", + xy=(bar.get_x() + bar.get_width() / 2, height), + xytext=(0, 3), + textcoords="offset points", + ha="center", + va="bottom", + fontsize=8, + ) + + ax.set_xticks(range(len(percentiles))) + ax.set_xticklabels(percentiles) + ax.set_ylabel("Latency (ms)") + ax.set_title(endpoint, fontsize=12) + ax.grid(True, axis="y", linestyle="--", alpha=0.7) + + fig.tight_layout() + fig.subplots_adjust(right=0.75) + ax.legend(loc="center left", bbox_to_anchor=(1, 0.5), framealpha=0.9) + + output = Path("artifacts") / f"comparison_{sanitize_label(endpoint)}.png" + plt.savefig(output) + plt.close(fig) + print(f"Saved chart: {output}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Run Locust and compare metrics") + parser.add_argument("--locustfile", required=True, help="Locust file in scenarios/") + parser.add_argument("--host", required=True, help="Target host URL") + parser.add_argument( + "--users", type=int, default=10, help="Number of simulated users" + ) + parser.add_argument("--rate", type=int, default=1, help="Hatch rate per second") + parser.add_argument("--time", default="1m", help="Test duration (e.g. 30s, 1m)") + parser.add_argument( + "--metrics-dir", default="baselines", help="Directory with CSV baselines" + ) + parser.add_argument("--version", default="current", help="Test version") + args = parser.parse_args() + + metrics_dir = Path(args.metrics_dir) + if not metrics_dir.is_dir(): + sys.exit(f"Metrics directory not found: {metrics_dir}") + + metrics_data: dict[str, pd.DataFrame] = {} + for csv_file in sorted(metrics_dir.glob("*.csv")): + metrics_data[csv_file.stem] = load_percentiles(csv_file) + + current_prefix = Path(args.version) + current_csv = run_locust( + locust_file=args.locustfile, + host=args.host, + users=args.users, + hatch_rate=args.rate, + run_time=args.time, + csv_prefix=current_prefix, + ) + metrics_data[args.version] = load_percentiles(current_csv) + + for endpoint in sorted( + set.intersection(*(set(df.index) for df in metrics_data.values())) + ): + parts = [endpoint] + for label, df in metrics_data.items(): + s = df.loc[endpoint] + parts.append(f"{label}: p50 {s.p50}, p75 {s.p75}, p90 {s.p90}, p95 {s.p95}") + print(" | ".join(parts)) + + plot_multi_comparison(metrics_data) + + +if __name__ == "__main__": + main() diff --git a/api/tests/performance/requirements.txt b/api/tests/performance/requirements.txt new file mode 100644 index 0000000000..139690df29 --- /dev/null +++ b/api/tests/performance/requirements.txt @@ -0,0 +1,2 @@ +locust==2.34.1 +matplotlib==3.10.1 diff --git a/api/tests/performance/scenarios/__init__.py b/api/tests/performance/scenarios/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/performance/scenarios/findings.py b/api/tests/performance/scenarios/findings.py new file mode 100644 index 0000000000..aef484a7ea --- /dev/null +++ b/api/tests/performance/scenarios/findings.py @@ -0,0 +1,202 @@ +from locust import events, task +from utils.config import ( + FINDINGS_UI_SORT_VALUES, + L_PROVIDER_NAME, + M_PROVIDER_NAME, + S_PROVIDER_NAME, + TARGET_INSERTED_AT, +) +from utils.helpers import ( + APIUserBase, + get_api_token, + get_auth_headers, + get_next_resource_filter, + get_resource_filters_pairs, + get_scan_id_from_provider_name, + get_sort_value, +) + +GLOBAL = { + "token": None, + "scan_ids": {}, + "resource_filters": None, + "large_resource_filters": None, +} + + +@events.test_start.add_listener +def on_test_start(environment, **kwargs): + GLOBAL["token"] = get_api_token(environment.host) + + GLOBAL["scan_ids"]["small"] = get_scan_id_from_provider_name( + environment.host, GLOBAL["token"], S_PROVIDER_NAME + ) + GLOBAL["scan_ids"]["medium"] = get_scan_id_from_provider_name( + environment.host, GLOBAL["token"], M_PROVIDER_NAME + ) + GLOBAL["scan_ids"]["large"] = get_scan_id_from_provider_name( + environment.host, GLOBAL["token"], L_PROVIDER_NAME + ) + + GLOBAL["resource_filters"] = get_resource_filters_pairs( + environment.host, GLOBAL["token"] + ) + GLOBAL["large_resource_filters"] = get_resource_filters_pairs( + environment.host, GLOBAL["token"], GLOBAL["scan_ids"]["large"] + ) + + +class APIUser(APIUserBase): + def on_start(self): + self.token = GLOBAL["token"] + self.s_scan_id = GLOBAL["scan_ids"]["small"] + self.m_scan_id = GLOBAL["scan_ids"]["medium"] + self.l_scan_id = GLOBAL["scan_ids"]["large"] + self.available_resource_filters = GLOBAL["resource_filters"] + self.available_resource_filters_large_scan = GLOBAL["large_resource_filters"] + + @task + def findings_default(self): + name = "/findings" + page_number = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page_number}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[inserted_at]={TARGET_INSERTED_AT}" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task(3) + def findings_default_include(self): + name = "/findings?include" + page = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[inserted_at]={TARGET_INSERTED_AT}" + f"&include=scan.provider,resources" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task(3) + def findings_metadata(self): + endpoint = f"/findings/metadata?" f"filter[inserted_at]={TARGET_INSERTED_AT}" + self.client.get( + endpoint, headers=get_auth_headers(self.token), name="/findings/metadata" + ) + + @task + def findings_scan_small(self): + name = "/findings?filter[scan_id] - 50k" + page_number = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page_number}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[scan]={self.s_scan_id}" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task + def findings_metadata_scan_small(self): + endpoint = f"/findings/metadata?" f"&filter[scan]={self.s_scan_id}" + self.client.get( + endpoint, + headers=get_auth_headers(self.token), + name="/findings/metadata?filter[scan_id] - 50k", + ) + + @task(2) + def findings_scan_medium(self): + name = "/findings?filter[scan_id] - 250k" + page_number = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page_number}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[scan]={self.m_scan_id}" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task + def findings_metadata_scan_medium(self): + endpoint = f"/findings/metadata?" f"&filter[scan]={self.m_scan_id}" + self.client.get( + endpoint, + headers=get_auth_headers(self.token), + name="/findings/metadata?filter[scan_id] - 250k", + ) + + @task + def findings_scan_large(self): + name = "/findings?filter[scan_id] - 500k" + page_number = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page_number}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[scan]={self.l_scan_id}" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task + def findings_scan_large_include(self): + name = "/findings?filter[scan_id]&include - 500k" + page_number = self._next_page(name) + endpoint = ( + f"/findings?page[number]={page_number}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[scan]={self.l_scan_id}" + f"&include=scan.provider,resources" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task + def findings_metadata_scan_large(self): + endpoint = f"/findings/metadata?" f"&filter[scan]={self.l_scan_id}" + self.client.get( + endpoint, + headers=get_auth_headers(self.token), + name="/findings/metadata?filter[scan_id] - 500k", + ) + + @task(2) + def findings_resource_filter(self): + name = "/findings?filter[resource_filter]&include" + filter_name, filter_value = get_next_resource_filter( + self.available_resource_filters + ) + + endpoint = ( + f"/findings?filter[{filter_name}]={filter_value}" + f"&filter[inserted_at]={TARGET_INSERTED_AT}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&include=scan.provider,resources" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task(3) + def findings_metadata_resource_filter(self): + name = "/findings/metadata?filter[resource_filter]" + filter_name, filter_value = get_next_resource_filter( + self.available_resource_filters + ) + + endpoint = ( + f"/findings?filter[{filter_name}]={filter_value}" + f"&filter[inserted_at]={TARGET_INSERTED_AT}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) + + @task + def findings_resource_filter_large_scan_include(self): + name = "/findings?filter[resource_filter][scan]&include - 500k" + filter_name, filter_value = get_next_resource_filter( + self.available_resource_filters + ) + + endpoint = ( + f"/findings?filter[{filter_name}]={filter_value}" + f"&{get_sort_value(FINDINGS_UI_SORT_VALUES)}" + f"&filter[scan]={self.l_scan_id}" + f"&include=scan.provider,resources" + ) + self.client.get(endpoint, headers=get_auth_headers(self.token), name=name) diff --git a/api/tests/performance/utils/__init__.py b/api/tests/performance/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/performance/utils/config.py b/api/tests/performance/utils/config.py new file mode 100644 index 0000000000..febc5e0a24 --- /dev/null +++ b/api/tests/performance/utils/config.py @@ -0,0 +1,19 @@ +import os + +USER_EMAIL = os.environ.get("USER_EMAIL") +USER_PASSWORD = os.environ.get("USER_PASSWORD") + +BASE_HEADERS = {"Content-Type": "application/vnd.api+json"} + +FINDINGS_UI_SORT_VALUES = ["severity", "status", "-inserted_at"] +TARGET_INSERTED_AT = os.environ.get("TARGET_INSERTED_AT", "2025-04-22") + +FINDINGS_RESOURCE_METADATA = { + "regions": "region", + "resource_types": "resource_type", + "services": "service", +} + +S_PROVIDER_NAME = "provider-50k" +M_PROVIDER_NAME = "provider-250k" +L_PROVIDER_NAME = "provider-500k" diff --git a/api/tests/performance/utils/helpers.py b/api/tests/performance/utils/helpers.py new file mode 100644 index 0000000000..999a2d55c8 --- /dev/null +++ b/api/tests/performance/utils/helpers.py @@ -0,0 +1,168 @@ +import random +from collections import defaultdict +from threading import Lock + +import requests +from locust import HttpUser, between +from utils.config import ( + BASE_HEADERS, + FINDINGS_RESOURCE_METADATA, + TARGET_INSERTED_AT, + USER_EMAIL, + USER_PASSWORD, +) + +_global_page_counters = defaultdict(int) +_page_lock = Lock() + + +class APIUserBase(HttpUser): + """ + Base class for API user simulation in Locust performance tests. + + Attributes: + abstract (bool): Indicates this is an abstract user class. + wait_time: Time between task executions, randomized between 1 and 5 seconds. + """ + + abstract = True + wait_time = between(1, 5) + + def _next_page(self, endpoint_name: str) -> int: + """ + Returns the next page number for a given endpoint. Thread-safe. + + Args: + endpoint_name (str): Name of the API endpoint being paginated. + + Returns: + int: The next page number for the given endpoint. + """ + with _page_lock: + _global_page_counters[endpoint_name] += 1 + return _global_page_counters[endpoint_name] + + +def get_next_resource_filter(available_values: dict) -> tuple: + """ + Randomly selects a filter type and value from available options. + + Args: + available_values (dict): Dictionary with filter types as keys and list of possible values. + + Returns: + tuple: A (filter_type, filter_value) pair randomly selected. + """ + filter_type = random.choice(list(available_values.keys())) + filter_value = random.choice(available_values[filter_type]) + return filter_type, filter_value + + +def get_auth_headers(token: str) -> dict: + """ + Returns the headers for the API requests. + + Args: + token (str): The token to be included in the headers. + + Returns: + dict: The headers for the API requests. + """ + return { + "Authorization": f"Bearer {token}", + **BASE_HEADERS, + } + + +def get_api_token(host: str) -> str: + """ + Authenticates with the API and retrieves a bearer token. + + Args: + host (str): The host URL of the API. + + Returns: + str: The access token for authenticated requests. + + Raises: + AssertionError: If the request fails or does not return a 200 status code. + """ + login_payload = { + "data": { + "type": "tokens", + "attributes": {"email": USER_EMAIL, "password": USER_PASSWORD}, + } + } + response = requests.post(f"{host}/tokens", json=login_payload, headers=BASE_HEADERS) + assert response.status_code == 200, f"Failed to get token: {response.text}" + return response.json()["data"]["attributes"]["access"] + + +def get_scan_id_from_provider_name(host: str, token: str, provider_name: str) -> str: + """ + Retrieves the scan ID associated with a specific provider name. + + Args: + host (str): The host URL of the API. + token (str): Bearer token for authentication. + provider_name (str): Name of the provider to filter scans by. + + Returns: + str: The ID of the scan. + + Raises: + AssertionError: If the request fails or does not return a 200 status code. + """ + response = requests.get( + f"{host}/scans?fields[scans]=id&filter[provider_alias]={provider_name}", + headers=get_auth_headers(token), + ) + assert response.status_code == 200, f"Failed to get scan: {response.text}" + return response.json()["data"][0]["id"] + + +def get_resource_filters_pairs(host: str, token: str, scan_id: str = "") -> dict: + """ + Retrieves and maps resource metadata filter values from the findings endpoint. + + Args: + host (str): The host URL of the API. + token (str): Bearer token for authentication. + scan_id (str, optional): Optional scan ID to filter metadata. Defaults to using inserted_at timestamp. + + Returns: + dict: A dictionary of resource filter metadata. + + Raises: + AssertionError: If the request fails or does not return a 200 status code. + """ + metadata_filters = ( + f"filter[scan]={scan_id}" + if scan_id + else f"filter[inserted_at]={TARGET_INSERTED_AT}" + ) + response = requests.get( + f"{host}/findings/metadata?{metadata_filters}", headers=get_auth_headers(token) + ) + assert ( + response.status_code == 200 + ), f"Failed to get resource filters values: {response.text}" + attributes = response.json()["data"]["attributes"] + return { + FINDINGS_RESOURCE_METADATA[key]: values + for key, values in attributes.items() + if key in FINDINGS_RESOURCE_METADATA.keys() + } + + +def get_sort_value(sort_values: list) -> str: + """ + Constructs a sort query string from a list of sort keys. + + Args: + sort_values (list): The list of sort values to include in the query. + + Returns: + str: A formatted sort query string (e.g., "sort=created_at,-severity"). + """ + return f"sort={','.join(sort_values)}" diff --git a/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh b/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh index 51fef78f77..c101da8613 100755 --- a/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh +++ b/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh @@ -1,6 +1,9 @@ #!/bin/bash # Run Prowler against All AWS Accounts in an AWS Organization +# Activate Poetry Environment +eval "$(poetry env activate)" + # Show Prowler Version prowler -v diff --git a/dashboard/common_methods.py b/dashboard/common_methods.py index cff28b62d9..338b6e6473 100644 --- a/dashboard/common_methods.py +++ b/dashboard/common_methods.py @@ -2228,13 +2228,356 @@ def get_section_containers_ens(data, section_1, section_2, section_3, section_4) return html.Div(section_containers, className="compliance-data-layout") +def get_section_containers_3_levels(data, section_1, section_2, section_3): + data["STATUS"] = data["STATUS"].apply(map_status_to_icon) + findings_counts_marco = ( + data.groupby([section_1, "STATUS"]).size().unstack(fill_value=0) + ) + section_containers = [] + data[section_1] = data[section_1].astype(str) + data[section_2] = data[section_2].astype(str) + data[section_3] = data[section_3].astype(str) + + data.sort_values( + by=section_3, + key=lambda x: x.map(extract_numeric_values), + ascending=True, + inplace=True, + ) + + for marco in data[section_1].unique(): + success_marco = findings_counts_marco.loc[marco].get(pass_emoji, 0) + failed_marco = findings_counts_marco.loc[marco].get(fail_emoji, 0) + + fig_name = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_marco], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_marco], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_name.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_marco + failed_marco, + y=0, + xref="x", + yref="y", + text=str(success_marco), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_marco), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_name.add_annotation( + x=failed_marco, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div = html.Div( + dcc.Graph( + figure=fig_name, config={"staticPlot": True}, className="info-bar" + ), + className="graph-section", + ) + direct_internal_items = [] + + for categoria in data[data[section_1] == marco][section_2].unique(): + specific_data = data[ + (data[section_1] == marco) & (data[section_2] == categoria) + ] + findings_counts_categoria = ( + specific_data.groupby([section_2, "STATUS"]) + .size() + .unstack(fill_value=0) + ) + success_categoria = findings_counts_categoria.loc[categoria].get( + pass_emoji, 0 + ) + failed_categoria = findings_counts_categoria.loc[categoria].get( + fail_emoji, 0 + ) + + fig_section = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_categoria], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_categoria], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_section.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_categoria + failed_categoria, + y=0, + xref="x", + yref="y", + text=str(success_categoria), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_categoria), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_section.add_annotation( + x=failed_categoria, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div_section = html.Div( + dcc.Graph( + figure=fig_section, + config={"staticPlot": True}, + className="info-bar-child", + ), + className="graph-section-req", + ) + direct_internal_items_idgrupocontrol = [] + + for idgrupocontrol in specific_data[section_3].unique(): + specific_data2 = specific_data[ + specific_data[section_3] == idgrupocontrol + ] + findings_counts_idgrupocontrol = ( + specific_data2.groupby([section_3, "STATUS"]) + .size() + .unstack(fill_value=0) + ) + success_idgrupocontrol = findings_counts_idgrupocontrol.loc[ + idgrupocontrol + ].get(pass_emoji, 0) + failed_idgrupocontrol = findings_counts_idgrupocontrol.loc[ + idgrupocontrol + ].get(fail_emoji, 0) + + fig_idgrupocontrol = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_idgrupocontrol], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_idgrupocontrol], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_idgrupocontrol.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_idgrupocontrol + failed_idgrupocontrol, + y=0, + xref="x", + yref="y", + text=str(success_idgrupocontrol), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_idgrupocontrol), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_idgrupocontrol.add_annotation( + x=failed_idgrupocontrol, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div_idgrupocontrol = html.Div( + dcc.Graph( + figure=fig_idgrupocontrol, + config={"staticPlot": True}, + className="info-bar-child", + ), + className="graph-section-req", + ) + + data_table = dash_table.DataTable( + data=specific_data2.to_dict("records"), + columns=[ + {"name": i, "id": i} + for i in [ + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ], + style_table={"overflowX": "auto"}, + style_as_list_view=True, + style_cell={"textAlign": "left", "padding": "5px"}, + ) + + internal_accordion_item_2 = dbc.AccordionItem( + title=idgrupocontrol, + children=[ + graph_div_idgrupocontrol, + html.Div([data_table], className="inner-accordion-content"), + ], + ) + direct_internal_items_idgrupocontrol.append( + html.Div( + [ + graph_div_idgrupocontrol, + dbc.Accordion( + [internal_accordion_item_2], + start_collapsed=True, + flush=True, + ), + ], + className="accordion-inner--child", + ) + ) + + internal_accordion_item = dbc.AccordionItem( + title=categoria, + children=direct_internal_items_idgrupocontrol, + ) + internal_section_container = html.Div( + [ + graph_div_section, + dbc.Accordion( + [internal_accordion_item], start_collapsed=True, flush=True + ), + ], + className="accordion-inner--child", + ) + direct_internal_items.append(internal_section_container) + + accordion_item = dbc.AccordionItem(title=marco, children=direct_internal_items) + section_container = html.Div( + [ + graph_div, + dbc.Accordion([accordion_item], start_collapsed=True, flush=True), + ], + className="accordion-inner", + ) + section_containers.append(section_container) + + return html.Div(section_containers, className="compliance-data-layout") + + # This function extracts and compares up to two numeric values, ensuring correct sorting for version-like strings. def extract_numeric_values(value): numbers = re.findall(r"\d+", str(value)) - if len(numbers) >= 2: + if len(numbers) == 3: + return int(numbers[0]), int(numbers[1]), int(numbers[2]) + elif len(numbers) == 2: return int(numbers[0]), int(numbers[1]) elif len(numbers) == 1: - return int(numbers[0]), 0 + return int(numbers[0]) return 0, 0 diff --git a/dashboard/compliance/aws_well_architected_framework_reliability_pillar_aws.py b/dashboard/compliance/aws_well_architected_framework_reliability_pillar_aws.py index 02fae31d81..d2c3272352 100644 --- a/dashboard/compliance/aws_well_architected_framework_reliability_pillar_aws.py +++ b/dashboard/compliance/aws_well_architected_framework_reliability_pillar_aws.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_format2 +from dashboard.common_methods import get_section_containers_3_levels warnings.filterwarnings("ignore") @@ -10,6 +10,7 @@ def get_table(data): [ "REQUIREMENTS_ATTRIBUTES_NAME", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -17,6 +18,10 @@ def get_table(data): "RESOURCEID", ] ] - return get_section_containers_format2( - aux, "REQUIREMENTS_ATTRIBUTES_NAME", "REQUIREMENTS_ATTRIBUTES_SECTION" + + return get_section_containers_3_levels( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ATTRIBUTES_NAME", ) diff --git a/dashboard/compliance/aws_well_architected_framework_security_pillar_aws.py b/dashboard/compliance/aws_well_architected_framework_security_pillar_aws.py index cfa7ecab40..d2c3272352 100644 --- a/dashboard/compliance/aws_well_architected_framework_security_pillar_aws.py +++ b/dashboard/compliance/aws_well_architected_framework_security_pillar_aws.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_format2 +from dashboard.common_methods import get_section_containers_3_levels warnings.filterwarnings("ignore") @@ -10,6 +10,7 @@ def get_table(data): [ "REQUIREMENTS_ATTRIBUTES_NAME", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -18,6 +19,9 @@ def get_table(data): ] ] - return get_section_containers_format2( - aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ATTRIBUTES_NAME" + return get_section_containers_3_levels( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ATTRIBUTES_NAME", ) diff --git a/dashboard/compliance/kisa_isms_p_2023_aws.py b/dashboard/compliance/kisa_isms_p_2023_aws.py index 1d079d46af..66643dab04 100644 --- a/dashboard/compliance/kisa_isms_p_2023_aws.py +++ b/dashboard/compliance/kisa_isms_p_2023_aws.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_kisa_ismsp +from dashboard.common_methods import get_section_containers_3_levels warnings.filterwarnings("ignore") @@ -8,7 +8,7 @@ warnings.filterwarnings("ignore") def get_table(data): aux = data[ [ - "REQUIREMENTS_ID", + "REQUIREMENTS_ATTRIBUTES_DOMAIN", "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION", # "REQUIREMENTS_DESCRIPTION", @@ -20,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_kisa_ismsp( - aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_3_levels( + aux, + "REQUIREMENTS_ATTRIBUTES_DOMAIN", + "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", + "REQUIREMENTS_ATTRIBUTES_SECTION", ) diff --git a/dashboard/compliance/kisa_isms_p_2023_korean_aws.py b/dashboard/compliance/kisa_isms_p_2023_korean_aws.py index 1d079d46af..66643dab04 100644 --- a/dashboard/compliance/kisa_isms_p_2023_korean_aws.py +++ b/dashboard/compliance/kisa_isms_p_2023_korean_aws.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_kisa_ismsp +from dashboard.common_methods import get_section_containers_3_levels warnings.filterwarnings("ignore") @@ -8,7 +8,7 @@ warnings.filterwarnings("ignore") def get_table(data): aux = data[ [ - "REQUIREMENTS_ID", + "REQUIREMENTS_ATTRIBUTES_DOMAIN", "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION", # "REQUIREMENTS_DESCRIPTION", @@ -20,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_kisa_ismsp( - aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_3_levels( + aux, + "REQUIREMENTS_ATTRIBUTES_DOMAIN", + "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", + "REQUIREMENTS_ATTRIBUTES_SECTION", ) diff --git a/dashboard/compliance/prowler_threatscore_aws.py b/dashboard/compliance/prowler_threatscore_aws.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/prowler_threatscore_aws.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/dashboard/compliance/prowler_threatscore_azure.py b/dashboard/compliance/prowler_threatscore_azure.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/prowler_threatscore_azure.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/dashboard/compliance/prowler_threatscore_gcp.py b/dashboard/compliance/prowler_threatscore_gcp.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/prowler_threatscore_gcp.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/dashboard/pages/compliance.py b/dashboard/pages/compliance.py index 422e801503..d9c5c9f4b0 100644 --- a/dashboard/pages/compliance.py +++ b/dashboard/pages/compliance.py @@ -398,6 +398,10 @@ def display_data( f"dashboard.compliance.{current}" ) data.drop_duplicates(keep="first", inplace=True) + + if "threatscore" in analytics_input: + data = get_threatscore_mean_by_pillar(data) + table = compliance_module.get_table(data) except ModuleNotFoundError: table = html.Div( @@ -430,6 +434,9 @@ def display_data( if "pci" in analytics_input: pie_2 = get_bar_graph(df, "REQUIREMENTS_ID") current_filter = "req_id" + elif "threatscore" in analytics_input: + pie_2 = get_table_prowler_threatscore(df) + current_filter = "threatscore" elif ( "REQUIREMENTS_ATTRIBUTES_SECTION" in df.columns and not df["REQUIREMENTS_ATTRIBUTES_SECTION"].isnull().values.any() @@ -488,6 +495,13 @@ def display_data( pie_2, f"Top 5 failed {current_filter} by requirements" ) + if "threatscore" in analytics_input: + security_level_graph = get_graph( + pie_2, + "Pillar Score by requirements (1 = Lowest Risk, 5 = Highest Risk)", + margin_top=0, + ) + return ( table_output, overall_status_result_graph, @@ -501,7 +515,7 @@ def display_data( ) -def get_graph(pie, title): +def get_graph(pie, title, margin_top=7): return [ html.Span( title, @@ -514,7 +528,7 @@ def get_graph(pie, title): "display": "flex", "justify-content": "center", "align-items": "center", - "margin-top": "7%", + "margin-top": f"{margin_top}%", }, ), ] @@ -618,3 +632,87 @@ def get_table(current_compliance, table): className="relative flex flex-col bg-white shadow-provider rounded-xl px-4 py-3 flex-wrap w-full", ), ] + + +def get_threatscore_mean_by_pillar(df): + modified_df = df[df["STATUS"] == "FAIL"] + + modified_df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"] = pd.to_numeric( + modified_df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" + ) + + pillar_means = ( + modified_df.groupby("REQUIREMENTS_ATTRIBUTES_SECTION")[ + "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK" + ] + .mean() + .round(2) + ) + + output = [] + for pillar, mean in pillar_means.items(): + output.append(f"{pillar} - [{mean}]") + + for value in output: + if value.split(" - ")[0] in df["REQUIREMENTS_ATTRIBUTES_SECTION"].values: + df.loc[ + df["REQUIREMENTS_ATTRIBUTES_SECTION"] == value.split(" - ")[0], + "REQUIREMENTS_ATTRIBUTES_SECTION", + ] = value + return df + + +def get_table_prowler_threatscore(df): + df = df[df["STATUS"] == "FAIL"] + + # Delete " - " from the column REQUIREMENTS_ATTRIBUTES_SECTION + df["REQUIREMENTS_ATTRIBUTES_SECTION"] = ( + df["REQUIREMENTS_ATTRIBUTES_SECTION"].str.split(" - ").str[0] + ) + + df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"] = pd.to_numeric( + df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" + ) + + score_df = ( + df.groupby("REQUIREMENTS_ATTRIBUTES_SECTION")[ + "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK" + ] + .mean() + .reset_index() + .rename( + columns={ + "REQUIREMENTS_ATTRIBUTES_SECTION": "Pillar", + "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK": "Score", + } + ) + ) + + fig = px.bar( + score_df, + x="Pillar", + y="Score", + color="Score", + color_continuous_scale=[ + "#45cc6e", + "#f4d44d", + "#e77676", + ], # verde → amarillo → rojo + hover_data={"Score": True, "Pillar": True}, + labels={"Score": "Average Risk Score", "Pillar": "Section"}, + height=400, + ) + + fig.update_layout( + xaxis_title="Pillar", + yaxis_title="Level of Risk", + margin=dict(l=20, r=20, t=30, b=20), + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + coloraxis_colorbar=dict(title="Risk"), + ) + + return dcc.Graph( + figure=fig, + style={"height": "25rem", "width": "40rem"}, + ) diff --git a/poetry.lock b/poetry.lock index 3f86152cdf..96465562a4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,100 +14,105 @@ files = [ [[package]] name = "aiohappyeyeballs" -version = "2.4.4" +version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, - {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] [[package]] name = "aiohttp" -version = "3.11.11" +version = "3.11.18" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"}, - {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"}, - {file = "aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c"}, - {file = "aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745"}, - {file = "aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773"}, - {file = "aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62"}, - {file = "aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e"}, - {file = "aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600"}, - {file = "aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5"}, - {file = "aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d"}, - {file = "aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3e23419d832d969f659c208557de4a123e30a10d26e1e14b73431d3c13444c2e"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21fef42317cf02e05d3b09c028712e1d73a9606f02467fd803f7c1f39cc59add"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f21bb8d0235fc10c09ce1d11ffbd40fc50d3f08a89e4cf3a0c503dc2562247a"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1642eceeaa5ab6c9b6dfeaaa626ae314d808188ab23ae196a34c9d97efb68350"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2170816e34e10f2fd120f603e951630f8a112e1be3b60963a1f159f5699059a6"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8be8508d110d93061197fd2d6a74f7401f73b6d12f8822bbcd6d74f2b55d71b1"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eed954b161e6b9b65f6be446ed448ed3921763cc432053ceb606f89d793927e"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6c9af134da4bc9b3bd3e6a70072509f295d10ee60c697826225b60b9959acdd"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44167fc6a763d534a6908bdb2592269b4bf30a03239bcb1654781adf5e49caf1"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:479b8c6ebd12aedfe64563b85920525d05d394b85f166b7873c8bde6da612f9c"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:10b4ff0ad793d98605958089fabfa350e8e62bd5d40aa65cdc69d6785859f94e"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b540bd67cfb54e6f0865ceccd9979687210d7ed1a1cc8c01f8e67e2f1e883d28"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dac54e8ce2ed83b1f6b1a54005c87dfed139cf3f777fdc8afc76e7841101226"}, - {file = "aiohttp-3.11.11-cp39-cp39-win32.whl", hash = "sha256:568c1236b2fde93b7720f95a890741854c1200fba4a3471ff48b2934d2d93fd3"}, - {file = "aiohttp-3.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:943a8b052e54dfd6439fd7989f67fc6a7f2138d0a2cf0a7de5f18aa4fe7eb3b1"}, - {file = "aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e"}, + {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"}, ] [package.dependencies] @@ -168,14 +173,14 @@ files = [ [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -185,20 +190,20 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] name = "astroid" -version = "3.3.8" +version = "3.3.9" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, - {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, + {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, + {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, ] [package.dependencies] @@ -219,34 +224,34 @@ files = [ [[package]] name = "attrs" -version = "24.3.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, - {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "authlib" -version = "1.4.0" +version = "1.5.2" 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.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, - {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, + {file = "authlib-1.5.2-py2.py3-none-any.whl", hash = "sha256:8804dd4402ac5e4a0435ac49e0b6e19e395357cfa632a3f624dcb4f6df13b4b1"}, + {file = "authlib-1.5.2.tar.gz", hash = "sha256:fe85ec7e50c5f86f1e2603518bb3b4f632985eb4a355e52256530790e326c512"}, ] [package.dependencies] @@ -254,21 +259,21 @@ cryptography = "*" [[package]] name = "aws-sam-translator" -version = "1.94.0" +version = "1.97.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.94.0-py3-none-any.whl", hash = "sha256:100e33eeffcfa81f7c45cadeb0ee29596ce829f6b4d2745140f04fa19a41f539"}, - {file = "aws_sam_translator-1.94.0.tar.gz", hash = "sha256:8ec258d9f7ece72ef91c81f4edb45a2db064c16844b6afac90c575893beaa391"}, + {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"}, ] [package.dependencies] boto3 = ">=1.19.5,<2.dev0" jsonschema = ">=3.2,<5" pydantic = ">=1.8,<1.10.15 || >1.10.15,<1.10.17 || >1.10.17,<3" -typing-extensions = ">=4.4" +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)"] @@ -315,14 +320,14 @@ files = [ [[package]] name = "azure-core" -version = "1.32.0" +version = "1.33.0" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4"}, - {file = "azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5"}, + {file = "azure_core-1.33.0-py3-none-any.whl", hash = "sha256:9b5b6d0223a1d38c37500e6971118c1e0f13f54951e6893968b38910bc9cda8f"}, + {file = "azure_core-1.33.0.tar.gz", hash = "sha256:f367aa07b5e3005fec2c1e184b882b0b039910733907d001c20fb08ebb8c0eb9"}, ] [package.dependencies] @@ -332,6 +337,7 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["aiohttp (>=3.0)"] +tracing = ["opentelemetry-api (>=1.26,<2.0)"] [[package]] name = "azure-identity" @@ -707,18 +713,18 @@ aio = ["azure-core[aio] (>=1.30.0)"] [[package]] name = "babel" -version = "2.16.0" +version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "bandit" @@ -849,26 +855,26 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "cachetools" -version = "5.5.0" +version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, - {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, + {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, + {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "dev", "docs"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -954,18 +960,18 @@ pycparser = "*" [[package]] name = "cfn-lint" -version = "1.22.5" +version = "1.34.1" description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "cfn_lint-1.22.5-py3-none-any.whl", hash = "sha256:18309e59cc03ff18b02676688df7eb1a17f5276da3776f31946fc0d9aa9b8fe7"}, - {file = "cfn_lint-1.22.5.tar.gz", hash = "sha256:8b4f55e283143e99d8d331627637226c291cecfb936606f7aab2d940e71e566d"}, + {file = "cfn_lint-1.34.1-py3-none-any.whl", hash = "sha256:2c21a908fa5d9b0c1caac2081c10261eaae7fce88364e4edc607717892f36d68"}, + {file = "cfn_lint-1.34.1.tar.gz", hash = "sha256:58f4352c370a710b72b389eda0acd6762e62f926e534d2eb1609d5326ded4bed"}, ] [package.dependencies] -aws-sam-translator = ">=1.94.0" +aws-sam-translator = ">=1.97.0" jsonpatch = "*" networkx = ">=2.4,<4" pyyaml = ">5.4" @@ -1349,21 +1355,21 @@ files = [ [[package]] name = "deprecated" -version = "1.2.15" +version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["main"] files = [ - {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, - {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools ; python_version >= \"3.12\"", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] [[package]] name = "detect-secrets" @@ -1387,14 +1393,14 @@ word-list = ["pyahocorasick"] [[package]] name = "dill" -version = "0.3.9" +version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, - {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, ] [package.extras] @@ -1601,104 +1607,116 @@ python-dateutil = ">=2.7" [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {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"}, ] [[package]] @@ -1755,22 +1773,22 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "google-api-core" -version = "2.24.0" +version = "2.24.2" description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_core-2.24.0-py3-none-any.whl", hash = "sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9"}, - {file = "google_api_core-2.24.0.tar.gz", hash = "sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf"}, + {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"}, ] [package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" +google-auth = ">=2.14.1,<3.0.0" +googleapis-common-protos = ">=1.56.2,<2.0.0" +proto-plus = ">=1.22.3,<2.0.0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" +requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] @@ -1799,14 +1817,14 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.37.0" +version = "2.39.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_auth-2.37.0-py2.py3-none-any.whl", hash = "sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0"}, - {file = "google_auth-2.37.0.tar.gz", hash = "sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00"}, + {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"}, ] [package.dependencies] @@ -1815,12 +1833,14 @@ pyasn1-modules = ">=0.2.1" rsa = ">=3.1.4,<5" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +requests = ["requests (>=2.20.0,<3.0.0)"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +urllib3 = ["packaging", "urllib3"] [[package]] name = "google-auth-httplib2" @@ -1840,21 +1860,21 @@ httplib2 = ">=0.19.0" [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, + {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] [package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" [package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "grapheme" @@ -1872,14 +1892,14 @@ test = ["pytest", "sphinx", "sphinx-autobuild", "twine", "wheel"] [[package]] name = "graphql-core" -version = "3.2.5" +version = "3.2.6" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = false python-versions = "<4,>=3.6" groups = ["dev"] files = [ - {file = "graphql_core-3.2.5-py3-none-any.whl", hash = "sha256:2f150d5096448aa4f8ab26268567bbfeef823769893b39c1a2e1409590939c8a"}, - {file = "graphql_core-3.2.5.tar.gz", hash = "sha256:e671b90ed653c808715645e3998b7ab67d382d55467b7e2978549111bbabf8d5"}, + {file = "graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f"}, + {file = "graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab"}, ] [package.dependencies] @@ -1899,42 +1919,42 @@ files = [ [[package]] name = "h2" -version = "4.1.0" -description = "HTTP/2 State-Machine based protocol implementation" +version = "4.2.0" +description = "Pure-Python HTTP/2 protocol implementation" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, - {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, + {file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"}, + {file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"}, ] [package.dependencies] -hpack = ">=4.0,<5" -hyperframe = ">=6.0,<7" +hpack = ">=4.1,<5" +hyperframe = ">=6.1,<7" [[package]] name = "hpack" -version = "4.0.0" -description = "Pure-Python HPACK header compression" +version = "4.1.0" +description = "Pure-Python HPACK header encoding" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, - {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, + {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, + {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.8" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, + {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, ] [package.dependencies] @@ -1990,14 +2010,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "hyperframe" -version = "6.0.1" -description = "HTTP/2 framing layer for Python" +version = "6.1.0" +description = "Pure-Python HTTP/2 framing" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, - {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, + {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, + {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, ] [[package]] @@ -2017,14 +2037,14 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "docs"] files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] markers = {dev = "python_version < \"3.10\"", docs = "python_version < \"3.10\""} @@ -2037,19 +2057,19 @@ 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)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -2066,18 +2086,19 @@ files = [ [[package]] name = "isort" -version = "5.13.2" +version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" groups = ["dev"] files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, ] [package.extras] -colors = ["colorama (>=0.4.6)"] +colors = ["colorama"] +plugins = ["setuptools"] [[package]] name = "itsdangerous" @@ -2123,14 +2144,14 @@ files = [ [[package]] name = "joserfc" -version = "1.0.2" +version = "1.0.4" 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.2-py3-none-any.whl", hash = "sha256:8d3e0253b0b24aeaf049d7bf7a847040dd6462de24cefd0c14abfc8b353f9619"}, - {file = "joserfc-1.0.2.tar.gz", hash = "sha256:f3c4efa35a62fc100097e4bec2584f5fd21b878eee3eb5324bc9b37d2969b2a4"}, + {file = "joserfc-1.0.4-py3-none-any.whl", hash = "sha256:ecf3a5999f89d3a663485ab7c4f633541586d6f44e664ee760197299f39ed51b"}, + {file = "joserfc-1.0.4.tar.gz", hash = "sha256:dc3fc216cfcfc952d4c0d4b06c759a04711af0b667e5973adc47dbb1ba784127"}, ] [package.dependencies] @@ -2206,20 +2227,20 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-path" -version = "0.3.3" +version = "0.3.4" description = "JSONSchema Spec with object-oriented paths" optional = false python-versions = "<4.0.0,>=3.8.0" groups = ["dev"] files = [ - {file = "jsonschema_path-0.3.3-py3-none-any.whl", hash = "sha256:203aff257f8038cd3c67be614fe6b2001043408cb1b4e36576bc4921e09d83c4"}, - {file = "jsonschema_path-0.3.3.tar.gz", hash = "sha256:f02e5481a4288ec062f8e68c808569e427d905bedfecb7f2e4c69ef77957c382"}, + {file = "jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8"}, + {file = "jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001"}, ] [package.dependencies] pathable = ">=0.4.1,<0.5.0" PyYAML = ">=5.1" -referencing = ">=0.28.0,<0.36.0" +referencing = "<0.37.0" requests = ">=2.31.0,<3.0.0" [[package]] @@ -2267,68 +2288,45 @@ adal = ["adal (>=1.0.2)"] [[package]] name = "lazy-object-proxy" -version = "1.10.0" +version = "1.11.0" description = "A fast and thorough lazy object proxy." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, + {file = "lazy_object_proxy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:132bc8a34f2f2d662a851acfd1b93df769992ed1b81e2b1fda7db3e73b0d5a18"}, + {file = "lazy_object_proxy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:01261a3afd8621a1accb5682df2593dc7ec7d21d38f411011a5712dcd418fbed"}, + {file = "lazy_object_proxy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:090935756cc041e191f22f4f9c7fd4fe9a454717067adf5b1bbd2ce3046b556e"}, + {file = "lazy_object_proxy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:76ec715017f06410f57df442c1a8d66e6b5f7035077785b129817f5ae58810a4"}, + {file = "lazy_object_proxy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a9f39098e93a63618a79eef2889ae3cf0605f676cd4797fdfd49fcd7ddc318b"}, + {file = "lazy_object_proxy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee13f67f4fcd044ef27bfccb1c93d39c100046fec1fad6e9a1fcdfd17492aeb3"}, + {file = "lazy_object_proxy-1.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4c84eafd8dd15ea16f7d580758bc5c2ce1f752faec877bb2b1f9f827c329cd"}, + {file = "lazy_object_proxy-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d2503427bda552d3aefcac92f81d9e7ca631e680a2268cbe62cd6a58de6409b7"}, + {file = "lazy_object_proxy-1.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0613116156801ab3fccb9e2b05ed83b08ea08c2517fdc6c6bc0d4697a1a376e3"}, + {file = "lazy_object_proxy-1.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bb03c507d96b65f617a6337dedd604399d35face2cdf01526b913fb50c4cb6e8"}, + {file = "lazy_object_proxy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28c174db37946f94b97a97b579932ff88f07b8d73a46b6b93322b9ac06794a3b"}, + {file = "lazy_object_proxy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d662f0669e27704495ff1f647070eb8816931231c44e583f4d0701b7adf6272f"}, + {file = "lazy_object_proxy-1.11.0-py3-none-any.whl", hash = "sha256:a56a5093d433341ff7da0e89f9b486031ccd222ec8e52ec84d0ec1cdc819674b"}, + {file = "lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c"}, ] [[package]] name = "markdown" -version = "3.7" +version = "3.8" description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, - {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, + {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, + {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] @@ -2429,14 +2427,14 @@ files = [ [[package]] name = "marshmallow" -version = "3.25.1" +version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210"}, - {file = "marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a"}, + {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, + {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, ] [package.dependencies] @@ -2502,20 +2500,20 @@ std-uritemplate = ">=2.0.0" [[package]] name = "microsoft-kiota-authentication-azure" -version = "1.9.1" +version = "1.9.2" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "microsoft_kiota_authentication_azure-1.9.1-py3-none-any.whl", hash = "sha256:3a3030b01e0cbf007736ec6548ac483742e04ad0d20979b49e293a683f839fc6"}, - {file = "microsoft_kiota_authentication_azure-1.9.1.tar.gz", hash = "sha256:8ba31b1ecf78777128daf3191c7ecd28bfc2d10d0fe80260dd5c07a9ccd5d7f5"}, + {file = "microsoft_kiota_authentication_azure-1.9.2-py3-none-any.whl", hash = "sha256:56840f8b15df8aedfd143fb2deb7cc7fae4ac0bafb1a50546b7313a7b3ab4ca0"}, + {file = "microsoft_kiota_authentication_azure-1.9.2.tar.gz", hash = "sha256:171045f522a93d9340fbddc4cabb218f14f1d9d289e82e535b3d9291986c3d5a"}, ] [package.dependencies] aiohttp = ">=3.8.0" azure-core = ">=1.21.1" -microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" +microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" opentelemetry-api = ">=1.27.0" opentelemetry-sdk = ">=1.27.0" @@ -2539,63 +2537,63 @@ opentelemetry-sdk = ">=1.27.0" [[package]] name = "microsoft-kiota-serialization-form" -version = "1.9.1" +version = "1.9.2" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "microsoft_kiota_serialization_form-1.9.1-py3-none-any.whl", hash = "sha256:d86c0dc08b51288f2851a265dde729b200998bca1dd1681bbebcb27cd274ba34"}, - {file = "microsoft_kiota_serialization_form-1.9.1.tar.gz", hash = "sha256:58b81eca5e0ad66bcbd6a4b65ba91e6255d3510f4c4f576fb4f5e83ca9a310c3"}, + {file = "microsoft_kiota_serialization_form-1.9.2-py3-none-any.whl", hash = "sha256:7b997efb2c8750b1d4fbc00878ba2a3e6e1df3fcefc8815226c90fcc9c54f218"}, + {file = "microsoft_kiota_serialization_form-1.9.2.tar.gz", hash = "sha256:badfbe65d8ec3369bd58b01022d13ef590edf14babeef94188efe3f4ec24fe41"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" +microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" [[package]] name = "microsoft-kiota-serialization-json" -version = "1.9.1" +version = "1.9.2" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "microsoft_kiota_serialization_json-1.9.1-py3-none-any.whl", hash = "sha256:ab752bf642a77266713bac3942a4c547dde60b917f674a9ab63261490fecf841"}, - {file = "microsoft_kiota_serialization_json-1.9.1.tar.gz", hash = "sha256:0039458b885875daf246f1e8c0296551695e0ec70f3e4689b00b270ce923c0cc"}, + {file = "microsoft_kiota_serialization_json-1.9.2-py3-none-any.whl", hash = "sha256:8f4ecf485607fff3df5ce8fa9b9c957bc7f4bff1658b183703e180af753098e3"}, + {file = "microsoft_kiota_serialization_json-1.9.2.tar.gz", hash = "sha256:19f7beb69c67b2cb77ca96f77824ee78a693929e20237bb5476ea54f69118bf1"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" +microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" [[package]] name = "microsoft-kiota-serialization-multipart" -version = "1.9.1" +version = "1.9.2" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "microsoft_kiota_serialization_multipart-1.9.1-py3-none-any.whl", hash = "sha256:1ee69d8e3b2c0d24431b85fc1628534f97dcafed45dcb6bb3f0143824ce8a665"}, - {file = "microsoft_kiota_serialization_multipart-1.9.1.tar.gz", hash = "sha256:288790a486aad33aac0a7329f05b8851e9be82f50cc9a8f19fe6f267a4f2b183"}, + {file = "microsoft_kiota_serialization_multipart-1.9.2-py3-none-any.whl", hash = "sha256:641ad374046f1c7adff90d110bdc68d77418adb1e479a716f4ffea3647f0ead6"}, + {file = "microsoft_kiota_serialization_multipart-1.9.2.tar.gz", hash = "sha256:b1851409205668d83f5c7a35a8b6fca974b341985b4a92841e95aaec93b7ca0a"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" +microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" [[package]] name = "microsoft-kiota-serialization-text" -version = "1.9.1" +version = "1.9.2" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "microsoft_kiota_serialization_text-1.9.1-py3-none-any.whl", hash = "sha256:021fbfad887f7525f9e1c8bbe225d0aa1b25befe54357ae0f739f47576d946ff"}, - {file = "microsoft_kiota_serialization_text-1.9.1.tar.gz", hash = "sha256:b4b1f61c2388edd704ab33a1792f92f3507db9648d389197a625e152b52743ab"}, + {file = "microsoft_kiota_serialization_text-1.9.2-py3-none-any.whl", hash = "sha256:6e63129ea29eb9b976f4ed56fc6595d204e29fc309958b639299e9f9f4e5edb4"}, + {file = "microsoft_kiota_serialization_text-1.9.2.tar.gz", hash = "sha256:4289508ebac0cefdc4fa21c545051769a9409913972355ccda9116b647f978f2"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" +microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" [[package]] name = "mkdocs" @@ -2804,18 +2802,18 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.31.1" +version = "1.32.0" 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.31.1-py3-none-any.whl", hash = "sha256:29d9882de247e96db01386496d59f29035e5e841bcac892e6d7bf4390bf6bd17"}, - {file = "msal-1.31.1.tar.gz", hash = "sha256:11b5e6a3f802ffd3a72107203e20c4eac6ef53401961b880af2835b723d80578"}, + {file = "msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7"}, + {file = "msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2"}, ] [package.dependencies] -cryptography = ">=2.5,<46" +cryptography = ">=2.5,<47" PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" @@ -2824,30 +2822,32 @@ broker = ["pymsalruntime (>=0.14,<0.18) ; python_version >= \"3.6\" and platform [[package]] name = "msal-extensions" -version = "1.2.0" +version = "1.3.1" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msal_extensions-1.2.0-py3-none-any.whl", hash = "sha256:cf5ba83a2113fa6dc011a254a72f1c223c88d7dfad74cc30617c4679a417704d"}, - {file = "msal_extensions-1.2.0.tar.gz", hash = "sha256:6f41b320bfd2933d631a215c91ca0dd3e67d84bd1a2f50ce917d5874ec646bef"}, + {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, + {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, ] [package.dependencies] msal = ">=1.29,<2" -portalocker = ">=1.4,<3" + +[package.extras] +portalocker = ["portalocker (>=1.4,<4)"] [[package]] name = "msgraph-core" -version = "1.3.2" +version = "1.3.3" description = "Core component of the Microsoft Graph Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "msgraph_core-1.3.2-py3-none-any.whl", hash = "sha256:96d3d1ad1d2bc3d1d5eb76599e18cb699ccc78781b3492702b2f23ec8f22ae6e"}, - {file = "msgraph_core-1.3.2.tar.gz", hash = "sha256:d23964bfb20f636874ef875a984f5ab9e48fc4854f1530c6e4a617b84d59777d"}, + {file = "msgraph_core-1.3.3-py3-none-any.whl", hash = "sha256:9dbbc0c88e174c1d5da1c17d286965d6b26ebaf24996c7af64a39e2069006cf6"}, + {file = "msgraph_core-1.3.3.tar.gz", hash = "sha256:a3226b08b4cf9b6dbb16b868be21d5f82d8ee514ae8e46d9f0cad896159ef8d3"}, ] [package.dependencies] @@ -2906,104 +2906,116 @@ async = ["aiodns ; python_version >= \"3.5\"", "aiohttp (>=3.0) ; python_version [[package]] name = "multidict" -version = "6.1.0" +version = "6.4.3" description = "multidict implementation" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, + {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"}, ] [package.dependencies] @@ -3027,16 +3039,40 @@ docs = ["sphinx (>=8,<9)", "sphinx-autobuild"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "narwhals" +version = "1.35.0" +description = "Extremely lightweight compatibility layer between dataframe libraries" +optional = false +python-versions = ">=3.8" +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"}, +] + +[package.extras] +cudf = ["cudf (>=24.10.0)"] +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)"] +polars = ["polars (>=0.20.3)"] +pyarrow = ["pyarrow (>=11.0.0)"] +pyspark = ["pyspark (>=3.5.0)"] +sqlframe = ["sqlframe (>=3.22.0)"] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -3056,6 +3092,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.9" groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, @@ -3068,6 +3105,27 @@ doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9. extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] +[[package]] +name = "networkx" +version = "3.4.2" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, +] + +[package.extras] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "numpy" version = "2.0.2" @@ -3177,63 +3235,63 @@ openapi-schema-validator = ">=0.6.0,<0.7.0" [[package]] name = "opentelemetry-api" -version = "1.29.0" +version = "1.32.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"}, - {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"}, + {file = "opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724"}, + {file = "opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.5.0" +importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-sdk" -version = "1.29.0" +version = "1.32.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"}, - {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"}, + {file = "opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17"}, + {file = "opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092"}, ] [package.dependencies] -opentelemetry-api = "1.29.0" -opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-api = "1.32.1" +opentelemetry-semantic-conventions = "0.53b1" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.50b0" +version = "0.53b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"}, - {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"}, + {file = "opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208"}, + {file = "opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.29.0" +opentelemetry-api = "1.32.1" [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "docs"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -3365,48 +3423,54 @@ files = [ [[package]] name = "pbr" -version = "6.1.0" +version = "6.1.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" groups = ["dev"] files = [ - {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, - {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, + {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, + {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, ] +[package.dependencies] +setuptools = "*" + [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "docs"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "plotly" -version = "5.24.1" -description = "An open-source, interactive data visualization library for Python" +version = "6.0.1" +description = "An open-source interactive data visualization library for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, - {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, + {file = "plotly-6.0.1-py3-none-any.whl", hash = "sha256:4714db20fea57a435692c548a4eb4fae454f7daddf15f8d8ba7e1045681d7768"}, + {file = "plotly-6.0.1.tar.gz", hash = "sha256:dd8400229872b6e3c964b099be699f8d00c489a974f2cfccfad5e8240873366b"}, ] [package.dependencies] +narwhals = ">=1.15.1" packaging = "*" -tenacity = ">=6.2.0" + +[package.extras] +express = ["numpy"] [[package]] name = "pluggy" @@ -3436,155 +3500,149 @@ files = [ {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, ] -[[package]] -name = "portalocker" -version = "2.10.1" -description = "Wraps the portalocker recipe for easy usage" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"}, - {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"}, -] - -[package.dependencies] -pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} - -[package.extras] -docs = ["sphinx (>=1.7.1)"] -redis = ["redis"] -tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] - [[package]] name = "propcache" -version = "0.2.1" +version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, - {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, - {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, - {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, - {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, - {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, - {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, - {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, - {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, - {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, - {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, - {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, - {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, + {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"}, ] [[package]] name = "proto-plus" -version = "1.25.0" -description = "Beautiful, Pythonic protocol buffers." +version = "1.26.1" +description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, - {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, + {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, + {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, ] [package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" +protobuf = ">=3.19.0,<7.0.0" [package.extras] testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "5.29.3" +version = "6.30.2" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, - {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, - {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, - {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, - {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, - {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, - {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, - {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, - {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, + {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"}, ] [[package]] @@ -3663,18 +3721,18 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.4.1" +version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" +pyasn1 = ">=0.6.1,<0.7.0" [[package]] name = "pycodestyle" @@ -3849,14 +3907,14 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.14" +version = "10.14.3" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"}, - {file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"}, + {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, + {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, ] [package.dependencies] @@ -3868,14 +3926,14 @@ extra = ["pygments (>=2.19.1)"] [[package]] name = "pyparsing" -version = "3.2.1" +version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, - {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, ] [package.extras] @@ -4008,32 +4066,30 @@ files = [ [[package]] name = "pywin32" -version = "308" +version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["main", "dev"] +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -4115,19 +4171,20 @@ pyyaml = "*" [[package]] name = "referencing" -version = "0.35.1" +version = "0.36.2" description = "JSON Referencing + Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, - {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, ] [package.dependencies] attrs = ">=22.2.0" rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" @@ -4291,14 +4348,14 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "responses" -version = "0.25.6" +version = "0.25.7" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "responses-0.25.6-py3-none-any.whl", hash = "sha256:9cac8f21e1193bb150ec557875377e41ed56248aed94e4567ed644db564bacf1"}, - {file = "responses-0.25.6.tar.gz", hash = "sha256:eae7ce61a9603004e76c05691e7c389e59652d91e94b419623c12bbfb8e331d8"}, + {file = "responses-0.25.7-py3-none-any.whl", hash = "sha256:92ca17416c90fe6b35921f52179bff29332076bb32694c0df02dcac2c6bc043c"}, + {file = "responses-0.25.7.tar.gz", hash = "sha256:8ebae11405d7a5df79ab6fd54277f6f2bc29b2d002d0dd2d5c632594d1ddcedb"}, ] [package.dependencies] @@ -4341,14 +4398,14 @@ six = "*" [[package]] name = "rich" -version = "13.9.4" +version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["dev"] files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, ] [package.dependencies] @@ -4361,127 +4418,138 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.22.3" +version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, - {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, - {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, - {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, - {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, - {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, - {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, - {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, - {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, - {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, - {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, - {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, - {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, + {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"}, ] [[package]] name = "rsa" -version = "4.9" +version = "4.9.1" description = "Pure-Python RSA implementation" optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" groups = ["main"] files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, ] [package.dependencies] @@ -4645,19 +4713,19 @@ files = [ [[package]] name = "setuptools" -version = "75.8.0" +version = "79.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, - {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, + {file = "setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a"}, + {file = "setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] @@ -4748,26 +4816,26 @@ files = [ [[package]] name = "std-uritemplate" -version = "2.0.1" +version = "2.0.3" description = "std-uritemplate implementation for Python" optional = false python-versions = "<4.0,>=3.8" groups = ["main"] files = [ - {file = "std_uritemplate-2.0.1-py3-none-any.whl", hash = "sha256:6405d13f9ff3b8f70e1fa4eaefa373049659e1a61870c4b651441821a3fc984d"}, - {file = "std_uritemplate-2.0.1.tar.gz", hash = "sha256:f4684ae050167e237ed5819423139893e0b5b7f08dc6b7467bba3fdd64608be8"}, + {file = "std_uritemplate-2.0.3-py3-none-any.whl", hash = "sha256:434df26453bf68c6077879fed6609b2c39e2fc73080e74cd157269d5f8abdb3e"}, + {file = "std_uritemplate-2.0.3.tar.gz", hash = "sha256:ad4cb1d671bcf4a3608b3598c687be4b0929867c53a2d69c105989da6a5a2d4c"}, ] [[package]] name = "stevedore" -version = "5.4.0" +version = "5.4.1" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "stevedore-5.4.0-py3-none-any.whl", hash = "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857"}, - {file = "stevedore-5.4.0.tar.gz", hash = "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d"}, + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, ] [package.dependencies] @@ -4806,32 +4874,16 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tenacity" -version = "9.0.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, - {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - [[package]] name = "tldextract" -version = "5.1.3" +version = "5.3.0" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tldextract-5.1.3-py3-none-any.whl", hash = "sha256:78de310cc2ca018692de5ddf320f9d6bd7c5cf857d0fd4f2175f0cdf4440ea75"}, - {file = "tldextract-5.1.3.tar.gz", hash = "sha256:d43c7284c23f5dc8a42fd0fee2abede2ff74cc622674e4cb07f514ab3330c338"}, + {file = "tldextract-5.3.0-py3-none-any.whl", hash = "sha256:f70f31d10b55c83993f55e91ecb7c5d84532a8972f22ec578ecfbe5ea2292db2"}, + {file = "tldextract-5.3.0.tar.gz", hash = "sha256:b3d2b70a1594a0ecfa6967d57251527d58e00bb5a91a74387baa0d87a0678609"}, ] [package.dependencies] @@ -4901,14 +4953,14 @@ files = [ [[package]] name = "typer" -version = "0.15.1" +version = "0.15.2" 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.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, - {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, + {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, + {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, ] [package.dependencies] @@ -4919,26 +4971,26 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] [[package]] name = "tzdata" -version = "2024.2" +version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main"] files = [ - {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, - {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] [[package]] @@ -4991,15 +5043,15 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev", "docs"] markers = "python_version >= \"3.10\"" files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] @@ -5192,14 +5244,14 @@ files = [ [[package]] name = "xlsxwriter" -version = "3.2.0" +version = "3.2.3" description = "A Python module for creating Excel XLSX files." optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e"}, - {file = "XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c"}, + {file = "XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d"}, + {file = "xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5"}, ] [[package]] @@ -5216,100 +5268,122 @@ files = [ [[package]] name = "yarl" -version = "1.18.3" +version = "1.20.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {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"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -propcache = ">=0.2.0" +propcache = ">=0.2.1" [[package]] name = "zipp" @@ -5335,4 +5409,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">3.9.1,<3.13" -content-hash = "be5328ab34647258a0ba8c9a7966b7abb38c842bd84238d242f05afec60cddbb" +content-hash = "cc15c8ee6b064b3fda85177c0f1c24a57880c401682fe62daefc2d0f4043150a" diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index a938b5cf3e..34601abebf 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -18,6 +18,9 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add check for Bypass Disable in every Mailbox for service Defender in M365 [(#7418)](https://github.com/prowler-cloud/prowler/pull/7418) - Add new check `teams_external_domains_restricted` [(#7557)](https://github.com/prowler-cloud/prowler/pull/7557) - Add new check `teams_email_sending_to_channel_disabled` [(#7533)](https://github.com/prowler-cloud/prowler/pull/7533) +- Add new check for External Mails Tagged for service Exchange in M365 [(#7580)](https://github.com/prowler-cloud/prowler/pull/7580) +- Add new check for WhiteList not used in Transport Rules for service Defender in M365 [(#7569)](https://github.com/prowler-cloud/prowler/pull/7569) +- Add check for Inbound Antispam Policy with no allowed domains from service Defender in M365 [(#7500)](https://github.com/prowler-cloud/prowler/pull/7500) - Add new check `teams_meeting_anonymous_user_join_disabled` [(#7565)](https://github.com/prowler-cloud/prowler/pull/7565) - Add new check `teams_unmanaged_communication_disabled` [(#7561)](https://github.com/prowler-cloud/prowler/pull/7561) - Add new check `teams_external_users_cannot_start_conversations` [(#7562)](https://github.com/prowler-cloud/prowler/pull/7562) @@ -25,6 +28,16 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add new check for SafeList not enabled in the Connection Filter Policy from service Defender in M365 [(#7492)](https://github.com/prowler-cloud/prowler/pull/7492) - Add new check for DKIM enabled for service Defender in M365 [(#7485)](https://github.com/prowler-cloud/prowler/pull/7485) - Add new check `teams_meeting_anonymous_user_start_disabled` [(#7567)](https://github.com/prowler-cloud/prowler/pull/7567) +- Add new check `teams_meeting_external_lobby_bypass_disabled` [(#7568)](https://github.com/prowler-cloud/prowler/pull/7568) +- Add new check `teams_meeting_dial_in_lobby_bypass_disabled` [(#7571)](https://github.com/prowler-cloud/prowler/pull/7571) +- Add new check `teams_meeting_external_control_disabled` [(#7604)](https://github.com/prowler-cloud/prowler/pull/7604) +- Add new check `teams_meeting_external_chat_disabled` [(#7605)](https://github.com/prowler-cloud/prowler/pull/7605) +- Add new check `teams_meeting_recording_disabled` [(#7607)](https://github.com/prowler-cloud/prowler/pull/7607) +- Add new check `teams_meeting_presenters_restricted` [(#7613)](https://github.com/prowler-cloud/prowler/pull/7613) +- Add new check `teams_meeting_chat_anonymous_users_disabled` [(#7579)](https://github.com/prowler-cloud/prowler/pull/7579) +- Add Prowler Threat Score Compliance Framework [(#7603)](https://github.com/prowler-cloud/prowler/pull/7603) +- Add new check `sharepoint_onedrive_sync_restricted_unmanaged_devices` [(#7589)](https://github.com/prowler-cloud/prowler/pull/7589) +- Add new check for Additional Storage restricted for Exchange in M365 [(#7638)](https://github.com/prowler-cloud/prowler/pull/7638) ### Fixed @@ -33,6 +46,10 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add the correct values for logger.info inside iam service [(#7526)](https://github.com/prowler-cloud/prowler/pull/7526). - Update S3 bucket naming validation to accept dots [(#7545)](https://github.com/prowler-cloud/prowler/pull/7545). - Handle new FlowLog model properties in Azure [(#7546)](https://github.com/prowler-cloud/prowler/pull/7546). +- Improve compliance and dashboard [(#7596)](https://github.com/prowler-cloud/prowler/pull/7596) +- Remove invalid parameter `create_file_descriptor` [(#7600)](https://github.com/prowler-cloud/prowler/pull/7600) +- Remove first empty line in HTML output [(#7606)](https://github.com/prowler-cloud/prowler/pull/7606) +- Ensure that ContentType in upload_file matches the uploaded file’s format [(#7635)](https://github.com/prowler-cloud/prowler/pull/7635) --- diff --git a/prowler/__main__.py b/prowler/__main__.py index 3bacaf291f..b201268da3 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -70,6 +70,15 @@ from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( AzureMitreAttack, ) from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_gcp import GCPMitreAttack +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_aws import ( + ProwlerThreatScoreAWS, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_azure import ( + ProwlerThreatScoreAzure, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_gcp import ( + ProwlerThreatScoreGCP, +) from prowler.lib.outputs.csv.csv import CSV from prowler.lib.outputs.finding import Finding from prowler.lib.outputs.html.html import HTML @@ -478,6 +487,18 @@ def prowler(): ) generated_outputs["compliance"].append(kisa_ismsp) kisa_ismsp.batch_write_data_to_file() + elif compliance_name == "prowler_threatscore_aws": + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + prowler_threatscore = ProwlerThreatScoreAWS( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(prowler_threatscore) + prowler_threatscore.batch_write_data_to_file() else: filename = ( f"{output_options.output_directory}/compliance/" @@ -545,6 +566,18 @@ def prowler(): ) generated_outputs["compliance"].append(iso27001) iso27001.batch_write_data_to_file() + elif compliance_name == "prowler_threatscore_azure": + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + prowler_threatscore = ProwlerThreatScoreAzure( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(prowler_threatscore) + prowler_threatscore.batch_write_data_to_file() else: filename = ( f"{output_options.output_directory}/compliance/" @@ -612,6 +645,18 @@ def prowler(): ) generated_outputs["compliance"].append(iso27001) iso27001.batch_write_data_to_file() + elif compliance_name == "prowler_threatscore_gcp": + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + prowler_threatscore = ProwlerThreatScoreGCP( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(prowler_threatscore) + prowler_threatscore.batch_write_data_to_file() else: filename = ( f"{output_options.output_directory}/compliance/" @@ -717,7 +762,6 @@ def prowler(): generic_compliance = GenericCompliance( findings=finding_outputs, compliance=bulk_compliance_frameworks[compliance_name], - create_file_descriptor=True, file_path=filename, ) generated_outputs["compliance"].append(generic_compliance) diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index f4f9736e54..28d1fc4406 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -12,7 +12,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.1 Executive Participation", "AuditChecklist": [ "Is there documentation outlining the responsibilities and roles of executives to ensure their participation in the establishment and operation of the information protection and personal information protection management system?", @@ -41,7 +41,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.2 Designation of Chief Officers", "AuditChecklist": [ "Has the CEO officially designated a chief officer responsible for overseeing information protection and personal information protection?", @@ -77,7 +77,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.3 Organization Structure", "AuditChecklist": [ "Has the organization established and operated a working group with expertise to support the work of the CISO and CPO and systematically implement the organization's information protection and personal information protection activities?", @@ -112,7 +112,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.4 Scope Setting", "AuditChecklist": [ "Has the organization set the scope of the management system to include key assets that may affect core services and personal information processing?", @@ -145,7 +145,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.5 Policy Establishment", "AuditChecklist": [ "Has the organization established a top-level information protection and personal information protection policy that serves as the foundation for all information protection and personal information protection activities?", @@ -180,7 +180,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.1. Management System", + "Subdomain": "1.1 Management System", "Section": "1.1.6 Resource Allocation", "AuditChecklist": [ "Has the organization secured personnel with expertise in the fields of information protection and personal information protection?", @@ -216,7 +216,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.2. Risk Management", + "Subdomain": "1.2 Risk Management", "Section": "1.2.1 Identification of Information Assets", "AuditChecklist": [ "Has the organization established classification criteria for information assets and identified all assets within the scope of the information protection and personal information protection management system, maintaining them in a list?", @@ -249,7 +249,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.2. Risk Management", + "Subdomain": "1.2 Risk Management", "Section": "1.2.2 Status and Flow Analysis", "AuditChecklist": [ "Has the organization identified and documented the status and workflows of information services across all areas of the management system?", @@ -279,7 +279,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.2. Risk Management", + "Subdomain": "1.2 Risk Management", "Section": "1.2.3 Risk Assessment", "AuditChecklist": [ "Has the organization defined methods for identifying and assessing risks that could arise from various aspects, depending on the characteristics of the organization or service?", @@ -322,7 +322,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.2. Risk Management", + "Subdomain": "1.2 Risk Management", "Section": "1.2.4 Selection of Protective Measures", "AuditChecklist": [ "Has the organization developed risk treatment strategies (e.g., risk reduction, avoidance, transfer, acceptance) and selected protective measures to address the identified risks?", @@ -352,7 +352,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.3. Operation of the Management System", + "Subdomain": "1.3 Operation of the Management System", "Section": "1.3.1 Implementation of Protective Measures", "AuditChecklist": [ "Are the protective measures effectively implemented according to the implementation plan, and are the implementation results reported to management to verify their accuracy and effectiveness?", @@ -384,7 +384,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.3. Operation of the Management System", + "Subdomain": "1.3 Operation of the Management System", "Section": "1.3.2 Sharing of Protective Measures", "AuditChecklist": [ "Has the organization clearly identified the departments and personnel responsible for the operation or implementation of the protective measures?", @@ -409,7 +409,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.3. Operation of the Management System", + "Subdomain": "1.3 Operation of the Management System", "Section": "1.3.3 Operation Status Management", "AuditChecklist": [ "Are information protection and personal information protection activities that need to be performed periodically or continuously for the operation of the management system documented and managed?", @@ -439,7 +439,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.4. Inspection and Improvement of the Management System", + "Subdomain": "1.4 Inspection and Improvement of the Management System", "Section": "1.4.1 Review of Legal Requirements Compliance", "AuditChecklist": [ "Is the organization regularly identifying and maintaining up-to-date legal requirements related to information protection and personal information protection?", @@ -477,7 +477,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.4. Inspection and Improvement of the Management System", + "Subdomain": "1.4 Inspection and Improvement of the Management System", "Section": "1.4.2 Management System Audit", "AuditChecklist": [ "Has the organization established a management system audit plan that includes the criteria, scope, frequency, and qualifications for audit personnel to audit the management system's effectiveness in accordance with legal requirements and established policies?", @@ -508,7 +508,7 @@ "Attributes": [ { "Domain": "1. Establishment and Operation of the Management System", - "Subdomain": "1.4. Management System Inspection and Improvement", + "Subdomain": "1.4 Inspection and Improvement of the Management System", "Section": "1.4.3 Management System Improvement", "AuditChecklist": [ "Are the root causes of the issues identified during legal compliance reviews and management system inspections analyzed, and are preventive and improvement measures established and implemented?", @@ -541,7 +541,7 @@ "Attributes": [ { "Domain": "2. Control Measures Requirements", - "Subdomain": "2.1. Policies, Organization, and Asset Management", + "Subdomain": "2.1 Policy, Organization, Asset Management", "Section": "2.1.1 Policy Maintenance", "AuditChecklist": [ "Has the organization established and implemented a procedure for regularly reviewing the validity of information protection and personal information protection policies and implementation documents?", @@ -577,7 +577,7 @@ "Attributes": [ { "Domain": "2. Control Measures Requirements", - "Subdomain": "2.1. Policies, Organization, and Asset Management", + "Subdomain": "2.1 Policy, Organization, Asset Management", "Section": "2.1.2 Organization Maintenance", "AuditChecklist": [ "Are the roles and responsibilities of those responsible for and involved in information protection and personal information protection clearly defined?", @@ -624,8 +624,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.1. Policy, Organization, Asset Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.1 Policy, Organization, Asset Management", "Section": "2.1.3 Management of Information Assets", "AuditChecklist": [ "Are handling procedures (creation, introduction, storage, use, disposal) and protection measures defined and implemented according to the security classification of information assets?", @@ -657,8 +657,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.2. Personnel Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.1 Designation and Management of Key Personnel", "AuditChecklist": [ "Are the criteria for key duties, such as handling personal information and important information or accessing key systems, clearly defined?", @@ -693,8 +693,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.2. Personnel Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.2 Separation of Duties", "AuditChecklist": [ "Are criteria for the separation of duties established and applied to prevent potential harm from the misuse or abuse of authority?", @@ -720,8 +720,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.2. Human Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.3 Security Pledge", "AuditChecklist": [ "When hiring new personnel, is there a signed security and personal information protection agreement that specifies their responsibilities?", @@ -750,8 +750,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.2. Human Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.4 Awareness and Training", "AuditChecklist": [ "Is an annual training plan approved by management, detailing the timing, duration, target audience, content, and method of information protection and personal information protection training?", @@ -788,8 +788,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.2. Human Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.5 Management of Resignation and Job Changes", "AuditChecklist": [ "Are personnel changes (e.g., resignation, job changes, department transfers, leave of absence) shared among HR, information protection, personal information protection, and IT system operations departments?", @@ -820,8 +820,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.2. Human Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.2 Human Security", "Section": "2.2.6 Actions in Case of Security Violations", "AuditChecklist": [ "Has the organization established disciplinary measures for employees and relevant external parties in case of violations of information protection and personal information protection responsibilities and obligations under laws, regulations, and internal policies?", @@ -847,8 +847,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.3. External Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.3 External Security", "Section": "2.3.1 Management of External Parties", "AuditChecklist": [ "Has the organization identified the status of outsourcing and the use of external facilities and services within the scope of the management system?", @@ -878,8 +878,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.3. External Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.3 External Security", "Section": "2.3.2 Security in Contracts with External Parties", "AuditChecklist": [ "When selecting external services or outsourcing vendors related to the handling of important information and personal information, does the organization follow procedures to consider the vendors' capabilities in information protection and personal information protection?", @@ -910,8 +910,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.3. External Party Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.3 External Security", "Section": "2.3.3 External Party Security Implementation Management", "AuditChecklist": [ "Are periodic inspections or audits conducted to ensure external parties comply with information protection and personal information protection requirements specified in contracts, agreements, and internal policies?", @@ -945,8 +945,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.3. External Party Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.3 External Security", "Section": "2.3.4 Security for External Party Contract Changes and Expiry", "AuditChecklist": [ "Has the organization established and implemented security measures to ensure the return of information assets, deletion of information system access accounts, and the acquisition of confidentiality agreements in accordance with official procedures when an external party contract expires, a task is completed, or there is a personnel change?", @@ -977,8 +977,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.1 Designation of Protected Zones", "AuditChecklist": [ "Has the organization established criteria for designating physical protection zones such as controlled areas, restricted areas, and reception areas to protect personal and sensitive information, documents, storage media, key facilities, and systems from physical and environmental threats?", @@ -1008,8 +1008,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.2 Access Control", "AuditChecklist": [ "Is access to protected areas controlled so that only authorized personnel are allowed to enter according to access procedures?", @@ -1040,8 +1040,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.3 Information System Protection", "AuditChecklist": [ "Are information systems placed in separated locations based on their importance, usage, and characteristics?", @@ -1068,8 +1068,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.4 Operation of Protective Facilities", "AuditChecklist": [ "Are necessary facilities established and operational procedures set up based on the importance and characteristics of each protected area to prevent disasters such as fire, flood, and power failure caused by human error or natural disasters?", @@ -1100,8 +1100,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.5 Operations in Secure Zones", "AuditChecklist": [ "When operations within secure zones, such as the introduction and maintenance of information systems, are required, are formal procedures for application and execution of such operations established and implemented?", @@ -1127,8 +1127,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.6 Device Control for Inbound and Outbound", "AuditChecklist": [ "Are control procedures established and implemented to prevent security incidents such as information leakage and malware infection when information systems, mobile devices, storage media, etc., are brought into or taken out of secure zones?", @@ -1157,8 +1157,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.4. Physical Security", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.4 Physical Security", "Section": "2.4.7 Work Environment Security", "AuditChecklist": [ "Are protection measures established and implemented for shared facilities and office equipment such as document storage, shared PCs, multifunction printers, file servers, etc.?", @@ -1215,8 +1215,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.5. Authentication and Access Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.1 User Account Management", "AuditChecklist": [ "Has the organization established and implemented formal procedures for registering, changing, and deleting user accounts and access rights to information systems, personal information, and critical information?", @@ -1248,8 +1248,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.5. Authentication and Access Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.2 User Identification", "AuditChecklist": [ "Are unique identifiers assigned to users and personal information handlers in information systems and personal information processing systems, and is the use of easily guessable identifiers restricted?", @@ -1309,8 +1309,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.5. Authentication and Authorization Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.3 User Authentication", "AuditChecklist": [ "Is access to information systems and personal information processing systems controlled through secure user authentication procedures, login attempt limitations, and warnings for illegal login attempts?", @@ -1354,8 +1354,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.5. Authentication and Authorization Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.4 Password Management", "AuditChecklist": [ "Are procedures for managing and creating secure user passwords for information systems established and implemented?", @@ -1397,8 +1397,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.5. Authentication and Privilege Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.5 Management of Special Accounts and Privileges", "AuditChecklist": [ "Is there a formal privilege request and approval process established and implemented to ensure that special privileges, such as administrative privileges, are only granted to a minimal number of people?", @@ -1445,8 +1445,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.5. Authentication and Privilege Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.5 Authentication and Access Management", "Section": "2.5.6 Review of Access Rights", "AuditChecklist": [ "Are the histories of account and access right creation, registration, granting, use, modification, and deletion for information systems, personal information, and important information being recorded?", @@ -1590,8 +1590,8 @@ ], "Attributes": [ { - "Domain": "2. Control Measures", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.1 Network Access", "AuditChecklist": [ "Has the organization identified all access paths to its network and ensured that internal networks are controlled so that only authorized users can access them according to the access control policy?", @@ -1651,8 +1651,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.2 Access to Information Systems", "AuditChecklist": [ "Have users, access locations, and access means allowed to access operating systems (OS) of information systems such as servers, network systems, and security systems been defined and controlled?", @@ -1687,8 +1687,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.3 Access to Applications", "AuditChecklist": [ "Are access rights to applications granted differentially based on the user's tasks to control access to sensitive information?", @@ -1769,8 +1769,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.4 Database Access", "AuditChecklist": [ "Are you identifying the information stored and managed in the database, such as the table list?", @@ -1804,8 +1804,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.5 Wireless Network Access", "AuditChecklist": [ "When using a wireless network for business purposes, are you establishing and implementing protection measures such as authentication and encryption of transmitted and received data to ensure the security of the wireless AP and network segment?", @@ -1864,8 +1864,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.6 Remote Access Control", "AuditChecklist": [ "Is remote operation of information systems through external networks such as the internet prohibited in principle, and are compensatory measures in place if allowed for unavoidable reasons such as incident response?", @@ -1922,8 +1922,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.6. Access Control", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.6 Access Control", "Section": "2.6.7 Internet Access Control", "AuditChecklist": [ "Is there an established and implemented policy to control internet access for work PCs used for key duties and personal information handling terminals?", @@ -2025,8 +2025,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.7. Application of Encryption", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.7 Application of Encryption", "Section": "2.7.1 Application of Encryption Policy", "AuditChecklist": [ "Has an encryption policy been established that includes encryption targets, encryption strength, and encryption usage in consideration of legal requirements for the protection of personal and important information?", @@ -2069,8 +2069,8 @@ ], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.7. Application of Encryption", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.7 Application of Encryption", "Section": "2.7.2 Cryptographic Key Management", "AuditChecklist": [ "Are procedures for the generation, use, storage, distribution, modification, recovery, and destruction of cryptographic keys established and implemented?", @@ -2116,8 +2116,8 @@ ], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.8. Security for Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.1 Definition of Security Requirements", "AuditChecklist": [ "When introducing, developing, or modifying an information system, are procedures for reviewing the validity of information protection and personal information protection aspects and for acquisition established and implemented?", @@ -2167,8 +2167,8 @@ ], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.8. Security for Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.2 Review and Testing of Security Requirements", "AuditChecklist": [ "When introducing, developing, or modifying an information system, are tests conducted to verify whether the security requirements defined during the analysis and design stages have been effectively applied?", @@ -2208,8 +2208,8 @@ ], "Attributes": [ { - "Domain": "2. Security Requirements for Protection Measures", - "Subdomain": "2.8. Security for Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.3 Separation of Test and Production Environments", "AuditChecklist": [ "Are development and test systems separated from the production system?", @@ -2237,8 +2237,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.8. Security in Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.4 Test Data Security", "AuditChecklist": [ "Is the use of actual operational data restricted during the development and testing of information systems?", @@ -2269,8 +2269,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.8. Security in Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.5 Source Program Management", "AuditChecklist": [ "Have procedures been established and implemented to control access to source programs by unauthorized persons?", @@ -2297,8 +2297,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.8. Security in Information System Introduction and Development", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.8 Security in Information System Introduction and Development", "Section": "2.8.6 Transition to Operational Environment", "AuditChecklist": [ "Have control procedures been established and implemented to safely transition newly introduced, developed, or modified systems to the operational environment?", @@ -2341,8 +2341,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.9. System and Service Operations Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.1 Change Management", "AuditChecklist": [ "Have procedures been established and implemented for changes to assets related to information systems (hardware, operating systems, commercial software packages, etc.)?", @@ -2409,8 +2409,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.9. System and Service Operations Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.2 Performance and Fault Management", "AuditChecklist": [ "Have procedures been established and implemented to continuously monitor performance and capacity to ensure the availability of information systems?", @@ -2480,8 +2480,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.9. System and Service Operation Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.3 Backup and Recovery Management", "AuditChecklist": [ "Have backup and recovery procedures been established and implemented, including targets, frequency, methods, and procedures?", @@ -2595,8 +2595,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.9. System and Service Operation Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.4 Log and Access Record Management", "AuditChecklist": [ "Has the organization established log management procedures for information systems such as servers, applications, security systems, and network systems, and is it generating and storing the necessary logs accordingly?", @@ -2658,8 +2658,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Control Requirements", - "Subdomain": "2.9. System and Service Operation Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.5 Log and Access Record Inspection", "AuditChecklist": [ "Are there established log review and monitoring procedures, including the frequency, targets, and methods for detecting errors, misuse (unauthorized access, excessive queries, etc.), fraud, and other anomalies in the information system?", @@ -2693,8 +2693,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Control Requirements", - "Subdomain": "2.9. System and Service Operation Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.6 Time Synchronization", "AuditChecklist": [ "Is the system time synchronized with the standard time?", @@ -2719,8 +2719,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Control Requirements", - "Subdomain": "2.9. System and Service Operation Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.9 System and Service Operation Management", "Section": "2.9.7 Reuse and Disposal of Information Assets", "AuditChecklist": [ "Are secure reuse and disposal procedures for information assets established and implemented?", @@ -2831,8 +2831,8 @@ ], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.1 Security System Operation", "AuditChecklist": [ "Has the organization established and implemented operational procedures for the security systems in use?", @@ -2872,8 +2872,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protective Measure Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.2 Cloud Security", "AuditChecklist": [ "Is the responsibility and role for information protection and personal information protection clearly defined with the cloud service provider, and is it reflected in contracts (such as SLA)?", @@ -2984,8 +2984,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measure Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.3 Public Server Security", "AuditChecklist": [ "Are protective measures established and implemented for the operation of public servers?", @@ -3014,8 +3014,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.4 Security for Electronic Transactions and FinTech", "AuditChecklist": [ "Are protection measures established and implemented to ensure the safety and reliability of transactions when providing electronic transaction and FinTech services?", @@ -3059,8 +3059,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.5 Secure Information Transmission", "AuditChecklist": [ "Has a secure transmission policy been established when transmitting personal and critical information to external organizations?", @@ -3093,8 +3093,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measure Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.6 Security for Business Devices", "AuditChecklist": [ "Are security control policies, such as device authentication, approval, access scope, and security settings, established and implemented for devices used for business purposes, such as PCs, laptops, virtual PCs, and tablets?", @@ -3129,8 +3129,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.7 Management of Removable Media", "AuditChecklist": [ "Are policies and procedures established and implemented for handling (use), storage, disposal, and reuse of removable media such as external hard drives, USB memory, and CDs?", @@ -3179,8 +3179,8 @@ ], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.8 Patch Management", "AuditChecklist": [ "Are patch management policies and procedures for operating systems (OS) and software established and implemented according to the characteristics and importance of each asset, such as servers, network systems, security systems, and PCs?", @@ -3213,8 +3213,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Security Control Requirements", - "Subdomain": "2.10. System and Service Security Management", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.10 System and Service Security Management", "Section": "2.10.9 Malware Control", "AuditChecklist": [ "Are protection measures established and implemented to protect information systems and business terminals from malware such as viruses, worms, Trojans, and ransomware?", @@ -3248,8 +3248,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.11. Incident Prevention and Response", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.11 Incident Prevention and Response", "Section": "2.11.1 Establishment of Incident Prevention and Response System", "AuditChecklist": [ "Has the organization established procedures and systems to prevent security breaches and personal information leaks and to respond quickly and effectively when incidents occur?", @@ -3307,8 +3307,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measures Requirements", - "Subdomain": "2.11. Incident Prevention and Response", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.11 Incident Prevention and Response", "Section": "2.11.2 Vulnerability Inspection and Remediation", "AuditChecklist": [ "Has the organization established and implemented procedures for conducting regular vulnerability inspections of information systems?", @@ -3371,8 +3371,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.11. Incident Prevention and Response", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.11 Incident Prevention and Response", "Section": "2.11.3 Abnormal Behavior Analysis and Monitoring", "AuditChecklist": [ "Is the organization collecting, analyzing, and monitoring network traffic, data flows, and event logs from major information systems, applications, networks, and security systems to detect abnormal behaviors such as intrusion attempts, personal information leakage attempts, or fraudulent activities?", @@ -3403,8 +3403,8 @@ ], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.11. Incident Prevention and Response", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.11 Incident Prevention and Response", "Section": "2.11.4 Incident Response Training and Improvement", "AuditChecklist": [ "Has the organization established a simulation training plan for responding to security incidents and personal information leakage incidents, and are such training exercises conducted at least once a year?", @@ -3431,8 +3431,8 @@ "Checks": [], "Attributes": [ { - "Domain": "2. Protection Measures Requirements", - "Subdomain": "2.11. Incident Prevention and Response", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.11 Incident Prevention and Response", "Section": "2.11.5 Incident Response and Recovery", "AuditChecklist": [ "When signs of or actual incidents of security breaches or personal information leakage are detected, is the organization responding and reporting promptly according to the defined incident response procedures?", @@ -3501,8 +3501,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measure Requirements", - "Subdomain": "2.12. Disaster Recovery", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.12 Disaster Recovery", "Section": "2.12.1 Safety Measures for Disaster Preparedness", "AuditChecklist": [ "Has the organization identified IT disaster types that could threaten the continuity of core services (businesses) and analyzed the expected scale of damage and impact on operations to identify core IT services (businesses) and systems?", @@ -3570,8 +3570,8 @@ ], "Attributes": [ { - "Domain": "2. Protective Measure Requirements", - "Subdomain": "2.12. Disaster Recovery", + "Domain": "2. Control Measures Requirements", + "Subdomain": "2.12 Disaster Recovery", "Section": "2.12.2 Disaster Recovery Testing and Improvement", "AuditChecklist": [ "Has the organization established and implemented disaster recovery test plans to evaluate the effectiveness of the established IT disaster recovery system?", @@ -3599,7 +3599,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.1. Protection Measures during Personal Information Collection", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.1 Collection and Use of Personal Information", "AuditChecklist": [ "When collecting personal information, is it collected in accordance with lawful requirements such as obtaining the data subject’s consent, complying with legal obligations, or concluding and fulfilling contracts?", @@ -3645,7 +3645,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.1. Protection Measures during Personal Information Collection", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.2 Restrictions on the Collection of Personal Information", "AuditChecklist": [ "When collecting personal information, is only the minimum amount of information necessary for the intended purpose being collected?", @@ -3678,7 +3678,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.1. Protection Measures during Personal Information Collection", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.3 Restrictions on the Processing of Resident Registration Numbers", "AuditChecklist": [ "Are resident registration numbers only processed when there is a clear legal basis?", @@ -3713,8 +3713,8 @@ "Checks": [], "Attributes": [ { - "Domain": "3. Personal Information Processing Requirements", - "Subdomain": "3.1. Protection Measures for Personal Information Collection", + "Domain": "3. Requirements for Each Stage of Personal Information Processing", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.4 Restriction on Processing of Sensitive and Unique Identifying Information", "AuditChecklist": [ "Is sensitive information processed only with the separate consent of the data subject or when legally required?", @@ -3744,8 +3744,8 @@ "Checks": [], "Attributes": [ { - "Domain": "3. Personal Information Processing Requirements", - "Subdomain": "3.1. Protection Measures for Personal Information Collection", + "Domain": "3. Requirements for Each Stage of Personal Information Processing", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.5 Indirect Collection of Personal Information", "AuditChecklist": [ "When receiving personal information from a third party, is it clearly stated in the contract that the responsibility for obtaining consent for the collection of personal information lies with the party providing the information?", @@ -3779,8 +3779,8 @@ "Checks": [], "Attributes": [ { - "Domain": "3. Personal Information Processing Requirements", - "Subdomain": "3.1. Protection Measures for Personal Information Collection", + "Domain": "3. Requirements for Each Stage of Personal Information Processing", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.6 Installation and Operation of Video Information Processing Devices", "AuditChecklist": [ "When installing and operating fixed video information processing devices in public places, is it reviewed whether the installation meets legal requirements?", @@ -3819,7 +3819,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.1. Protection Measures When Collecting Personal Information", + "Subdomain": "3.1 Protection Measures for Personal Information Collection", "Section": "3.1.7 Collection and Use of Personal Information for Marketing Purposes", "AuditChecklist": [ "When obtaining consent from data subjects to process personal information for the purpose of promoting or recommending goods or services, is the data subject clearly informed, and is separate consent obtained?", @@ -3861,7 +3861,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.2. Protection Measures When Retaining and Using Personal Information", + "Subdomain": "3.2 Protection Measures When Retaining and Using Personal Information", "Section": "3.2.1 Management of Personal Information Status", "AuditChecklist": [ "Is the status of collected and retained personal information, including the items, volume, purpose and method of processing, and retention period, regularly managed?", @@ -3904,7 +3904,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.2. Protection Measures for Retention and Use of Personal Information", + "Subdomain": "3.2 Protection Measures When Retaining and Using Personal Information", "Section": "3.2.2 Personal Information Quality Assurance", "AuditChecklist": [ "Are procedures and methods in place to maintain personal information in an accurate and up-to-date state?", @@ -3932,7 +3932,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.2. Protection Measures for Retention and Use of Personal Information", + "Subdomain": "3.2 Protection Measures When Retaining and Using Personal Information", "Section": "3.2.3 Protection of User Device Access", "AuditChecklist": [ "When accessing information stored on the user's mobile device or functions installed on the device, are users clearly informed and their consent obtained?", @@ -3963,7 +3963,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.2. Protection Measures for Retention and Use of Personal Information", + "Subdomain": "3.2 Protection Measures When Retaining and Using Personal Information", "Section": "3.2.4 Use and Provision of Personal Information Beyond Purpose", "AuditChecklist": [ "Is personal information used or provided only within the scope of the purpose consented to by the data subject at the time of collection or as permitted by law?", @@ -4000,7 +4000,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.2. Protection Measures for Retention and Use of Personal Information", + "Subdomain": "3.2 Protection Measures When Retaining and Using Personal Information", "Section": "3.2.5 Processing of Pseudonymized Information", "AuditChecklist": [ "When processing pseudonymized information, are procedures established for purpose limitation, pseudonymization methods and standards, adequacy review, prohibition of re-identification, and actions in case of re-identification?", @@ -4035,7 +4035,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.3. Protective Measures When Providing Personal Information", + "Subdomain": "3.3 Protection Measures When Providing Personal Information", "Section": "3.3.1 Provision of Personal Information to Third Parties", "AuditChecklist": [ "When providing personal information to third parties, are legal requirements such as consent from the data subject or compliance with legal obligations clearly identified and followed?", @@ -4074,7 +4074,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.3. Protective Measures When Providing Personal Information", + "Subdomain": "3.3 Protection Measures When Providing Personal Information", "Section": "3.3.2 Outsourcing of Personal Information Processing", "AuditChecklist": [ "When outsourcing personal information processing tasks (including sub-outsourcing) to third parties, are the details of the outsourced tasks and the trustees regularly updated and disclosed on the website?", @@ -4106,7 +4106,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.3. Protective Measures When Providing Personal Information", + "Subdomain": "3.3 Protection Measures When Providing Personal Information", "Section": "3.3.3 Transfer of Personal Information Due to Business Transfers", "AuditChecklist": [ "When transferring personal information to another party due to the transfer or merger of all or part of the business, are the necessary matters communicated to the data subjects in advance?", @@ -4137,7 +4137,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.3. Protection Measures When Providing Personal Information", + "Subdomain": "3.3 Protection Measures When Providing Personal Information", "Section": "3.3.4 Transfer of Personal Information Abroad", "AuditChecklist": [ "When transferring personal information abroad, has the data subject been fully informed of all notification requirements and obtained separate consent, or complied with certification or recognition, as required by law?", @@ -4171,7 +4171,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.4. Protection Measures When Destroying Personal Information", + "Subdomain": "3.4 Protection Measures When Destroying Personal Information", "Section": "3.4.1 Destruction of Personal Information", "AuditChecklist": [ "Has an internal policy been established regarding the retention period and destruction of personal information?", @@ -4205,7 +4205,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.4. Protection Measures When Destroying Personal Information", + "Subdomain": "3.4 Protection Measures When Destroying Personal Information", "Section": "3.4.2 Measures When Retaining Personal Information After Purpose Is Achieved", "AuditChecklist": [ "When personal information is retained beyond the retention period or after the processing purpose has been achieved, in accordance with relevant laws, is it limited to the minimum necessary period and only the minimum necessary information?", @@ -4238,7 +4238,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.5. Protection of Data Subject's Rights", + "Subdomain": "3.5 Protection of Data Subject's Rights", "Section": "3.5.1 Disclosure of Privacy Policy", "AuditChecklist": [ "Is the privacy policy written in clear and easy-to-understand language, covering all the contents required by law?", @@ -4270,7 +4270,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.5. Protection of Data Subject's Rights", + "Subdomain": "3.5 Protection of Data Subject's Rights", "Section": "3.5.2 Guaranteeing Data Subject's Rights", "AuditChecklist": [ "Are procedures in place to ensure that data subjects or their representatives can exercise their rights (hereinafter referred to as 'Requests for Access, etc.') to access, rectify, delete, or suspend the processing of their personal information in a way that is not more difficult than the process used for collecting it?", @@ -4309,7 +4309,7 @@ "Attributes": [ { "Domain": "3. Requirements for Each Stage of Personal Information Processing", - "Subdomain": "3.5. Protection of Data Subject's Rights", + "Subdomain": "3.5 Protection of Data Subject's Rights", "Section": "3.5.3 Notification to Data Subjects", "AuditChecklist": [ "If the organization is legally obligated to do so, does it periodically notify data subjects of the use and provision of their personal information, or provide them with access to an information system where they can review such details?", diff --git a/prowler/compliance/aws/prowler_threatscore_aws.json b/prowler/compliance/aws/prowler_threatscore_aws.json new file mode 100644 index 0000000000..dcc036f7f6 --- /dev/null +++ b/prowler/compliance/aws/prowler_threatscore_aws.json @@ -0,0 +1,1864 @@ +{ + "Framework": "ProwlerThreatScore", + "Version": "1.0", + "Provider": "AWS", + "Description": "Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + "Requirements": [ + { + "Id": "1.1.1", + "Description": "Ensure MFA is enabled for the 'root' user account", + "Checks": [ + "iam_root_mfa_enabled" + ], + "Attributes": [ + { + "Title": "MFA enabled for 'root'", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + "AdditionalInformation": "Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.1.2", + "Description": "Ensure hardware MFA is enabled for the 'root' user account", + "Checks": [ + "iam_root_hardware_mfa_enabled" + ], + "Attributes": [ + { + "Title": "Hardware MFA enabled for 'root'", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + "AdditionalInformation": "A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.1.3", + "Description": "Ensure multi-factor authentication (MFA) is enabled for all IAM users that have console access", + "Checks": [ + "iam_user_mfa_enabled_console_access" + ], + "Attributes": [ + { + "Title": "MFA enabled for IAM console users", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "To enhance security and reduce the risk of unauthorized access, Multi-Factor Authentication (MFA) should be enabled for all IAM users who have access to the AWS Management Console.", + "AdditionalInformation": "Without Multi-Factor Authentication (MFA), a compromised password alone is enough to allow an attacker to access the console, gaining full visibility and control over AWS resources.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.1.4", + "Description": "Ensure IAM password policy requires minimum length of 14 or greater", + "Checks": [ + "iam_password_policy_minimum_length_14" + ], + "Attributes": [ + { + "Title": "IAM password lenght 14 or greater", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Password policies help enforce password complexity requirements to strengthen account security. In AWS IAM, password policies can be configured to ensure that user passwords meet specific criteria, including a minimum length requirement. It is recommended to enforce a minimum password length of 14 characters to enhance security.", + "AdditionalInformation": "Requiring longer and more complex passwords reduces the risk of compromise from brute force attacks, credential stuffing, and other password-based threats. A 14-character minimum makes it significantly harder for attackers to guess or crack passwords, improving overall account security and resilience.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.5", + "Description": "Ensure IAM password policy prevents password reuse", + "Checks": [ + "iam_password_policy_reuse_24" + ], + "Attributes": [ + { + "Title": "IAM prevent password reuse", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "IAM password policies can be configured to prevent users from reusing previous passwords. This ensures that users create new, unique passwords instead of cycling through old ones. It is recommended to enforce password history restrictions to enhance security.", + "AdditionalInformation": "Blocking password reuse helps mitigate the risk of credential-based attacks, such as brute force and credential stuffing. It prevents users from reverting to previously compromised passwords, reducing the likelihood of unauthorized access.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.6", + "Description": "Ensure IAM password policy require at least one number", + "Checks": [ + "iam_password_policy_number" + ], + "Attributes": [ + { + "Title": "IAM password requires one number or more", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Password policies help enforce password complexity requirements to strengthen account security. In AWS IAM, password policies can be configured to ensure that user passwords meet specific criteria, including using at least number as requirement. It is recommended to enforce the usage of one number to enhance security.", + "AdditionalInformation": "Requiring more complex passwords reduces the risk of compromise from brute force attacks, credential stuffing, and other password-based threats. Using a number at least makes it significantly harder for attackers to guess or crack passwords, improving overall account security and resilience.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.7", + "Description": "Ensure IAM password policy require at least one symbol", + "Checks": [ + "iam_password_policy_symbol" + ], + "Attributes": [ + { + "Title": "IAM password policy require one symbol or more", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one special character (symbol) in user passwords. Special characters (e.g., @, #, $, %) add complexity, making passwords harder to guess or crack. It is recommended to require at least one symbol in IAM passwords to enhance security.", + "AdditionalInformation": "Requiring a symbol in passwords increases entropy, making brute-force and dictionary attacks more difficult. Attackers often rely on common or predictable password patterns, and enforcing special characters helps reduce the effectiveness of such attacks. This policy strengthens overall password security and aligns with industry best practices.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.8", + "Description": "Ensure IAM password policy require at least one lowercase letter", + "Checks": [ + "iam_password_policy_lowercase" + ], + "Attributes": [ + { + "Title": "IAM password policy require one lowercase or more", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one lowercase letter in user passwords. Including lowercase letters increases password complexity, making them more resistant to brute-force and dictionary attacks. It is recommended to require at least one lowercase letter in IAM passwords to strengthen security.", + "AdditionalInformation": "Requiring at least one lowercase letter ensures that passwords are not composed solely of numbers or uppercase letters, which are easier to guess. Attackers often use wordlists and predictable patterns when attempting to crack passwords. By enforcing lowercase letters, password complexity improves, reducing the likelihood of unauthorized access.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.9", + "Description": "Ensure IAM password policy requires at least one uppercase letter", + "Checks": [ + "iam_password_policy_uppercase" + ], + "Attributes": [ + { + "Title": "IAM password policy require one uppercase or more", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one uppercase letter in user passwords. Including uppercase letters increases password complexity, making them more resilient to brute-force and dictionary attacks. It is recommended to require at least one uppercase letter in IAM passwords to enhance security.", + "AdditionalInformation": "Requiring at least one uppercase letter ensures that passwords are not composed solely of lowercase letters or numbers, which are more predictable and easier to crack. Attackers often rely on common word variations in password attacks, and enforcing uppercase letters adds an additional layer of complexity, reducing the risk of unauthorized access.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.10", + "Description": "Ensure credentials unused for 45 days or more are disabled", + "Checks": [ + "iam_user_accesskey_unused", + "iam_user_console_access_unused" + ], + "Attributes": [ + { + "Title": "IAM credentials unused disabled", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "AWS IAM users can authenticate and access AWS resources using various types of credentials, including passwords and access keys. To minimize security risks, it is recommended to deactivate or remove any credentials that have been unused for 45 days or more.", + "AdditionalInformation": "Disabling or removing inactive credentials reduces the attack surface and prevents unauthorized access through compromised or forgotten credentials. Unused credentials pose a security risk, as attackers may exploit them if they remain active without regular monitoring. Regularly auditing and revoking stale credentials enhances overall account security.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.1.11", + "Description": "Ensure access keys are rotated every 90 days or less", + "Checks": [ + "iam_rotate_access_key_90_days" + ], + "Attributes": [ + { + "Title": "IAM rotate access keys 90 days", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Access keys consist of an access key ID and a secret access key, which are used to authenticate and sign programmatic requests made to AWS. These keys allow users and applications to interact with AWS services via the AWS Command Line Interface (CLI), AWS SDKs, PowerShell tools, or direct API calls. To maintain security, it is recommended that all access keys be rotated regularly to minimize the risk of unauthorized access.", + "AdditionalInformation": "Regularly rotating access keys reduces the risk of compromised credentials being exploited. If an access key is leaked, cracked, or stolen, rotating it limits the window of opportunity for malicious use. Additionally, rotating keys ensures that inactive or outdated credentials cannot be used for unauthorized access, enhancing overall security and compliance.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "1.1.12", + "Description": "Ensure credentials unused for 90 days or greater are disabled", + "Checks": [ + "iam_user_accesskey_unused", + "iam_user_console_access_unused" + ], + "Attributes": [ + { + "Title": "Credentials unused for 90 days disabled", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "AWS IAM credentials, such as passwords and access keys, grant access to AWS resources. Credentials that remain unused for 90 days or more pose a security risk, as they may belong to inactive users or forgotten accounts. It is recommended to disable or remove IAM credentials that have not been used for 90 days to reduce the risk of unauthorized access.", + "AdditionalInformation": "Disabling unused credentials minimizes the attack surface by ensuring that inactive or abandoned accounts cannot be exploited by attackers. Stale credentials may become a target for brute-force attacks, credential stuffing, or insider threats. Regularly auditing and deactivating unused credentials helps maintain a least privilege security model, reducing the likelihood of unauthorized access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.1.13", + "Description": "Ensure IAM password policy expires passwords within 90 days or less", + "Checks": [ + "iam_password_policy_expires_passwords_within_90_days_or_less" + ], + "Attributes": [ + { + "Title": "IAM password expires within 90 days", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "IAM password policies can be configured to enforce password expiration after a defined period. It is recommended that passwords be set to expire within 90 days or less to ensure users regularly update their credentials. This helps mitigate security risks associated with stale or compromised passwords that remain active for extended periods.", + "AdditionalInformation": "Requiring password expiration within 90 days or less reduces the risk of credential-based attacks, such as brute-force attacks and credential stuffing, by ensuring that old passwords cannot be used indefinitely. If a password has been exposed or compromised without detection, regular expiration limits the window of opportunity for an attacker to exploit it. This policy enforces stronger access control and aligns with industry security best practices.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "1.1.14", + "Description": "Ensure no root account access key exists", + "Checks": [ + "iam_no_root_access_key" + ], + "Attributes": [ + { + "Title": "No root access key", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "The root account in AWS has unrestricted administrative privileges and should be protected with the highest security measures. Access keys provide programmatic access to AWS, but when linked to the root account, they pose a significant security risk. It is recommended that no access keys be associated with the root account, ensuring that all programmatic access is managed through IAM roles and users with least privilege access.", + "AdditionalInformation": "The root account holds the highest level of privileges in an AWS environment. AWS Access Keys enable programmatic access to AWS resources, but when associated with the root account, they pose a significant security risk. It is recommended to remove all access keys linked to the root account to minimize potential attack vectors. Eliminating root access keys reduces the risk of unauthorized access and enforces the use of role-based IAM accounts with least privilege, promoting a more secure and controlled access management approach.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.1", + "Description": "Ensure IAM policies are attached only to groups or roles", + "Checks": [ + "iam_policy_attached_only_to_group_or_roles" + ], + "Attributes": [ + { + "Title": "IAM policies attached to group or roles", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "IAM policies define permissions that control access to AWS resources. To ensure scalability, security, and manageability, it is recommended that IAM policies be attached only to groups or roles rather than individual users. By assigning permissions at the group or role level, organizations can apply consistent security policies and avoid permission sprawl.", + "AdditionalInformation": "Attaching policies to groups or roles simplifies access control, reduces security risks, and improves compliance tracking. This approach prevents overprivileged accounts and ensures a structured, scalable IAM policy framework.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.2", + "Description": "Ensure IAM users receive permissions only through groups", + "Checks": [ + "iam_policy_attached_only_to_group_or_roles" + ], + "Attributes": [ + { + "Title": "IAM users get permissions through groups", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "IAM users gain access to AWS services, functions, and data through IAM policies. There are four ways to assign policies to a user: 1.Inline (User-Specific) Policy – Editing the policy directly within the user’s profile.2.Directly Attached Policy – Assigning a standalone policy to a user.3.Group-Based Policy (Recommended) – Adding the user to an IAM group with an attached policy. 4.Group with Inline Policy – Assigning an inline policy to a group that includes the user.Among these methods, only the third approach (group-based policies) is recommended for security and manageability.", + "AdditionalInformation": "Managing IAM permissions exclusively through groups ensures consistent, scalable, and role-based access control. This approach reduces the risk of excessive privileges, simplifies auditing, and aligns user permissions with organizational roles.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.1", + "Description": "Ensure IAM policies that allow full *:* administrative privileges are not attached", + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges" + ], + "Attributes": [ + { + "Title": "IAM policies with full privileges not attached", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "IAM policies define permissions for users, groups, and roles, controlling access to AWS resources. Following the principle of least privilege, users should be granted only the permissions necessary to perform their tasks. Instead of assigning broad administrative privileges, permissions should be carefully crafted to allow only the required actions.", + "AdditionalInformation": "Starting with minimal permissions and granting additional access as needed is significantly more secure than providing excessive permissions and attempting to restrict them later. Assigning full administrative privileges increases the risk of unauthorized or accidental actions that could compromise AWS resources. IAM policies containing Effect: Allow, Action: , Resource: should be removed to prevent unrestricted access and enforce security best practices.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.3", + "Description": "Ensure a support role has been created to manage incidents with AWS Support", + "Checks": [ + "iam_support_role_created" + ], + "Attributes": [ + { + "Title": "Support role exists to manage incidents", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "AWS offers a Support Center for incident notification, response, technical support, and customer service assistance. To ensure secure and controlled access, an IAM role should be created with a properly assigned policy, allowing only authorized users to manage incidents with AWS Support.", + "AdditionalInformation": "Implementing least privilege access control ensures that only designated users can interact with AWS Support. Assigning an IAM role with a specific policy limits access to only necessary actions, reducing the risk of unauthorized modifications or exposure of sensitive account information.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.4", + "Description": "Ensure IAM instance roles are used for AWS resource access from instances", + "Checks": [ + "ec2_instance_profile_attached" + ], + "Attributes": [ + { + "Title": "Roles used for resource access from instances", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "AWS instances can access AWS resources either by embedding access keys in API calls or by assigning an IAM role with the necessary permissions. Using IAM roles ensures secure, controlled access without hardcoding credentials.", + "AdditionalInformation": "IAM roles eliminate the risks associated with hardcoded credentials, reducing exposure to external threats. Unlike access keys, which can be used outside AWS if compromised, IAM roles require an attacker to maintain control of an instance to exploit privileges. Additionally, IAM roles simplify credential management by ensuring permissions are automatically updated without the need for manual key rotation.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.3", + "Description": "Ensure that all expired SSL/TLS certificates stored in AWS IAM are removed", + "Checks": [ + "iam_no_expired_server_certificates_stored" + ], + "Attributes": [ + { + "Title": "Expired SSL/TLS certificates removed", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "To enable HTTPS connections for applications and websites hosted on AWS, an SSL/TLS server certificate is required. AWS provides two options for managing certificates: AWS Certificate Manager (ACM) – The preferred method for managing SSL/TLS certificates, automating renewals and deployment. IAM Certificate Storage – Used only when deploying SSL/TLS certificates in regions not supported by ACM. IAM securely encrypts private keys and stores them, but certificates must be obtained from an external provider. ACM certificates cannot be uploaded to IAM, and IAM certificates cannot be managed from the IAM Console.", + "AdditionalInformation": "Removing expired SSL/TLS certificates prevents the accidental deployment of invalid certificates, which could cause service disruptions, security warnings, and loss of credibility for applications using AWS services like Elastic Load Balancer (ELB). As a best practice, expired certificates should be deleted to maintain a secure and trusted application environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.5", + "Description": "Avoid the use of the 'root' account", + "Checks": [ + "iam_avoid_root_usage" + ], + "Attributes": [ + { + "Title": "Don't use root account", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "The root account in AWS has unrestricted administrative privileges and should be used only for initial account setup and emergency scenarios. Regular operations should be performed using IAM users or roles with least privilege access to minimize security risks.", + "AdditionalInformation": "Using the root account increases the risk of unauthorized access, accidental misconfigurations, and privilege misuse. By restricting root account usage and delegating tasks to IAM users or roles, organizations can enforce better access control, auditing, and security best practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.6", + "Description": "Ensure that IAM Access Analyzer is enabled for all regions", + "Checks": [ + "accessanalyzer_enabled" + ], + "Attributes": [ + { + "Title": "Access Analyzer enabled", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Enable IAM Access Analyzer for all AWS regions to monitor IAM policies and identify resources with unintended external access. IAM Access Analyzer, introduced at AWS re:Invent 2019, scans resource-based policies and provides visibility into which resources—such as KMS keys, IAM roles, S3 buckets, Lambda functions, and SQS queues—are accessible by external accounts or federated users. This allows administrators to enforce least privilege access and mitigate unauthorized access risks. IAM Access Analyzer operates within the same AWS region as the resources being analyzed.", + "AdditionalInformation": "IAM Access Analyzer enhances security visibility by detecting AWS resources shared with external entities, helping organizations identify potential security risks and ensure compliance with least privilege principles. It continuously evaluates resource-based policies using logic-based analysis, allowing teams to promptly remediate misconfigurations that could lead to unauthorized access or data exposure.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.7", + "Description": "Ensure IAM users are managed centrally via identity federation or AWS Organizations for multi-account environments", + "Checks": [ + "iam_check_saml_providers_sts" + ], + "Attributes": [ + { + "Title": "IAM users managed centrally or by organizations", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "In multi-account AWS environments, centralizing IAM user management improves control, security, and access management efficiency. Instead of creating separate IAM users in each account, access should be managed through role assumption. This can be achieved using AWS Organizations or federation with an external identity provider (e.g., AWS IAM Identity Center, Okta, or Active Directory).", + "AdditionalInformation": "Centralizing IAM user management into a single identity store simplifies administration, reduces the risk of access misconfigurations, and enforces consistent security policies across all accounts. This approach enhances security, scalability, and compliance while minimizing user duplication and permission errors.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.2", + "Description": "Ensure access to AWSCloudShellFullAccess is restricted", + "Checks": [ + "iam_policy_cloudshell_admin_not_attached" + ], + "Attributes": [ + { + "Title": "AWSCloudShellFullAccess restricted", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "AWS CloudShell provides a managed command-line interface (CLI) for interacting with AWS services. The AWSCloudShellFullAccess IAM policy grants full access to CloudShell, including file upload and download capabilities between a user’s local system and the CloudShell environment. Within CloudShell, users have sudo privileges and unrestricted internet access, making it possible to install software—such as file transfer tools—that could facilitate data movement to external servers.", + "AdditionalInformation": "Access to AWSCloudShellFullAccess should be restricted, as it can serve as a potential data exfiltration vector for malicious or compromised cloud administrators. Granting full permissions to CloudShell increases the risk of unauthorized data transfers outside the AWS environment. AWS provides guidance on creating more restrictive IAM policies to limit file transfer capabilities, reducing security risks.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.3", + "Description": "Ensure no IAM Inline policies allow actions that may lead into Privilege Escalation", + "Checks": [ + "iam_inline_policy_allows_privilege_escalation" + ], + "Attributes": [ + { + "Title": "IAM policy allow privilege escalation", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "IAM inline policies define permissions directly attached to users, groups, or roles, rather than being managed as standalone policies. If improperly configured, these policies can grant actions that enable privilege escalation, allowing users to elevate their access beyond intended permissions. Privilege escalation can occur through misconfigured IAM roles, excessive permissions, or indirect access paths, potentially leading to unauthorized control over AWS resources.", + "AdditionalInformation": "Users with some IAM permissions are allowed to elevate their privileges up to administrator rights.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "4.1.1", + "Description": "Ensure S3 Bucket Policy is set to deny HTTP requests", + "Checks": [ + "s3_bucket_secure_transport_policy" + ], + "Attributes": [ + { + "Title": "S3 bucket deny HTTP requests", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Amazon S3 bucket permissions can be configured using a bucket policy to enforce access restrictions. To enhance security, objects within the bucket should be made accessible only via HTTPS, ensuring encrypted data transmission.", + "AdditionalInformation": "By default, Amazon S3 accepts both HTTP and HTTPS requests, which can expose data to interception. To enforce secure access, HTTP requests should be explicitly denied in the bucket policy. Simply allowing HTTPS without blocking HTTP does not fully comply with security best practices, as unencrypted requests may still be accepted.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.2", + "Description": "Ensure that EC2 Metadata Service only allows IMDSv2", + "Checks": [ + "ec2_instance_imdsv2_enabled" + ], + "Attributes": [ + { + "Title": "EC2 Metadata Service only allows IMDSv2", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "AWS EC2 instances allow users to choose between Instance Metadata Service Version 1 (IMDSv1), which uses a request/response model, or Instance Metadata Service Version 2 (IMDSv2), which uses a session-based approach for enhanced security", + "AdditionalInformation": "Instance metadata refers to the data about an EC2 instance, such as host names, events, and security groups, that is used for managing and configuring the instance. When enabling the Metadata Service, users can opt for either IMDSv1, which operates via a simple request/response model, or IMDSv2, which implements session authentication for additional security. With IMDSv2, each request is secured by session-based authentication, ensuring that all interactions with the instance's metadata and credentials are protected. IMDSv1, on the other hand, may expose instances to Server-Side Request Forgery (SSRF) attacks. To improve security, Amazon recommends using IMDSv2", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.1", + "Description": "Ensure MFA Delete is enabled on S3 buckets", + "Checks": [ + "s3_bucket_no_mfa_delete" + ], + "Attributes": [ + { + "Title": "MFA delete enabled on S3 buckets", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Enabling MFA Delete on a sensitive or classified Amazon S3 bucket adds an extra layer of protection by requiring two-factor authentication for critical actions, such as deleting object versions or changing the bucket’s versioning state.", + "AdditionalInformation": "MFA Delete helps prevent accidental or malicious deletions by requiring an additional authentication step. This mitigates the risk of data loss due to compromised credentials or unauthorized access, ensuring that critical objects remain protected.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.2", + "Description": "Ensure all data in Amazon S3 has been discovered, classified, and secured when necessary", + "Checks": [ + "macie_is_enabled" + ], + "Attributes": [ + { + "Title": "Macie enabled", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon S3 buckets may store sensitive data that needs to be discovered, classified, monitored, and protected to maintain security and compliance. Amazon Macie, along with third-party tools, can automatically inventory S3 buckets and identify sensitive data at scale.", + "AdditionalInformation": "Using automated data discovery and classification tools, such as Amazon Macie, enhances security by continuously monitoring S3 buckets for sensitive information. Macie leverages machine learning and pattern matching to detect and protect critical data, reducing the risk of data leaks and unauthorized access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.1", + "Description": "Ensure the default security group of every VPC restricts all traffic", + "Checks": [ + "ec2_securitygroup_default_restrict_traffic" + ], + "Attributes": [ + { + "Title": "Default SG of VPC restrict all traffic", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Each Amazon VPC includes a default security group that initially denies all inbound traffic, allows all outbound traffic, and permits unrestricted communication between instances within the group. If no security group is specified when launching an instance, it is automatically assigned to this default security group. Since security groups control stateful ingress and egress traffic, it is recommended to restrict all inbound and outbound traffic in the default security group.", + "AdditionalInformation": "Restricting all traffic in the default security group enforces least privilege access by ensuring that AWS resources are explicitly assigned to well-defined security groups. This approach reduces unintended exposure, improves network segmentation, and promotes secure resource placement within AWS environments.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "2.1.2", + "Description": "Ensure routing tables for VPC peering are least access", + "Checks": [ + "vpc_peering_routing_tables_with_least_privilege" + ], + "Attributes": [ + { + "Title": "Routing tables for VPC least access", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "After establishing a VPC peering connection, routing tables must be updated to enable communication between the peered VPCs. Routes can be configured with granular specificity, allowing connections to be restricted to a single host or a specific subnet within the peered VPC.", + "AdditionalInformation": "Defining highly specific routes in VPC peering connections enhances security by limiting access to only the necessary resources. This minimizes the potential impact of a security breach, ensuring that resources outside the defined routes remain inaccessible, reducing the risk of lateral movement within the network.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.3", + "Description": "Ensure no Network ACLs allow ingress from 0.0.0.0/0 to remote server administration ports", + "Checks": [ + "ec2_networkacl_allow_ingress_any_port", + "ec2_networkacl_allow_ingress_tcp_port_22", + "ec2_networkacl_allow_ingress_tcp_port_3389" + ], + "Attributes": [ + { + "Title": "No Network ACLs allow ingress from 0.0.0.0/0 to remote server administration ports", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Network Access Control Lists (NACLs) provide stateless filtering of ingress and egress traffic to AWS resources. It is recommended that NACLs do not allow unrestricted inbound access to remote administration ports, such as SSH (port 22) and RDP (port 3389), over TCP (6), UDP (17), or ALL (-1) protocols to prevent unauthorized access.", + "AdditionalInformation": "Exposing remote server administration ports (e.g., SSH on 22 and RDP on 3389) to the public internet increases the attack surface, making resources more vulnerable to brute-force attacks and unauthorized access. Restricting inbound access to these ports helps reduce security risks and limit potential exploitation.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.4", + "Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to remote server administration ports", + "Checks": [ + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389" + ], + "Attributes": [ + { + "Title": "No SG allow ingress from 0.0.0.0/0 to remote server administration ports", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Security groups enforce stateful filtering of ingress and egress traffic to AWS resources. To enhance security, no security group should allow unrestricted inbound access to remote administration ports, such as SSH (port 22) and RDP (port 3389), over TCP (6), UDP (17), or ALL (-1) protocols.", + "AdditionalInformation": "Exposing remote administration ports to the public internet significantly increases the attack surface, making resources more vulnerable to brute-force attacks, exploitation, and unauthorized access. Restricting ingress traffic to these ports helps reduce security risks and prevent potential system compromises.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "2.2.3", + "Description": "Ensure EBS snapshots are not publicly accessible", + "Checks": [ + "ec2_ebs_public_snapshot" + ], + "Attributes": [ + { + "Title": "EBS snapshots nor publicy accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Elastic Block Store (EBS) snapshots contain backups of EC2 volumes, which may include sensitive data such as credentials, application configurations, or customer information. EBS snapshots should never be publicly accessible to prevent unauthorized access and data exposure. By default, snapshots are private, but they can be manually shared with other AWS accounts or made public, which poses a significant security risk if misconfigured.", + "AdditionalInformation": "Exposing EBS snapshots publicly increases the risk of data breaches, unauthorized access, and compliance violations. Attackers can scan for publicly accessible snapshots and extract sensitive information. To prevent data leaks, snapshots should be restricted to specific AWS accounts or kept private unless explicitly needed for sharing. Implementing proper access controls helps protect critical data and maintain compliance with security best practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.2.1", + "Description": "Ensure EBS volume encryption is enabled in all regions", + "Checks": [ + "ec2_ebs_volume_encryption" + ], + "Attributes": [ + { + "Title": "EBS volume encryption", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Amazon Elastic Compute Cloud (EC2) supports encryption at rest for Elastic Block Store (EBS) volumes, ensuring that stored data remains protected. While EBS encryption is disabled by default, organizations can enforce automatic encryption of newly created volumes to enhance data security and compliance.", + "AdditionalInformation": "Enforcing EBS volume encryption reduces the risk of data exposure, unauthorized access, and compliance violations. If encryption remains intact, even if storage is compromised, data remains unreadable to unauthorized users. Encrypting data at rest ensures that sensitive information is protected against accidental disclosure, insider threats, and external attacks.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "4.2.2", + "Description": "Ensure that encryption-at-rest is enabled for RDS instances", + "Checks": [ + "rds_instance_storage_encrypted" + ], + "Attributes": [ + { + "Title": "RDS instances encryption at rest enabled", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Amazon Relational Database Service (RDS) supports encryption at rest using the industry-standard AES-256 encryption algorithm to secure database instances and their associated storage. Once enabled, RDS encryption automatically handles access authentication and decryption, ensuring secure data storage with minimal performance impact.", + "AdditionalInformation": "Databases often contain sensitive and business-critical information, making encryption essential to protect against unauthorized access and data breaches. Enabling RDS encryption ensures that underlying storage, automated backups, read replicas, and snapshots are all encrypted, preventing accidental or malicious data exposure while maintaining compliance with security best practices.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.3.1", + "Description": "Ensure SNS topics do not allow global send or subscribe", + "Checks": [ + "sns_topics_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "SNS topics do not allow global send or subscribe", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Simple Notification Service (SNS) topics enable messaging between AWS services, applications, and users. By default, SNS topics should be restricted to trusted AWS accounts or IAM roles to prevent unauthorized access. Allowing global send (sns:Publish) or subscribe (sns:Subscribe) permissions means any AWS account or unauthenticated entity could send messages or subscribe to the topic, potentially leading to spam, data leaks, or misuse of notifications.", + "AdditionalInformation": "SNS topics with global send or subscribe permissions expose AWS environments to unauthorized message injection, data exfiltration, and Denial-of-Service (DoS) attacks. An attacker could flood an SNS topic with malicious or fraudulent messages, leading to unexpected charges or service disruptions. Restricting access ensures that only authorized AWS accounts, applications, or IAM roles can send and receive messages, reducing security risks and protecting system integrity.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.4", + "Description": "Ensure RDS snapshots are not publicly accessible", + "Checks": [ + "rds_instance_no_public_access" + ], + "Attributes": [ + { + "Title": "RDS snapshots not publicy accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Relational Database Service (RDS) snapshots store backups of database instances, potentially containing sensitive data such as customer records, credentials, and application configurations. By default, RDS snapshots are private, but they can be shared with other AWS accounts or made public, which can lead to data exposure if misconfigured. To prevent unauthorized access, RDS snapshots should never be publicly accessible unless explicitly required and secured.", + "AdditionalInformation": "Publicly accessible RDS snapshots create a serious security risk, as anyone can copy the snapshot and restore the database, exposing sensitive information. Attackers actively scan for publicly available snapshots to extract credentials, personally identifiable information (PII), or business-critical data. To prevent unauthorized access and data leaks, RDS snapshots should remain private or restricted to trusted AWS accounts following the principle of least privilege.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.5", + "Description": "Ensure the S3 bucket CloudTrail logs is not publicly accessible", + "Checks": [ + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "S3 bucket CloudTrail logs is not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "AWS CloudTrail logs record account activity, including API calls, user actions, and resource modifications, making them critical for security monitoring and compliance auditing. These logs are typically stored in an Amazon S3 bucket for long-term retention and analysis. To protect sensitive security data, the S3 bucket storing CloudTrail logs should never be publicly accessible.", + "AdditionalInformation": "If the S3 bucket containing CloudTrail logs is publicly accessible, unauthorized users could access sensitive security information, including API calls, IAM activity, and infrastructure changes. Exposing CloudTrail logs can help attackers reconstruct system activity, identify vulnerabilities, and plan targeted attacks. To prevent data leaks and unauthorized access, CloudTrail log buckets should be restricted using IAM policies, bucket policies, and S3 Block Public Access settings.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.6", + "Description": "Ensure Redshift clusters do not have a public endpoint", + "Checks": [ + "redshift_cluster_public_access" + ], + "Attributes": [ + { + "Title": "Redshift clusters dont have a public endpoint", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Redshift clusters store and process large-scale data for analytics and business intelligence workloads. By default, Redshift clusters can be configured with a public endpoint, making them accessible from the internet. To minimize security risks, Redshift clusters should be restricted to private networks and should not have a public endpoint unless absolutely necessary and properly secured.", + "AdditionalInformation": "Exposing a Redshift cluster to the public internet increases the risk of unauthorized access, data breaches, and cyberattacks. Attackers could attempt brute-force login attempts, exploit misconfigurations, or access sensitive business data. Keeping Redshift clusters within private subnets and restricting access via security groups, VPC settings, and IAM policies ensures that only trusted networks and users can connect, reducing the attack surface and enhancing data security.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.5", + "Description": "Ensure ApiGateway endpoint is not public", + "Checks": [ + "apigateway_restapi_public" + ], + "Attributes": [ + { + "Title": "ApiGateway endpoint is public", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "AWS API Gateway allows developers to create, deploy, and manage APIs that connect applications to backend services. By default, API Gateway endpoints can be publicly accessible, meaning they can be invoked from anywhere on the internet. To enhance security, API Gateway endpoints should be restricted to private networks using VPC links, private API settings, or access control mechanisms to ensure that only authorized entities can interact with the API.", + "AdditionalInformation": "Publicly accessible API Gateway endpoints can expose backend services to unauthorized access, data leaks, and potential exploitation. Attackers may attempt brute-force authentication, injection attacks, or abuse API functionality if access is not properly restricted. To reduce the attack surface, API Gateway endpoints should be limited to internal use or protected with authentication, IAM permissions, WAF rules, or private VPC access to ensure only trusted users and systems can invoke the API.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.2", + "Description": "Ensure that amazon EC2 instances launched using Auto Scaling group launch configurations do not have Public IP addresses", + "Checks": [ + "autoscaling_group_launch_configuration_no_public_ip" + ], + "Attributes": [ + { + "Title": "EC2 instances launched using autoscaling group launch configurations do not have Public IP addresses", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon EC2 instances launched via Auto Scaling groups can automatically scale workloads based on demand. By default, instances can be assigned public IP addresses, making them accessible from the internet. To enhance security, EC2 instances in Auto Scaling group launch configurations should not have public IP addresses, ensuring they remain within a private network and are only accessible through secure channels such as bastion hosts or VPN connections.", + "AdditionalInformation": "Assigning public IP addresses to Auto Scaling group instances increases the risk of unauthorized access, brute-force attacks, and potential exploitation. Publicly accessible instances can become targets for malicious actors, leading to data breaches or service disruptions. By restricting public IP addresses, organizations can enforce network segmentation, ensuring that EC2 instances are accessed securely via private networks, VPNs, or load balancers.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.3", + "Description": "Ensure that lambda functions have not resource-based policy set as Public", + "Checks": [ + "awslambda_function_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "Lambda functions have not resource-based policy set as Public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Lambda functions allow running code without managing servers. Lambda supports resource-based policies that define who can invoke the function. If a Lambda function’s resource-based policy allows public access, it can be triggered by anyone on the internet, posing a significant security risk. To prevent unauthorized execution, Lambda functions should not be publicly accessible unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible Lambda functions can be abused for unauthorized execution, leading to service disruptions, data exfiltration, or increased AWS costs due to excessive invocations. Attackers could exploit misconfigured functions to perform malicious actions, extract sensitive data, or abuse compute resources. To reduce security risks, Lambda functions should only be accessible to specific IAM roles, AWS services, or trusted accounts, enforcing least privilege access and maintaining secure function execution.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.4", + "Description": "Ensure that Lambda have not public URL Function", + "Checks": [ + "awslambda_function_url_public" + ], + "Attributes": [ + { + "Title": "Lambda have not public URL Function", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Lambda function URLs provide a built-in HTTPS endpoint that allows functions to be invoked directly via HTTP requests. By default, Lambda function URLs can be publicly accessible, meaning anyone on the internet can invoke the function if proper access controls are not enforced. To minimize security risks, Lambda function URLs should not be publicly accessible unless explicitly required and properly restricted.", + "AdditionalInformation": "Exposing Lambda function URLs to the public internet increases the risk of unauthorized access, API abuse, and potential exploitation. Attackers may invoke functions maliciously, leading to data leaks, unauthorized operations, increased costs, or denial-of-service (DoS) attacks. To enhance security, Lambda function URLs should be restricted to specific IAM roles, AWS services, or trusted clients, ensuring that only authorized users can trigger the function.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.5", + "Description": "Ensure DMS instances are not publicly accessible", + "Checks": [ + "dms_instance_no_public_access" + ], + "Attributes": [ + { + "Title": "DMS instances are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Database Migration Service (DMS) instances facilitate data migration between databases across on-premises and cloud environments. By default, DMS instances can be configured with publicly accessible endpoints, making them reachable from the internet. To enhance security and prevent unauthorized access, DMS instances should not be publicly accessible unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible DMS instances increase the risk of unauthorized access, data interception, and potential exploitation. Attackers could target exposed instances to steal or manipulate sensitive data during migration. Restricting public access ensures data migrations remain secure, limiting access to trusted networks, private VPCs, and authorized IAM roles, thereby reducing the attack surface and ensuring compliance with security best practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.7", + "Description": "Ensure DocumentDB manual cluster snapshot is not public", + "Checks": [ + "documentdb_cluster_public_snapshot" + ], + "Attributes": [ + { + "Title": "DocumentDB manual cluster snapshot is not public", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "AWS DocumentDB manual cluster snapshots store backups of DocumentDB clusters, containing sensitive database information such as application data, configurations, and credentials. By default, snapshots are private, but they can be manually shared or made public, which poses a significant security risk. To prevent unauthorized access, DocumentDB manual cluster snapshots should never be publicly accessible unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible DocumentDB snapshots expose critical database information, increasing the risk of data breaches, unauthorized access, and compliance violations. Attackers could restore the snapshot in their own AWS account and gain full access to the database content. To protect sensitive data, DocumentDB snapshots should only be shared with specific AWS accounts or remain private, following least privilege principles and AWS security best practices.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.6", + "Description": "Ensure there are no EC2 AMIs set as Public", + "Checks": [ + "ec2_ami_public" + ], + "Attributes": [ + { + "Title": "No EC2 AMIs set as Public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon EC2 Amazon Machine Images (AMIs) contain pre-configured operating system and application environments that can be used to launch new EC2 instances. By default, AMIs are private, but they can be manually shared or made public, which poses a security risk if sensitive data or proprietary configurations are exposed. To prevent unauthorized access and data leaks, EC2 AMIs should not be set as public unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible EC2 AMIs increase the risk of data exposure, unauthorized access, and compliance violations. Attackers could copy, analyze, or exploit public AMIs to extract sensitive credentials, misconfigurations, or proprietary software. Keeping AMIs private or shared only with specific AWS accounts ensures that only trusted users or teams can access and launch instances from them, reducing security risks and preventing unintended data exposure.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.8", + "Description": "Ensure that public access to EBS snapshots is disabled", + "Checks": [ + "ec2_ebs_snapshot_account_block_public_access" + ], + "Attributes": [ + { + "Title": "Public access to EBS snapshots is disabled", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Elastic Block Store (EBS) snapshots are backups of EC2 volumes that may contain sensitive data, such as credentials, application configurations, and customer records. By default, EBS snapshots are private, but they can be manually shared or made public, allowing anyone to copy or restore them. To prevent unauthorized access and data exposure, public access to EBS snapshots should always be disabled.", + "AdditionalInformation": "Publicly accessible EBS snapshots pose a significant security risk, as attackers can restore and extract sensitive data if a snapshot is exposed. Misconfigured public snapshots have led to data breaches and compliance violations in the past. To mitigate this risk, EBS snapshots should be kept private or explicitly shared only with trusted AWS accounts, following least privilege principles to protect critical data and maintain security compliance.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.6", + "Description": "Ensure that ec2 common ports from instances are not internet-exposed", + "Checks": [ + "ec2_instance_port_cassandra_exposed_to_internet", + "ec2_instance_port_cifs_exposed_to_internet", + "ec2_instance_port_elasticsearch_kibana_exposed_to_internet", + "ec2_instance_port_ftp_exposed_to_internet", + "ec2_instance_port_kafka_exposed_to_internet", + "ec2_instance_port_kerberos_exposed_to_internet", + "ec2_instance_port_ldap_exposed_to_internet", + "ec2_instance_port_memcached_exposed_to_internet", + "ec2_instance_port_mongodb_exposed_to_internet", + "ec2_instance_port_mysql_exposed_to_internet", + "ec2_instance_port_oracle_exposed_to_internet", + "ec2_instance_port_postgresql_exposed_to_internet", + "ec2_instance_port_rdp_exposed_to_internet", + "ec2_instance_port_redis_exposed_to_internet", + "ec2_instance_port_sqlserver_exposed_to_internet", + "ec2_instance_port_ssh_exposed_to_internet", + "ec2_instance_port_telnet_exposed_to_internet" + ], + "Attributes": [ + { + "Title": "Common ports from instances are not exposed", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Amazon EC2 instances can run various services that communicate over common ports such as 22 (SSH), 3389 (RDP), 80 (HTTP), and 443 (HTTPS) (and more). If these ports are open to the internet, attackers can attempt unauthorized access, brute-force attacks, or exploit known vulnerabilities. To reduce security risks, EC2 instances should be configured so that common ports are not exposed to the public internet, unless explicitly required and properly secured.", + "AdditionalInformation": "Exposing common ports directly to the internet increases the attack surface and risks unauthorized access or system compromise. Attackers frequently scan for open ports to target misconfigured or unpatched services. To enhance security, access to EC2 common ports should be restricted using security groups, network ACLs, and VPC configurations, ensuring that only trusted networks and users can connect.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.7", + "Description": "Ensure that ec2 security groups do not allow ingress from internet to common ports", + "Checks": [ + "ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_cassandra_7199_9160_8888", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_elasticsearch_kibana_9200_9300_5601", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_kafka_9092", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_memcached_11211", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_mysql_3306", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_oracle_1521_2483", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_postgres_5432", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_redis_6379", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" + ], + "Attributes": [ + { + "Title": "Common ports from security groups do not allow ingress traffic", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Amazon EC2 security groups act as virtual firewalls, controlling inbound and outbound traffic to instances. If a security group allows ingress (incoming traffic) from the internet (0.0.0.0/0 or ::/0) to common ports such as 22 (SSH), 3389 (RDP), 80 (HTTP), or 443 (HTTPS) (and more), it creates a significant security risk. To minimize exposure, security groups should be configured to restrict ingress access to these ports to only trusted IP addresses or internal networks.", + "AdditionalInformation": "Allowing unrestricted inbound traffic to common ports increases the risk of brute-force attacks, unauthorized access, and exploitation of vulnerabilities. Attackers actively scan for open ports on public-facing EC2 instances to gain unauthorized control. To reduce security risks, ingress rules should be restricted using least privilege principles, IP whitelisting, VPN access, or bastion hosts, ensuring that only authorized users and networks can connect.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "2.3.7", + "Description": "Ensure there are no ECR repositories set as Public", + "Checks": [ + "ecr_repositories_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "No ECR repositories set as Public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic Container Registry (ECR) repositories store and manage container images for deployment in AWS services. By default, ECR repositories are private, but they can be manually configured as public, allowing anyone to pull container images. To prevent unauthorized access and potential security risks, ECR repositories should not be set as public unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible ECR repositories expose container images to unauthorized users, increasing the risk of intellectual property theft, malware injection, or unauthorized use of containerized applications. Attackers could analyze public images for vulnerabilities or use misconfigured images for malicious purposes. To mitigate this risk, ECR repositories should remain private or be explicitly shared with trusted AWS accounts, ensuring secure access and compliance with best practices.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.8", + "Description": "Ensure ECS services do not assign public IPs automatically", + "Checks": [ + "ecs_service_no_assign_public_ip" + ], + "Attributes": [ + { + "Title": "No ECS services assign public IPs automatically", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic Container Service (ECS) allows running containerized applications on AWS. By default, ECS services can be configured to assign public IP addresses to tasks or services, making them directly accessible from the internet. To enhance security, ECS services should be configured not to automatically assign public IPs, ensuring they remain within a private network and are accessed securely through internal load balancers, VPC peering, or private endpoints.", + "AdditionalInformation": "Automatically assigning public IPs to ECS services exposes them to the internet, increasing the risk of unauthorized access, brute-force attacks, and data breaches. Attackers could target publicly exposed containers, exploit vulnerabilities, or disrupt services. To mitigate these risks, ECS services should be restricted to private subnets and accessed through secure networking configurations, such as AWS PrivateLink, VPNs, or internal ALBs.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.9", + "Description": "Ensure ECS task sets do not automatically assign public IP addresses", + "Checks": [ + "ecs_task_set_no_assign_public_ip" + ], + "Attributes": [ + { + "Title": "No ECS tasks sets assign public IPs autocatically", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic Container Service (ECS) task sets manage multiple versions of a service during deployments. By default, ECS task sets can be configured to automatically assign public IP addresses, making them directly accessible from the internet. To enhance security, ECS task sets should be restricted to private subnets and should not automatically receive public IP addresses unless explicitly required and properly secured.", + "AdditionalInformation": "Automatically assigning public IPs to ECS task sets increases the risk of unauthorized access, cyberattacks, and data exposure. Publicly exposed tasks can be targeted by attackers, leading to service disruptions or exploitation of vulnerabilities. To mitigate these risks, ECS task sets should be restricted to private networking environments, accessed only through internal load balancers, VPC endpoints, or secure VPN connections, ensuring controlled and secure communication.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.9", + "Description": "Ensure EFS mount targets are not publicly accessible", + "Checks": [ + "efs_mount_target_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "No EFS mount targets are publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Elastic File System (EFS) provides scalable, shared file storage for AWS services. EFS mount targets allow instances to connect to the file system within a VPC. By default, EFS mount targets can be configured with public accessibility, making them reachable from the internet. To enhance security, EFS mount targets should be restricted to private networks and should not be publicly accessible unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible EFS mount targets expose stored data to unauthorized access, cyberattacks, and data breaches. Attackers could exploit misconfigured security groups or network ACLs to access or modify files. To reduce security risks, EFS mount targets should be restricted to private subnets, with access limited to trusted VPCs, security groups, and IAM roles, ensuring secure file storage and controlled access.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.10", + "Description": "Ensure that EFS do not have policies which allow access to any client within the VPC", + "Checks": [ + "efs_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "No EFS have policies which allow access to any client within the VPC", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon Elastic File System (EFS) provides shared storage that can be accessed by multiple EC2 instances and services within a VPC. EFS access is controlled through resource-based policies that define which clients can connect. If an EFS policy allows access to any client within the VPC, it increases the risk of unauthorized access and data exposure. To enhance security, EFS policies should be restricted to specific IAM roles, security groups, or trusted resources instead of granting broad access to all VPC clients.", + "AdditionalInformation": "Allowing any client within a VPC to access an EFS file system increases the risk of data leaks, accidental modifications, or unauthorized access by compromised instances or misconfigured services. To minimize exposure, EFS policies should enforce least privilege access, restricting permissions to specific instances, roles, or users that require access, ensuring secure file storage and controlled data access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.8", + "Description": "Ensure EKS cluster Network Policy is Enabled and Set as Appropriate", + "Checks": [ + "eks_cluster_network_policy_enabled" + ], + "Attributes": [ + { + "Title": "EKS cluster Network Policy is Enabled and Set as Appropriate", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "A Network Policy defines how network traffic is controlled and restricted between workloads within a cloud environment. Enforcing network policies ensures that only authorized communication occurs between services, reducing the risk of unauthorized access and lateral movement. It is recommended to enable Network Policies and configure them appropriately to enforce least privilege access and secure communication between workloads.", + "AdditionalInformation": "Without properly configured Network Policies, workloads may be exposed to unnecessary or unauthorized network traffic, increasing the risk of data leaks, exploitation, or lateral movement by attackers. By enabling and enforcing Network Policies, organizations can limit communication between workloads, ensuring that only approved and necessary network interactions are allowed, minimizing the attack surface and enhancing overall security.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.9", + "Description": "Ensure EKS Clusters are not publicly accessible", + "Checks": [ + "eks_cluster_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "EKS Clusters are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Amazon Elastic Kubernetes Service (EKS) clusters manage containerized applications and can be configured with either private or public access. If an EKS cluster is publicly accessible, it means that the Kubernetes API endpoint can be reached from the internet, increasing the risk of unauthorized access and attacks. To enhance security, EKS clusters should be restricted to private networks and accessed only through secure VPNs, VPC peering, or AWS PrivateLink.", + "AdditionalInformation": "Exposing an EKS cluster to the public internet increases the risk of brute-force attacks, credential theft, and unauthorized access to Kubernetes workloads. Attackers could exploit misconfigured RBAC policies or API vulnerabilities to gain control over the cluster. To reduce security risks, EKS clusters should be configured with private endpoints, ensuring that only trusted networks and IAM-authenticated users can manage Kubernetes resources.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.1.10", + "Description": "Ensure EKS Clusters are created with Private Nodes", + "Checks": [ + "eks_cluster_private_nodes_enabled" + ], + "Attributes": [ + { + "Title": "EKS Clusters are created with Private Nodes", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Amazon Elastic Kubernetes Service (EKS) clusters run workloads on worker nodes, which can be either public or private. If EKS clusters are created with public nodes, these nodes are assigned public IP addresses, making them accessible from the internet, which increases the risk of unauthorized access and potential attacks. To enhance security, EKS clusters should be created with private nodes that operate within private subnets and are only accessible through secured networking configurations such as VPNs, VPC peering, or AWS PrivateLink.", + "AdditionalInformation": "Using public nodes in EKS exposes Kubernetes workloads to the internet, increasing the risk of unauthorized access, lateral movement, and potential exploitation. Attackers can target misconfigured workloads, open services, or unsecured API endpoints. By creating EKS clusters with private nodes, organizations can restrict access, limit exposure to public threats, and enforce network segmentation, ensuring that workloads remain secure and isolated within a private VPC environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.11", + "Description": "Ensure Elasticache Cluster is not using a public subnet", + "Checks": [ + "elasticache_cluster_uses_public_subnet" + ], + "Attributes": [ + { + "Title": "Elasticache Cluster is not using a public subnet", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon ElastiCache provides in-memory caching services using Redis and Memcached. By default, ElastiCache clusters can be deployed in either public or private subnets. If an ElastiCache cluster is placed in a public subnet, it becomes accessible from the internet, which significantly increases the risk of unauthorized access and data breaches. To enhance security, ElastiCache clusters should only be deployed in private subnets, ensuring restricted access within a VPC.", + "AdditionalInformation": "Deploying an ElastiCache cluster in a public subnet exposes it to external threats, such as unauthorized access, brute-force attacks, and potential data exfiltration. Attackers could exploit misconfigurations to access cached data or disrupt services. By restricting ElastiCache clusters to private subnets, organizations can limit access to trusted resources, enforce VPC security controls, and reduce the attack surface, ensuring secure and efficient caching operations.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.10", + "Description": "Ensure no Elastic Load Balancers are facing internet", + "Checks": [ + "elbv2_internet_facing" + ], + "Attributes": [ + { + "Title": "No Elastic Load Balancers are facing internet", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic Load Balancers (ELBs) distribute incoming traffic across multiple targets, such as EC2 instances, containers, and Lambda functions. By default, ELBs can be configured as either internet-facing or internal (private). If an ELB is publicly accessible, it exposes backend services to the internet, increasing the risk of unauthorized access and attacks. To enhance security, ELBs should be restricted to private networks unless explicitly required and properly secured.", + "AdditionalInformation": "Publicly accessible Elastic Load Balancers can serve as entry points for unauthorized traffic, brute-force attacks, and potential data breaches. Attackers may exploit misconfigured security groups, open ports, or exposed application endpoints behind the load balancer. To reduce security risks, ELBs should be configured as internal (private), allowing access only from trusted networks, VPNs, or specific VPCs, ensuring that backend services remain protected and isolated from external threats.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.11", + "Description": "Ensure EMR Account Public Access Block enabled", + "Checks": [ + "emr_cluster_account_public_block_enabled" + ], + "Attributes": [ + { + "Title": "EMR Account Public Access Block enabled", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic MapReduce (EMR) is a managed big data processing service that can access S3, EC2, and other AWS resources. The EMR Account Public Access Block setting helps prevent public access to EMR resources, such as data stored in S3 buckets. If this setting is not enabled, there is a risk that EMR-related data and configurations could be exposed to the public, leading to unauthorized access or data breaches. To enhance security, the Public Access Block should be enabled for the EMR account.", + "AdditionalInformation": "Allowing public access to EMR resources increases the risk of data leaks, unauthorized access, and compliance violations. Attackers could exploit misconfigured policies or publicly accessible S3 buckets to access sensitive data processed by EMR. Enabling EMR Account Public Access Block ensures that S3 data and other EMR-related resources cannot be accessed publicly, reducing exposure and maintaining strong access controls in AWS.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.12", + "Description": "Ensure EMR cluster is not publicly accessible", + "Checks": [ + "emr_cluster_publicly_accesible" + ], + "Attributes": [ + { + "Title": "EMR cluster is not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Elastic MapReduce (EMR) is a managed service for processing big data workloads using Apache Spark, Hadoop, and other frameworks. By default, EMR clusters can be configured with public or private access. If an EMR cluster is publicly accessible, it exposes data processing nodes and services to the internet, increasing the risk of unauthorized access and potential exploitation. To enhance security, EMR clusters should only be deployed in private subnets and restricted to trusted networks.", + "AdditionalInformation": "Publicly accessible EMR clusters increase the risk of data breaches, unauthorized access, and attacks on running workloads. Malicious actors could exploit misconfigured security groups, open ports, or weak authentication settings to compromise the cluster. To reduce exposure, EMR clusters should be placed in private subnets, restricted using VPC security controls, IAM permissions, and firewall rules, ensuring secure data processing and access management.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.13", + "Description": "Ensure that your AWS EventBridge event bus is not exposed to everyone", + "Checks": [ + "eventbridge_bus_exposed" + ], + "Attributes": [ + { + "Title": "AWS EventBridge event bus is not exposed to everyone", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS EventBridge is a serverless event bus service that enables communication between AWS services, third-party applications, and custom event sources. By default, EventBridge event buses can be configured to allow events from any AWS account or external source. If an event bus is exposed to everyone, unauthorized entities could send events to your environment, potentially leading to security risks, data injection attacks, or service disruptions. To enhance security, event buses should be restricted to specific AWS accounts, services, or trusted IAM roles.", + "AdditionalInformation": "Allowing unrestricted access to an EventBridge event bus increases the risk of malicious event injection, unauthorized access, and data manipulation. Attackers could flood the event bus with malicious events, leading to unexpected behavior, security breaches, or excessive AWS costs. To reduce exposure, event buses should be secured using IAM policies and resource-based permissions, ensuring that only trusted AWS services and accounts can send or receive events.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.12", + "Description": "Ensure S3 Glacier vaults have not policies which allow access to everyone", + "Checks": [ + "glacier_vaults_policy_public_access" + ], + "Attributes": [ + { + "Title": "S3 Glacier vaults have not policies which allow access to everyone", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon S3 Glacier provides low-cost, long-term storage for archival data. Glacier vaults can be configured with resource-based policies that control access. If a Glacier vault policy allows access to everyone, unauthorized users could retrieve or delete archived data, leading to data exposure or loss. To enhance security, Glacier vault policies should be restricted to specific AWS accounts, IAM roles, or trusted entities, ensuring only authorized users can access or manage archived data.", + "AdditionalInformation": "Allowing public access to S3 Glacier vaults poses a significant security risk, increasing the chance of data breaches, unauthorized deletions, or compliance violations. Attackers could restore and download sensitive archived data if the vault is misconfigured. To prevent unauthorized access, Glacier vaults should have strict access controls, using IAM policies, encryption, and resource-based permissions, ensuring that only trusted users and systems can interact with archived data.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.13", + "Description": "Ensure Glue Data Catalogs are not publicly accessible", + "Checks": [ + "glue_data_catalogs_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "Glue Data Catalogs are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "AWS Glue Data Catalog is a centralized metadata repository used to store and manage schema information for data lakes and analytics workflows. By default, Glue Data Catalogs can be configured to allow public access, which poses a significant security risk if sensitive metadata is exposed. To enhance security, Glue Data Catalogs should be restricted to specific AWS accounts, IAM roles, or trusted services, ensuring that only authorized users can access or modify catalog information.", + "AdditionalInformation": "Allowing public access to Glue Data Catalogs increases the risk of unauthorized access, data leaks, and compliance violations. Attackers could gain insights into an organization’s data structure or modify catalog entries, leading to potential data corruption or unauthorized data exposure. To reduce security risks, Glue Data Catalogs should be secured using IAM policies, resource-based permissions, and AWS Lake Formation, ensuring that only trusted accounts and services can interact with metadata.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.14", + "Description": "Ensure Kafka Cluster is not exposed to the public", + "Checks": [ + "kafka_cluster_is_public" + ], + "Attributes": [ + { + "Title": "Kafka Cluster is not exposed to the public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Managed Streaming for Apache Kafka (MSK) allows organizations to build and manage real-time data streaming applications. If a Kafka cluster is publicly accessible, it exposes data streams, configurations, and messaging topics to the internet, increasing the risk of unauthorized access, data interception, and service disruptions. To enhance security, Kafka clusters should be restricted to private networks, ensuring that only trusted AWS resources, VPCs, and IAM-authenticated users can interact with the service.", + "AdditionalInformation": "Exposing a Kafka cluster to the public internet creates significant security risks, including unauthorized data ingestion, data leaks, and message tampering. Attackers could consume, modify, or inject malicious data into Kafka topics, disrupting real-time analytics and application workflows. To mitigate these risks, Kafka clusters should be deployed in private subnets, with access restricted via VPC security groups, IAM policies, and AWS PrivateLink, ensuring secure and controlled data streaming.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.14", + "Description": "Ensure there are no internet exposed KMS keys", + "Checks": [ + "kms_key_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "No internet exposed KMS keys", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "AWS Key Management Service (KMS) provides secure encryption key management for data encryption and cryptographic operations. If KMS keys are exposed to the internet, unauthorized entities could potentially use, modify, or compromise encryption keys, leading to data breaches and security vulnerabilities. To enhance security, KMS keys should be restricted to trusted AWS accounts, IAM roles, and specific AWS services, ensuring that only authorized users and systems can access and manage them.", + "AdditionalInformation": "Exposing KMS keys to the public poses a critical security risk, as compromised keys can lead to unauthorized data decryption, loss of data integrity, and compliance violations. Attackers could potentially use public KMS keys to encrypt or decrypt sensitive data, undermining security controls. To prevent unauthorized access, KMS key policies should enforce strict access control using IAM permissions, VPC endpoint policies, and AWS PrivateLink, ensuring that encryption operations remain fully secured and isolated from the public internet.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.15", + "Description": "Ensure lightsail database is not in public mode", + "Checks": [ + "lightsail_database_public" + ], + "Attributes": [ + { + "Title": "Lightsail database not public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Lightsail Databases provide managed database solutions for applications. If a Lightsail database is set to public mode, it is directly accessible from the internet, increasing the risk of unauthorized access and data breaches. To enhance security, Lightsail databases should be configured in private mode, ensuring they are accessible only from trusted instances, private networks, or VPN connections.", + "AdditionalInformation": "Publicly accessible Lightsail databases expose sensitive data to unauthorized access, brute-force attacks, and potential exploitation. Attackers can attempt to compromise credentials, inject malicious queries, or exfiltrate data. To mitigate these risks, Lightsail databases should remain private, with access controlled through firewalls, IAM authentication, and private networking configurations, ensuring secure database connectivity and data protection.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.16", + "Description": "Ensure that Lightsail instances are not publicly accessible", + "Checks": [ + "lightsail_instance_public" + ], + "Attributes": [ + { + "Title": "Lightsail instances are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Lightsail instances provide a simple way to deploy and manage cloud-based virtual machines. If a Lightsail instance is publicly accessible, it can be directly reached from the internet, increasing the risk of unauthorized access, attacks, and data breaches. To enhance security, Lightsail instances should be restricted to private access, ensuring they are reachable only through secure connections, such as VPNs, bastion hosts, or private networking configurations.", + "AdditionalInformation": "Publicly exposed Lightsail instances create a larger attack surface, making them vulnerable to brute-force attacks, unauthorized access, and exploitation of software vulnerabilities. Attackers could compromise credentials, gain control over the instance, or disrupt services. To mitigate these risks, Lightsail instances should be secured using firewalls, private IP configurations, security group restrictions, and IAM-based access controls, ensuring that only trusted users and networks can connect.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.17", + "Description": "Ensure MQ brokers are not publicly accessible", + "Checks": [ + "mq_broker_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "MQ brokers not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS MQ brokers manage message queues for applications, facilitating secure and reliable communication between distributed services. If an MQ broker is publicly accessible, it can be reached from the internet, increasing the risk of unauthorized access, message interception, and data breaches. To enhance security, MQ brokers should be restricted to private networks, ensuring they are accessible only from trusted VPCs, private endpoints, or secure VPN connections.", + "AdditionalInformation": "Publicly exposed MQ brokers pose a significant security risk, as attackers can attempt to intercept messages, inject malicious data, or disrupt message delivery. This could lead to data manipulation, unauthorized access to sensitive information, and system-wide outages. To mitigate these risks, MQ brokers should be configured within private subnets, with access restricted using security groups, IAM policies, and VPC endpoint controls, ensuring secure and controlled message queue operations.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.18", + "Description": "Ensure NeptuneDB manual cluster snapshot is not public", + "Checks": [ + "neptune_cluster_public_snapshot" + ], + "Attributes": [ + { + "Title": "NeptuneDB manual cluster snapshot is not public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon NeptuneDB manual cluster snapshots store backups of graph database clusters, containing sensitive data such as relationships, metadata, and application configurations. By default, NeptuneDB snapshots are private, but they can be manually shared or made public, which can expose critical database information. To enhance security, NeptuneDB manual snapshots should never be publicly accessible, ensuring they are only shared with trusted AWS accounts when necessary.", + "AdditionalInformation": "Publicly accessible NeptuneDB snapshots pose a significant security risk, as attackers could restore the snapshot in their own AWS account and gain full access to the database contents. This could lead to data leaks, compliance violations, and unauthorized access to sensitive business information. To prevent data exposure, NeptuneDB snapshots should be restricted using IAM policies and AWS resource-based permissions, ensuring that only authorized users and services can access and manage database backups securely.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.19", + "Description": "Ensure Neptune Cluster is not using a public subnet", + "Checks": [ + "neptune_cluster_uses_public_subnet" + ], + "Attributes": [ + { + "Title": "Neptune Cluster is not using a public subnet", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Neptune clusters provide a fully managed graph database service designed for applications requiring complex relationship queries. By default, Neptune clusters can be deployed in either public or private subnets. If a Neptune cluster is placed in a public subnet, it becomes accessible from the internet, significantly increasing the risk of unauthorized access and data breaches. To enhance security, Neptune clusters should only be deployed in private subnets, ensuring access is restricted to trusted VPCs, IAM roles, and security group configurations.", + "AdditionalInformation": "Deploying a Neptune cluster in a public subnet exposes the database endpoints to external threats, making them vulnerable to brute-force attacks, unauthorized queries, and data exfiltration. Attackers could exploit misconfigurations to gain access to sensitive graph data, leading to potential compliance violations and security incidents. To reduce exposure, Neptune clusters should be restricted to private subnets, with access controlled through VPC security groups, IAM authentication, and private endpoint configurations, ensuring secure database operations and protected data access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.20", + "Description": "Ensure Amazon Opensearch/Elasticsearch domains are not publicly accessible", + "Checks": [ + "opensearch_service_domains_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "Amazon Opensearch/Elasticsearch domains are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon OpenSearch (formerly Elasticsearch) domains provide search, analytics, and log management capabilities for applications. If an OpenSearch/Elasticsearch domain is publicly accessible, it can be reached from the internet, exposing sensitive data and administrative controls to unauthorized users. To enhance security, OpenSearch domains should be restricted to private networks, ensuring access is limited to trusted VPCs, IAM roles, or specific security group rules.", + "AdditionalInformation": "Publicly accessible OpenSearch/Elasticsearch domains pose a significant security risk, as attackers could execute unauthorized queries, modify data, or gain administrative control over the cluster. This could lead to data breaches, service disruptions, and compliance violations. To mitigate these risks, OpenSearch domains should be deployed in private subnets, with access controlled using VPC restrictions, fine-grained access control (FGAC), IAM policies, and security group rules, ensuring secure and isolated search and analytics operations.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.15", + "Description": "Ensure that S3 buckets have not policies which allow WRITE access", + "Checks": [ + "s3_bucket_policy_public_write_access" + ], + "Attributes": [ + { + "Title": "S3 buckets have not policies which allow WRITE access", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon S3 buckets store and manage data, files, and application assets. Bucket policies control access permissions, and if an S3 bucket has a policy that allows WRITE access to everyone, unauthorized users can upload, modify, or delete objects, leading to data tampering, security breaches, or service disruptions. To enhance security, S3 bucket policies should be restricted to specific AWS accounts, IAM roles, or trusted services, ensuring only authorized users have WRITE permissions.", + "AdditionalInformation": "Allowing unrestricted WRITE access to an S3 bucket increases the risk of unauthorized modifications, data injection attacks, and accidental data loss. Attackers could upload malicious files, delete critical data, or overwrite important configurations. To prevent unauthorized changes, S3 bucket policies should explicitly deny public WRITE access, enforce least privilege access control, and use AWS Block Public Access settings to ensure secure and controlled data storage.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.16", + "Description": "Ensure there are no S3 buckets listable by Everyone or Any AWS customer", + "Checks": [ + "s3_bucket_public_list_acl" + ], + "Attributes": [ + { + "Title": "No S3 buckets are listable by Everyone or Any AWS customer", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon S3 buckets store sensitive data and should have restricted access permissions. If an S3 bucket is listable by Everyone or Any AWS customer, unauthorized users can enumerate the objects within the bucket, potentially exposing sensitive information such as filenames, metadata, or even public datasets. To enhance security, S3 bucket permissions should be configured to restrict LIST access to only authorized IAM roles, AWS accounts, or specific services.", + "AdditionalInformation": "Allowing public or AWS-wide LIST access increases the risk of data enumeration, unauthorized access, and information leaks. Attackers or unauthorized users could identify and analyze stored files, extract metadata, or infer sensitive data. To mitigate this risk, S3 bucket policies should explicitly deny public LIST access, enforce least privilege permissions, and use AWS Block Public Access settings to prevent unintended data exposure.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.17", + "Description": "Ensure there are no S3 buckets writable by Everyone or Any AWS customer", + "Checks": [ + "s3_bucket_public_write_acl" + ], + "Attributes": [ + { + "Title": "No S3 buckets writable by Everyone or Any AWS customer", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Amazon S3 buckets should have strict access controls to prevent unauthorized modifications. If an S3 bucket is writable by Everyone or Any AWS customer, it allows unauthorized users to upload, modify, or delete objects, leading to data corruption, security breaches, and compliance risks. To enhance security, S3 bucket permissions should be restricted to trusted IAM roles, AWS accounts, or specific services.", + "AdditionalInformation": "Allowing public or AWS-wide WRITE access creates a significant security risk, as attackers can inject malicious files, overwrite critical data, or delete essential objects. This could lead to data loss, malware distribution, or unauthorized system modifications. To prevent unauthorized changes, S3 bucket policies should explicitly deny public WRITE access, enforce least privilege access, and use AWS Block Public Access settings to secure data integrity and prevent unauthorized modifications.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.21", + "Description": "Ensure Amazon SageMaker Notebook instances have not direct internet access", + "Checks": [ + "sagemaker_notebook_instance_without_direct_internet_access_configured" + ], + "Attributes": [ + { + "Title": "Amazon SageMaker Notebook instances have not direct internet access", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon SageMaker Notebook instances provide an interactive environment for machine learning development and data analysis. By default, these instances can be configured with direct internet access, which increases the risk of unauthorized access, data leaks, and exposure to malicious external threats. To enhance security, SageMaker Notebook instances should be restricted to private networks, ensuring they are accessed only through secure VPC connections, IAM authentication, or VPNs.", + "AdditionalInformation": "Allowing direct internet access to SageMaker Notebook instances poses a significant security risk, as attackers could exploit misconfigurations, exfiltrate data, or inject malicious code. Publicly accessible notebooks can lead to data breaches, intellectual property theft, or compromised model training workflows. To mitigate these risks, SageMaker Notebook instances should be configured within private subnets, with internet access disabled, and restricted using security groups, IAM policies, and VPC endpoint configurations to ensure secure and controlled machine learning operations.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.18", + "Description": "Ensure Secrets Manager secrets are not publicly accessible", + "Checks": [ + "secretsmanager_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "Secrets Manager secrets are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "AWS Secrets Manager is used to securely store and manage sensitive information, such as API keys, database credentials, and encryption keys. By default, Secrets Manager secrets should be restricted to authorized IAM roles and AWS services. If a secret is publicly accessible, it can be exposed to unauthorized users, leading to data leaks, security breaches, and potential exploitation of sensitive credentials. To enhance security, Secrets Manager secrets should be strictly controlled using IAM policies and resource-based permissions.", + "AdditionalInformation": "Allowing public access to Secrets Manager secrets creates a critical security vulnerability, as attackers could retrieve, misuse, or exfiltrate sensitive information. Compromised secrets could lead to unauthorized access to databases, applications, or cloud services, resulting in data breaches, financial loss, or compliance violations. To mitigate this risk, Secrets Manager secrets should be restricted using least privilege IAM permissions, encrypted with AWS KMS, and accessed only by trusted AWS services and roles, ensuring secure and controlled secret management.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.22", + "Description": "Ensure that SES identities are not publicly accessible", + "Checks": [ + "ses_identity_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "SES identities are not publicly accessible", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Simple Email Service (SES) identities (such as email addresses or domains) are used to send and receive emails through AWS. By default, SES identities should be restricted to authorized AWS accounts and IAM roles. If an SES identity is publicly accessible, unauthorized users could send emails using the identity, leading to email spoofing, phishing attacks, or misuse of the domain for malicious purposes. To enhance security, SES identities should be properly restricted using IAM policies and verified senders.", + "AdditionalInformation": "Allowing public access to SES identities creates a security and reputational risk, as attackers could impersonate the identity, send spam, or launch phishing campaigns. This could lead to domain blacklisting, compliance violations, and damage to the organization’s email reputation. To mitigate these risks, SES identities should be restricted to trusted AWS accounts and IAM roles, ensuring that only authorized services and users can send emails, protecting the integrity and security of email communications.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.23", + "Description": "Ensure SQS queues have not policy set as Public", + "Checks": [ + "sqs_queues_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "SQS queues have not policy set as Public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Amazon Simple Queue Service (SQS) queues enable asynchronous message processing between distributed systems. By default, SQS queues should be restricted to authorized AWS accounts and IAM roles. If an SQS queue has a public policy, it allows anyone on the internet to send, receive, or delete messages, leading to data leaks, unauthorized message injection, and potential denial-of-service (DoS) attacks. To enhance security, SQS queue policies should be configured to allow access only to trusted AWS accounts, IAM roles, or specific AWS services.", + "AdditionalInformation": "Publicly accessible SQS queues pose a significant security risk, as attackers could inject malicious messages, disrupt processing workflows, or delete critical messages, leading to system failures and data integrity issues. To prevent unauthorized access, SQS policies should explicitly deny public access, enforce least privilege access control, and use IAM policies and VPC endpoint restrictions to ensure secure and controlled messaging operations.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.3.24", + "Description": "Ensure that there are not SSM Documents set as public", + "Checks": [ + "ssm_documents_set_as_public" + ], + "Attributes": [ + { + "Title": "No SSM Documents are set as public", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "AWS Systems Manager (SSM) Documents define configuration, automation, and maintenance tasks for AWS resources. By default, SSM documents should be restricted to specific AWS accounts, IAM roles, or AWS services. If an SSM document is set as public, unauthorized users could access, modify, or execute automation tasks on AWS infrastructure, leading to misconfigurations, security breaches, or unintended system modifications. To enhance security, SSM documents should be kept private and assigned only to trusted AWS entities.", + "AdditionalInformation": "Publicly accessible SSM documents pose a significant security risk, as attackers could execute malicious commands, modify system configurations, or disrupt AWS operations. This could lead to unauthorized access, data leaks, compliance violations, or system downtime. To prevent security threats, SSM documents should explicitly deny public access, enforce least privilege permissions, and use IAM policies and resource-based access controls to ensure only trusted users and systems can manage AWS resources.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.1", + "Description": "Ensure CloudTrail is enabled in all regions", + "Checks": [ + "cloudtrail_multi_region_enabled" + ], + "Attributes": [ + { + "Title": "CloudTrail enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "AWS CloudTrail is a service that records and monitors AWS API calls across an account, providing detailed logs of who performed what action, when, and from where. CloudTrail captures API activity from the AWS Management Console, SDKs, CLI, and AWS services such as CloudFormation. The logs include key details such as the identity of the API caller, timestamp, source IP address, request parameters, and response elements.", + "AdditionalInformation": "CloudTrail enhances security, auditing, and compliance by providing a complete history of API activities in an AWS account. Enabling a multi-region trail ensures: Detection of unauthorized activity in rarely used AWS regions. Global Service Logging is automatically enabled, capturing API calls from global services such as IAM and AWS Organizations. Tracking of all management events, ensuring that both read and write operations across AWS resources are recorded for improved security monitoring and compliance.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.2", + "Description": "Ensure CloudTrail log file validation is enabled", + "Checks": [ + "cloudtrail_log_file_validation_enabled" + ], + "Attributes": [ + { + "Title": "CloudTrail file validation enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "AWS CloudTrail log file validation generates digitally signed digest files containing cryptographic hashes of each log file stored in Amazon S3. These digest files allow users to verify whether logs have been altered, deleted, or remain unchanged after being delivered by CloudTrail. Enabling log file validation ensures data integrity and auditability for security and compliance purposes.", + "AdditionalInformation": "Enabling log file validation enhances security by ensuring the integrity of CloudTrail logs, preventing tampering or unauthorized modifications. This helps: Detect log file alterations, ensuring logs remain trustworthy for audits and investigations. Improve compliance with frameworks that require log integrity, such as PCI DSS, SOC 2, and ISO 27001. Strengthen forensic capabilities, allowing security teams to verify log authenticity in case of a security incident.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.1", + "Description": "Ensure AWS Config is enabled in all regions", + "Checks": [ + "config_recorder_all_regions_enabled" + ], + "Attributes": [ + { + "Title": "AWS Config is enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "AWS Config is a service that continuously monitors, records, and evaluates configuration changes in AWS resources within an account. It tracks configuration items, relationships between resources, and changes over time, delivering logs for security analysis, change management, and compliance auditing. To ensure comprehensive monitoring, AWS Config should be enabled in all regions.", + "AdditionalInformation": "Enabling AWS Config in all regions improves security, visibility, and compliance by: Tracking resource changes, allowing for quick identification of misconfigurations. Supporting security audits and forensic investigations by maintaining a historical record of configurations.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.3", + "Description": "Ensure that server access logging is enabled on the CloudTrail S3 bucket", + "Checks": [ + "cloudtrail_logs_s3_bucket_access_logging_enabled" + ], + "Attributes": [ + { + "Title": "Server access logging is enabled on the CloudTrail S3 bucket", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Server access logging provides detailed records of requests made to an S3 bucket, including request type, accessed resources, timestamp, and requester details. Enabling server access logging on the CloudTrail S3 bucket ensures that all interactions with CloudTrail logs are recorded, improving security visibility and auditability", + "AdditionalInformation": "Enabling server access logging on CloudTrail S3 buckets enhances security monitoring, incident response, and compliance by: Capturing all events affecting CloudTrail logs, helping detect unauthorized access or modifications. Providing an audit trail for forensic investigations and compliance reporting. Enhancing security workflows by storing access logs in a separate, dedicated logging bucket for improved log integrity and analysis.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.2.3", + "Description": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs", + "Checks": [ + "cloudtrail_kms_encryption_enabled" + ], + "Attributes": [ + { + "Title": "CloudTrail logs are encrypted at rest using KMS CMKs", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "AWS CloudTrail records API activity across an AWS account, and its logs contain sensitive security and operational data. AWS Key Management Service (KMS) provides encryption key management using customer-managed keys (CMKs) and Hardware Security Modules (HSMs) to ensure secure key storage and usage. CloudTrail logs can be encrypted using Server-Side Encryption (SSE) with KMS (SSE-KMS) to add an extra layer of protection and access control", + "AdditionalInformation": "Using SSE-KMS encryption for CloudTrail logs enhances security by adding an extra layer of access control. This ensures that only authorized users with both S3 read permissions and KMS decryption rights can access log data, protecting sensitive security information from unauthorized access or tampering. It also helps maintain compliance with security and regulatory standards by enforcing strict encryption controls.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.2.1", + "Description": "Ensure rotation for customer-created symmetric CMKs is enabled", + "Checks": [ + "kms_cmk_rotation_enabled" + ], + "Attributes": [ + { + "Title": "Rotation for customer-created symmetric CMKs enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "AWS Key Management Service (KMS) allows users to manage encryption keys securely. Key rotation enables the automatic replacement of the backing key (the cryptographic material tied to a customer-managed key (CMK)), ensuring continuous security without disrupting access to previously encrypted data. AWS automatically retains previous backing keys to allow seamless decryption of older data while using a newly generated key for encryption. It is recommended to enable key rotation for symmetric CMKs, as asymmetric keys do not support this feature.", + "AdditionalInformation": "Regularly rotating encryption keys minimizes the risk associated with key compromise by ensuring that newly encrypted data is protected with a fresh key, reducing the potential impact of an exposed key. Since AWS KMS retains prior backing keys for seamless decryption, rotation does not disrupt access to previously encrypted data. Implementing key rotation enhances security by limiting the exposure window of any single encryption key and aligning with best practices for cryptographic hygiene.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.4", + "Description": "Ensure VPC flow logging is enabled in all VPCs", + "Checks": [ + "vpc_flow_logs_enabled" + ], + "Attributes": [ + { + "Title": "VPC flow logging enabled in all VPCs", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "VPC Flow Logs capture and record IP traffic information for network interfaces within a VPC, allowing administrators to monitor and analyze network activity. These logs are stored in Amazon CloudWatch Logs for retrieval and analysis. It is recommended to enable VPC Flow Logs for rejected packets to track unauthorized access attempts, misconfigurations, or potential security threats within the VPC.", + "AdditionalInformation": "Enabling VPC Flow Logs for rejected traffic enhances network visibility and security monitoring by detecting suspicious activity, failed connection attempts, and potential threats. These logs help identify anomalous traffic patterns, troubleshoot connectivity issues, and support incident response workflows, improving overall security posture.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.5", + "Description": "Ensure that object-level logging for write events is enabled for S3 buckets", + "Checks": [ + "cloudtrail_s3_dataevents_write_enabled" + ], + "Attributes": [ + { + "Title": "Object-level logging for write events is enabled for S3 buckets", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "S3 object-level API operations, such as GetObject, PutObject, and DeleteObject, are classified as data events in AWS CloudTrail. By default, CloudTrail does not log data events, meaning detailed tracking of individual object interactions is not enabled. To enhance visibility and security, it is recommended to enable object-level logging for S3 buckets to monitor access and modification activities.", + "AdditionalInformation": "Enabling object-level logging helps organizations meet compliance requirements, enhance security monitoring, and detect unauthorized access. It allows administrators to analyze user behavior, track modifications to critical data, and respond to security incidents in real time using Amazon CloudWatch Events, ensuring greater control over S3 bucket activity.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.6", + "Description": "Ensure that object-level logging for read events is enabled for S3 buckets", + "Checks": [ + "cloudtrail_s3_dataevents_read_enabled" + ], + "Attributes": [ + { + "Title": "Object-level logging for read events is enabled for S3 buckets", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "S3 object-level API operations, such as GetObject, PutObject, and DeleteObject, are classified as data events in AWS CloudTrail. By default, CloudTrail does not log data events, meaning individual object interactions are not tracked unless explicitly enabled. To improve security monitoring and compliance, it is recommended to enable object-level logging for S3 buckets.", + "AdditionalInformation": "Object-level logging enhances data security, compliance, and operational visibility by providing detailed tracking of who accessed, modified, or deleted objects within S3 buckets. This enables organizations to monitor user behavior, detect unauthorized access, and quickly respond to potential security incidents using Amazon CloudWatch Events.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.2", + "Description": "Ensure unauthorized API calls are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_unauthorized_api_calls" + ], + "Attributes": [ + { + "Title": "Unauthorized API calls are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can automatically detect and respond to unauthorized API calls, improving security visibility. It is recommended to establish a metric filter and alarm for unauthorized API calls to enhance threat detection and incident response.", + "AdditionalInformation": "Monitoring unauthorized API calls helps identify potential security incidents faster, reducing the time attackers have to exploit vulnerabilities. CloudWatch provides real-time monitoring and alerting, while SIEM solutions offer centralized security event analysis. Detecting unauthorized API calls early allows organizations to take immediate action, investigate potential threats, and strengthen overall AWS security posture.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.3", + "Description": "Ensure management console sign-in without MFA is monitored", + "Checks": [ + "cloudwatch_log_metric_filter_sign_in_without_mfa" + ], + "Attributes": [ + { + "Title": "Management console sign-in without MFA is monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By setting up metric filters and alarms, organizations can detect and respond to security risks effectively. It is recommended to establish a metric filter and alarm for AWS console logins that are not protected by multi-factor authentication (MFA) to enhance security monitoring.", + "AdditionalInformation": "Monitoring console logins without MFA improves visibility into accounts that lack strong authentication controls. Accounts without MFA are more vulnerable to credential theft, brute-force attacks, and unauthorized access. By detecting these login attempts in real-time, organizations can identify security gaps, enforce MFA policies, and reduce the risk of account compromise.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.4", + "Description": "Ensure usage of the root account is monitored", + "Checks": [ + "cloudwatch_log_metric_filter_root_usage" + ], + "Attributes": [ + { + "Title": "Usage of root account monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Setting up metric filters and alarms helps detect potential security threats. It is recommended to establish a metric filter and alarm for root account login attempts to identify unauthorized access or improper use of the highly privileged root account.", + "AdditionalInformation": "Monitoring root account logins enhances visibility into the usage of the most privileged AWS account, which should be used only in exceptional cases. Frequent or unauthorized root logins increase security risks by exposing critical administrative controls. Detecting root login attempts in real time enables organizations to identify potential security incidents, enforce least privilege principles, and limit unnecessary use of the root account.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.5", + "Description": "Ensure IAM policy changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_policy_changes" + ], + "Attributes": [ + { + "Title": "IAM policy changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect critical security events. It is recommended to establish a metric filter and alarm for changes to Identity and Access Management (IAM) policies to track modifications that could affect authentication and authorization controls.", + "AdditionalInformation": "Monitoring IAM policy changes helps ensure that access controls remain secure and intact. Unauthorized or unintended modifications to IAM policies can lead to privilege escalation, misconfigurations, and security breaches. Detecting these changes in real-time allows organizations to respond quickly to potential threats, enforce least privilege principles, and maintain a strong security posture.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.6", + "Description": "Ensure CloudTrail configuration changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" + ], + "Attributes": [ + { + "Title": "CloudTrail configuration changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can track critical security events. It is recommended to establish a metric filter and alarm to detect changes to CloudTrail configurations, ensuring that logging remains active and tamper-proof.", + "AdditionalInformation": "Monitoring CloudTrail configuration changes helps maintain continuous visibility into AWS account activity. Unauthorized modifications to CloudTrail settings could disable or alter logging, potentially allowing malicious activity to go undetected. Detecting these changes in real time enables organizations to quickly respond to threats, enforce security best practices, and ensure compliance with auditing requirements.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.7", + "Description": "Ensure AWS Management Console authentication failures are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_authentication_failures" + ], + "Attributes": [ + { + "Title": "AWS Management Console authentication failures are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By setting up metric filters and alarms, organizations can detect potential security threats. It is recommended to establish a metric filter and alarm for failed console authentication attempts to identify potential unauthorized access attempts or brute-force attacks.", + "AdditionalInformation": "Monitoring failed console logins helps detect brute-force attempts and unauthorized access attempts early. Repeated failed authentication attempts can indicate malicious activity, and tracking them allows security teams to identify suspicious IP addresses, correlate with other security events, and take proactive measures to protect AWS accounts.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.8", + "Description": "Ensure disabling or scheduled deletion of customer created CMKs is monitored", + "Checks": [ + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk" + ], + "Attributes": [ + { + "Title": "Disabling or scheduled deletion of customer created CMKs is monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect security-critical changes. It is recommended to set up a metric filter and alarm for customer-managed KMS keys (CMKs) that are disabled or scheduled for deletion to prevent unintended encryption key loss.", + "AdditionalInformation": "Disabling or deleting a customer-managed CMK can render encrypted data permanently inaccessible, leading to data loss and service disruptions. Monitoring CMK state changes helps detect unauthorized or accidental modifications, ensuring encryption keys remain available and aligned with security policies. Detecting such changes in real time allows organizations to prevent data loss, maintain compliance, and take corrective action if needed.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.9", + "Description": "Ensure S3 bucket policy changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes" + ], + "Attributes": [ + { + "Title": "S3 bucket policy changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can track critical security-related changes. It is recommended to set up a metric filter and alarm for modifications to S3 bucket policies to detect potential misconfigurations or unauthorized access changes.", + "AdditionalInformation": "Monitoring S3 bucket policy changes helps detect and respond to overly permissive configurations that could expose sensitive data. Unauthorized or accidental modifications may grant public access or excessive permissions, increasing the risk of data breaches and compliance violations. Real-time alerts allow security teams to quickly identify, investigate, and correct risky policy changes, reducing exposure and strengthening data security.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.10", + "Description": "Ensure AWS Config configuration changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled" + ], + "Attributes": [ + { + "Title": "AWS Config configuration changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect critical configuration changes. It is recommended to establish a metric filter and alarm for modifications to AWS Config’s configurations to ensure continuous monitoring and compliance.", + "AdditionalInformation": "Monitoring AWS Config configuration changes helps maintain visibility and control over resource configurations. Unauthorized or accidental modifications to AWS Config settings may result in gaps in security monitoring, misconfigurations going undetected, and compliance violations. Real-time alerts allow security teams to quickly detect, investigate, and respond to changes, ensuring the integrity of configuration tracking across the AWS environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.11", + "Description": "Ensure security group changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_security_group_changes" + ], + "Attributes": [ + { + "Title": "Security group changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Security groups act as stateful packet filters that control inbound and outbound traffic within a VPC. It is recommended to establish a metric filter and alarm to detect changes to security groups to prevent unauthorized modifications that could expose resources to security threats.", + "AdditionalInformation": "Monitoring security group changes helps ensure that network access controls remain secure and that AWS resources are not unintentionally exposed. Unauthorized or accidental modifications to security groups can create security gaps, increasing the risk of data breaches and unauthorized access. Real-time alerts enable security teams to detect, investigate, and respond to security group changes quickly, maintaining a strong network security posture.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.12", + "Description": "Ensure Network Access Control List (NACL) changes are monitored", + "Checks": [ + "cloudwatch_changes_to_network_acls_alarm_configured" + ], + "Attributes": [ + { + "Title": "Network Access Control List (NACL) changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Network Access Control Lists (NACLs) act as stateless packet filters that control inbound and outbound traffic for subnets within a VPC. It is recommended to establish a metric filter and alarm to detect changes to NACLs to prevent unauthorized modifications that could compromise network security.", + "AdditionalInformation": "Monitoring NACL changes helps ensure that network traffic controls remain properly configured and that AWS resources are not unintentionally exposed. Unauthorized or accidental modifications to NACL rules can lead to misconfigured security policies, increased attack surfaces, and potential data breaches. Real-time alerts enable security teams to detect, investigate, and respond to NACL modifications quickly, maintaining strong network security controls.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.13", + "Description": "Ensure changes to network gateways are monitored", + "Checks": [ + "cloudwatch_changes_to_network_gateways_alarm_configured" + ], + "Attributes": [ + { + "Title": "Changes to network gateways are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Network gateways serve as the primary route for traffic entering and leaving a VPC, facilitating communication with external networks. It is recommended to establish a metric filter and alarm for changes to network gateways to ensure that network traffic is securely routed through controlled paths.", + "AdditionalInformation": "Monitoring network gateway changes helps maintain secure ingress and egress traffic flows within a VPC. Unauthorized or accidental modifications to network gateways can disrupt connectivity, introduce security vulnerabilities, or expose AWS resources to external threats. Real-time alerts enable security teams to detect, investigate, and respond to changes quickly, ensuring that all traffic follows a controlled and secure routing policy.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.14", + "Description": "Ensure route table changes are monitored", + "Checks": [ + "cloudwatch_changes_to_network_route_tables_alarm_configured" + ], + "Attributes": [ + { + "Title": "Route table changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Route tables determine how network traffic is directed between subnets and network gateways within a VPC. It is recommended to establish a metric filter and alarm for changes to route tables to detect unauthorized or accidental modifications that could impact network security and connectivity.", + "AdditionalInformation": "Monitoring route table changes ensures that VPC traffic follows the intended and secure routing paths. Unauthorized modifications can result in misrouted traffic, exposure of sensitive resources, or connectivity disruptions. Real-time alerts enable security teams to detect, investigate, and respond to route table changes promptly, preventing potential security risks and maintaining a controlled and secure network environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.15", + "Description": "Ensure VPC changes are monitored", + "Checks": [ + "cloudwatch_changes_to_vpcs_alarm_configured" + ], + "Attributes": [ + { + "Title": "VPC changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. AWS accounts can contain multiple Virtual Private Clouds (VPCs), and VPC peering connections allow network traffic to flow between them. It is recommended to establish a metric filter and alarm for changes made to VPC configurations to detect unauthorized modifications that could impact network security and connectivity.", + "AdditionalInformation": "Monitoring VPC configuration changes helps ensure network integrity, security, and proper traffic flow within AWS environments. Unauthorized or accidental modifications can result in misconfigured routing, unintended internet exposure, or connectivity disruptions between resources. Real-time alerts enable security teams to detect, investigate, and respond to VPC changes promptly, preventing security risks and ensuring consistent network accessibility and isolation.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.16", + "Description": "Ensure AWS Organizations changes are monitored", + "Checks": [ + "cloudwatch_log_metric_filter_aws_organizations_changes" + ], + "Attributes": [ + { + "Title": "AWS Organizations changes are monitored", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. AWS Organizations allows centralized management of multiple AWS accounts, and modifications to its configuration can significantly impact access control, account governance, and security policies. It is recommended to establish a metric filter and alarm for changes made to AWS Organizations in the master AWS account to detect unauthorized or unintended modifications.", + "AdditionalInformation": "Monitoring AWS Organizations configuration changes helps prevent unwanted, accidental, or malicious modifications that could lead to unauthorized access, policy misconfigurations, or security breaches. Detecting changes in real time ensures that unexpected modifications can be investigated and remediated quickly, reducing the risk of compromised governance structures and ensuring compliance with organizational security policies.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.17", + "Description": "Ensure AWS Security Hub is enabled", + "Checks": [ + "securityhub_enabled" + ], + "Attributes": [ + { + "Title": "Security Hub enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "AWS Security Hub centralizes security data from multiple AWS services and third-party security tools, allowing for real-time threat detection, risk assessment, and compliance monitoring. When enabled, Security Hub aggregates, organizes, and prioritizes security findings from services such as Amazon GuardDuty, Amazon Inspector, and Amazon Macie, as well as integrated third-party security products. This provides organizations with a unified security management platform to enhance threat visibility.", + "AdditionalInformation": "Enabling AWS Security Hub provides a comprehensive view of your security posture, helping to identify vulnerabilities, detect threats, and enforce security best practices. It allows organizations to monitor security trends, benchmark environments against industry standards, and quickly respond to high-priority security issues, strengthening overall AWS security governance and compliance.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.2.2", + "Description": "Ensure CloudWatch Log Groups have a retention policy of specific days", + "Checks": [ + "cloudwatch_log_group_retention_policy_specific_days_enabled" + ], + "Attributes": [ + { + "Title": "CloudWatch Log Groups have a retention policy of specific days", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "AWS CloudWatch Log Groups store logs from various AWS services and applications, enabling monitoring, debugging, and security auditing. By default, CloudWatch logs are retained indefinitely, which can lead to unnecessary data storage costs and compliance risks. To manage log lifecycle effectively, it is recommended to set a retention policy for CloudWatch Log Groups, ensuring logs are retained only for a specific number of days based on operational and compliance requirements.", + "AdditionalInformation": "Setting a retention policy for CloudWatch logs helps balance cost management, compliance, and security. Retaining logs for too long increases storage costs and potential exposure to sensitive data, while keeping them for too short a duration can limit forensic investigations and compliance reporting. By defining a specific retention period, organizations can ensure logs are available for troubleshooting and audits while adhering to data retention best practices and regulatory requirements.", + "LevelOfRisk": 1 + } + ] + } + ] +} diff --git a/prowler/compliance/azure/cis_2.0_azure.json b/prowler/compliance/azure/cis_2.0_azure.json index 6e914f5057..6a35609d3e 100644 --- a/prowler/compliance/azure/cis_2.0_azure.json +++ b/prowler/compliance/azure/cis_2.0_azure.json @@ -2607,7 +2607,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Network security groups should be periodically evaluated for port misconfigurations. Where certain ports and protocols may be exposed to the Internet, they should be evaluated for necessity and restricted wherever they are not explicitly required.", @@ -2629,7 +2629,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Network security groups should be periodically evaluated for port misconfigurations. Where certain ports and protocols may be exposed to the Internet, they should be evaluated for necessity and restricted wherever they are not explicitly required.", @@ -2651,7 +2651,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Network security groups should be periodically evaluated for port misconfigurations. Where certain ports and protocols may be exposed to the Internet, they should be evaluated for necessity and restricted wherever they are not explicitly required.", @@ -2673,7 +2673,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Network security groups should be periodically evaluated for port misconfigurations. Where certain ports and protocols may be exposed to the Internet, they should be evaluated for necessity and restricted wherever they are not explicitly required and narrowly configured.", @@ -2695,7 +2695,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Network Security Group Flow Logs should be enabled and the retention period set to greater than or equal to 90 days.", @@ -2717,7 +2717,7 @@ ], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Enable Network Watcher for Azure subscriptions.", @@ -2737,7 +2737,7 @@ "Checks": [], "Attributes": [ { - "Section": "6 Networking", + "Section": "6. Networking", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Public IP Addresses provide tenant accounts with Internet connectivity for resources contained within the tenant. During the creation of certain resources in Azure, a Public IP Address may be created. All Public IP Addresses within the tenant should be periodically reviewed for accuracy and necessity.", @@ -2759,7 +2759,7 @@ ], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "The Azure Bastion service allows secure remote access to Azure Virtual Machines over the Internet without exposing remote access protocol ports and services directly to the Internet. The Azure Bastion service provides this access using TLS over 443/TCP, and subscribes to hardened configurations within an organization's Azure Active Directory service.", @@ -2781,7 +2781,7 @@ ], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Migrate blob-based VHDs to Managed Disks on Virtual Machines to exploit the default features of this configuration. The features include: 1. Default Disk Encryption 2. Resilience, as Microsoft will managed the disk storage and move around if underlying hardware goes faulty 3. Reduction of costs over storage accounts", @@ -2803,7 +2803,7 @@ ], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Ensure that OS disks (boot volumes) and data disks (non-boot volumes) are encrypted with CMK (Customer Managed Keys). Customer Managed keys can be either ADE orServer Side Encryption (SSE).", @@ -2825,7 +2825,7 @@ ], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Ensure that unattached disks in a subscription are encrypted with a Customer Managed Key (CMK).", @@ -2845,7 +2845,7 @@ "Checks": [], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "For added security, only install organization-approved extensions on VMs.", @@ -2867,7 +2867,7 @@ ], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Install endpoint protection for all virtual machines.", @@ -2887,7 +2887,7 @@ "Checks": [], "Attributes": [ { - "Section": "7 Virtual Machines", + "Section": "7. Virtual Machines", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "NOTE: This is a legacy recommendation. Managed Disks are encrypted by default and recommended for all new VM implementations. VHD (Virtual Hard Disks) are stored in blob storage and are the old-style disks that were attached to Virtual Machines. The blob VHD was then leased to the VM. By default, storage accounts are not encrypted, and Microsoft Defender will then recommend that the OS disks should be encrypted. Storage accounts can be encrypted as a whole using PMK or CMK. This should be turned on for storage accounts containing VHDs", @@ -2909,7 +2909,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Ensure that all Keys in Role Based Access Control (RBAC) Azure Key Vaults have an expiration date set.", @@ -2931,7 +2931,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Ensure that all Keys in Non Role Based Access Control (RBAC) Azure Key Vaults have an expiration date set.", @@ -2953,7 +2953,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Ensure that all Secrets in Role Based Access Control (RBAC) Azure Key Vaults have an expiration date set.", @@ -2975,7 +2975,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Ensure that all Secrets in Non Role Based Access Control (RBAC) Azure Key Vaults have an expiration date set.", @@ -2997,7 +2997,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "The Key Vault contains object keys, secrets, and certificates. Accidental unavailability of a Key Vault can cause immediate data loss or loss of security functions (authentication, validation, verification, non-repudiation, etc.) supported by the Key Vault objects. It is recommended the Key Vault be made recoverable by enabling the 'Do Not Purge' and 'Soft Delete' functions. This is in order to prevent loss of encrypted data, including storage accounts, SQL databases, and/or dependent services provided by Key Vault objects (Keys, Secrets, Certificates) etc. This may happen in the case of accidental deletion by a user or from disruptive activity by a malicious user. WARNING: A current limitation of the soft-delete feature across all Azure services is role assignments disappearing when Key Vault is deleted. All role assignments will need to be recreated after recovery.", @@ -3019,7 +3019,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "WARNING: Role assignments disappear when a Key Vault has been deleted (soft-delete) and recovered. Afterwards it will be required to recreate all role assignments. This is a limitation of the soft-delete feature across all Azure services.", @@ -3041,7 +3041,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Private endpoints will secure network traffic from Azure Key Vault to the resources requesting secrets and keys.", @@ -3063,7 +3063,7 @@ ], "Attributes": [ { - "Section": "8 Key Vault", + "Section": "8. Key Vault", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Automatic Key Rotation is available in Public Preview. The currently supported applications are Key Vault, Managed Disks, and Storage accounts accessing keys within Key Vault. The number of supported applications will incrementally increased.", @@ -3085,7 +3085,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Azure App Service Authentication is a feature that can prevent anonymous HTTP requests from reaching a Web Application or authenticate those with tokens before they reach the app. If an anonymous request is received from a browser, App Service will redirect to a logon page. To handle the logon process, a choice from a set of identity providers can be made, or a custom authentication mechanism can be implemented.", @@ -3107,7 +3107,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Azure Web Apps allows sites to run under both HTTP and HTTPS by default. Web apps can be accessed by anyone using non-secure HTTP links by default. Non-secure HTTP requests can be restricted and all HTTP requests redirected to the secure HTTPS port. It is recommended to enforce HTTPS-only traffic.", @@ -3129,7 +3129,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "The TLS (Transport Layer Security) protocol secures transmission of data over the internet using standard encryption technology. Encryption should be set with the latest version of TLS. App service allows TLS 1.2 by default, which is the recommended TLS level by industry standards such as PCI DSS.", @@ -3151,7 +3151,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 2", "AssessmentStatus": "Automated", "Description": "Client certificates allow for the app to request a certificate for incoming requests. Only clients that have a valid certificate will be able to reach the app.", @@ -3173,7 +3173,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Managed service identity in App Service provides more security by eliminating secrets from the app, such as credentials in the connection strings. When registering with Azure Active Directory in App Service, the app will connect to other Azure services securely without the need for usernames and passwords.", @@ -3195,7 +3195,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Periodically newer versions are released for PHP software either due to security flaws or to include additional functionality. Using the latest PHP version for web apps is recommended in order to take advantage of security fixes, if any, and/or additional functionalities of the newer version.", @@ -3217,7 +3217,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Periodically, newer versions are released for Python software either due to security flaws or to include additional functionality. Using the latest full Python version for web apps is recommended in order to take advantage of security fixes, if any, and/or additional functionalities of the newer version.", @@ -3239,7 +3239,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Periodically, newer versions are released for Java software either due to security flaws or to include additional functionality. Using the latest Java version for web apps is recommended in order to take advantage of security fixes, if any, and/or new functionalities of the newer version.", @@ -3261,7 +3261,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "Periodically, newer versions are released for HTTP either due to security flaws or to include additional functionality. Using the latest HTTP version for web apps to takeadvantage of security fixes, if any, and/or new functionalities of the newer version.", @@ -3283,7 +3283,7 @@ ], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "By default, Azure Functions, Web, and API Services can be deployed over FTP. If FTP is required for an essential deployment workflow, FTPS should be required for FTP login for all App Service Apps and Functions.", @@ -3303,7 +3303,7 @@ "Checks": [], "Attributes": [ { - "Section": "9 AppService", + "Section": "9. AppService", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Azure Key Vault will store multiple types of sensitive information such as encryption keys, certificate thumbprints, and Managed Identity Credentials. Access to these 'Secrets' can be controlled through granular permissions.", @@ -3323,7 +3323,7 @@ "Checks": [], "Attributes": [ { - "Section": "10 Miscellaneous", + "Section": "10. Miscellaneous", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Resource Manager Locks provide a way for administrators to lock down Azure resources to prevent deletion of, or modifications to, a resource. These locks sit outside of the Role Based Access Controls (RBAC) hierarchy and, when applied, will place restrictions on the resource for all users. These locks are very useful when there is an important resource in a subscription that users should not be able to delete or change. Locks can help prevent accidental and malicious changes or deletion.", diff --git a/prowler/compliance/azure/cis_2.1_azure.json b/prowler/compliance/azure/cis_2.1_azure.json index 32336ba368..594b2ad851 100644 --- a/prowler/compliance/azure/cis_2.1_azure.json +++ b/prowler/compliance/azure/cis_2.1_azure.json @@ -12,7 +12,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Require administrators or appropriately delegated users to create new tenants.", @@ -32,7 +32,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Microsoft Entra ID is extended to include Azure AD B2B collaboration, allowing you to invite people from outside your organization to be guest users in your cloud account and sign in with their own work, school, or social identities. Guest users allow you to share your company's applications and services with users from any other organization, while maintaining control over your own corporate data. Work with external partners, large or small, even if they don't have Azure AD or an IT department. A simple invitation and redemption process lets partners use their own credentials to access your company's resources as a guest user. Guest users in every subscription should be review on a regular basis to ensure that inactive and unneeded accounts are removed.", @@ -52,7 +52,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Ensures that two alternate forms of identification are provided before allowing a password reset.", @@ -72,7 +72,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Microsoft Azure provides a Global Banned Password policy that applies to Azure administrative and normal user accounts. This is not applied to user accounts that are synced from an on-premise Active Directory unless Microsoft Entra ID Connect is used and you enable EnforceCloudPasswordPolicyForPasswordSyncedUsers. Please see the list in default values on the specifics of this policy. To further password security, it is recommended to further define a custom banned password policy.", @@ -92,7 +92,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Ensure that the number of days before users are asked to re-confirm their authentication information is not set to 0.", @@ -112,7 +112,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Ensure that users are notified on their primary and secondary emails on password resets.", @@ -132,7 +132,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Ensure that all Global Administrators are notified if any other administrator resets their password.", @@ -154,7 +154,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Require administrators to provide consent for applications before use.", @@ -176,7 +176,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Allow users to provide consent for selected permissions when a request is coming from a verified publisher.", @@ -196,7 +196,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Require administrators to provide consent for the apps before use.", @@ -218,7 +218,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Require administrators or appropriately delegated users to register third-party applications.", @@ -240,7 +240,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Limit guest user permissions.", @@ -262,7 +262,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Restrict invitations to users with specific administrative roles only.", @@ -282,7 +282,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Restrict access to the Microsoft Entra ID administration center to administrators only. **NOTE**: This only affects access to the Entra ID administrator's web portal. This setting does not prohibit privileged users from using other methods such as Rest API or Powershell to obtain sensitive information from Microsoft Entra ID.", @@ -302,7 +302,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Restrict access to group web interface in the Access Panel portal.", @@ -324,7 +324,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Restrict security group creation to administrators only.", @@ -344,7 +344,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Restrict security group management to administrators only.", @@ -366,7 +366,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Restrict Microsoft 365 group creation to administrators only.", @@ -386,7 +386,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "Joining or registering devices to Microsoft Entra ID should require Multi-factor authentication.", @@ -408,7 +408,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Automated", "Description": "The principle of least privilege should be followed and only necessary privileges should be assigned instead of allowing full administrative access.", @@ -430,7 +430,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Resource locking is a powerful protection mechanism that can prevent inadvertent modification/deletion of resources within Azure subscriptions/Resource Groups and is a recommended NIST configuration.", @@ -450,7 +450,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Users who are set as subscription owners are able to make administrative changes to the subscriptions and move them into and out of Microsoft Entra ID.", @@ -472,7 +472,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "Profile": "Level 1", "AssessmentStatus": "Manual", "Description": "This recommendation aims to maintain a balance between security and operational efficiency by ensuring that a minimum of 2 and a maximum of 4 users are assigned the Global Administrator role in Microsoft Entra ID. Having at least two Global Administrators ensures redundancy, while limiting the number to four reduces the risk of excessive privileged access.", @@ -494,7 +494,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.1 Security Defaults Security Defaults", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -517,7 +517,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.1 Security Defaults Security Defaults", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -540,7 +540,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.1 Security Defaults Security Defaults", "Profile": "Level 2", "AssessmentStatus": "Manual", @@ -561,7 +561,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.1 Security Defaults Security Defaults", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -584,7 +584,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -605,7 +605,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -626,7 +626,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -647,7 +647,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -668,7 +668,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -691,7 +691,7 @@ ], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", @@ -712,7 +712,7 @@ "Checks": [], "Attributes": [ { - "Section": "1.Identity and Access Management", + "Section": "1. Identity and Access Management", "SubSection": "1.2 Conditional Access", "Profile": "Level 1", "AssessmentStatus": "Manual", diff --git a/prowler/compliance/azure/cis_3.0_azure.json b/prowler/compliance/azure/cis_3.0_azure.json index a72b5907ac..4175dd2a7e 100644 --- a/prowler/compliance/azure/cis_3.0_azure.json +++ b/prowler/compliance/azure/cis_3.0_azure.json @@ -3481,7 +3481,7 @@ "Checks": [], "Attributes": [ { - "Section": "10", + "Section": "10. Miscellaneous", "Profile": "Level 2", "AssessmentStatus": "Manual", "Description": "Resource Manager Locks provide a way for administrators to lock down Azure resources to prevent deletion of, or modifications to, a resource. These locks sit outside of the Role Based Access Controls (RBAC) hierarchy and, when applied, will place restrictions on the resource for all users. These locks are very useful when there is an important resource in a subscription that users should not be able to delete or change. Locks can help prevent accidental and malicious changes or deletion.", diff --git a/prowler/compliance/azure/prowler_threatscore_azure.json b/prowler/compliance/azure/prowler_threatscore_azure.json new file mode 100644 index 0000000000..db479fb616 --- /dev/null +++ b/prowler/compliance/azure/prowler_threatscore_azure.json @@ -0,0 +1,1317 @@ +{ + "Framework": "ProwlerThreatScore", + "Version": "1.0", + "Provider": "Azure", + "Description": "Prowler ThreatScore Compliance Framework for Azure ensures that the Azure subscription is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + "Requirements": [ + { + "Id": "1.1.1", + "Description": "Ensure Security Defaults is enabled on Microsoft Entra ID", + "Checks": [ + "entra_security_defaults_enabled" + ], + "Attributes": [ + { + "Title": "Security Defaults enabled on Entra ID", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Microsoft Entra ID Security Defaults offer preconfigured security settings designed to protect organizations from common identity attacks at no additional cost. These settings enforce basic security measures such as MFA registration, risk-based authentication prompts, and blocking legacy authentication clients that do not support MFA. Security defaults are available to all organizations and can be enabled via the Azure portal to strengthen authentication security.", + "AdditionalInformation": "Security defaults provide built-in protections to reduce the risk of unauthorized access until organizations configure their own identity security policies. By requiring MFA, blocking weak authentication methods, and adapting authentication challenges based on risk factors, these settings create a stronger security foundation without additional licensing requirements.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.1.2", + "Description": "Ensure that 'Multi-Factor Auth Status' is 'Enabled' for all Privileged Users", + "Checks": [ + "entra_privileged_user_has_mfa" + ], + "Attributes": [ + { + "Title": "MFA enabled for privileged users", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "[IMPORTANT] If your organization has Microsoft Entra ID licensing (included in Microsoft 365 E3, E5, F5, or EM&S E3/E5) and can use Conditional Access, you may skip this section and proceed to Conditional Access. Enable multi-factor authentication (MFA) for all users, roles, and groups with write access to Azure resources. This includes both custom-created roles and built-in roles such as: Service Co-Administrators, Subscription Owners, Contributors", + "AdditionalInformation": "MFA enhances security by requiring two or more authentication factors, ensuring that only authorized users gain access. It significantly reduces the risk of unauthorized access, credential theft, and privilege escalation. By implementing MFA, attackers must compromise multiple authentication mechanisms, making breaches far more difficult and improving overall Azure resource security.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.1.3", + "Description": "Ensure that 'Multi-Factor Auth Status' is 'Enabled' for all Non-Privileged Users", + "Checks": [ + "entra_non_privileged_user_has_mfa" + ], + "Attributes": [ + { + "Title": "MFA enabled for non-privileged users", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "[IMPORTANT] If your organization has Microsoft Entra ID licensing (included in Microsoft 365 E3, E5, F5, or EM&S E3/E5) and can use Conditional Access, you may skip this section and proceed to Conditional Access. Enable multi-factor authentication (MFA) for all non-privileged users to enhance security and prevent unauthorized access.", + "AdditionalInformation": "MFA strengthens authentication by requiring at least two verification factors, making it significantly harder for attackers to gain access using stolen credentials. Even if one authentication factor is compromised, an additional layer of security reduces the risk of unauthorized account access, enhancing overall identity and access management security.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.1.4", + "Description": "Ensure Multi-factor Authentication is Required for Windows Azure Service Management API", + "Checks": [ + "entra_conditional_access_policy_require_mfa_for_management_api" + ], + "Attributes": [ + { + "Title": "MFA Required for Windows Azure Service Management API", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "This recommendation ensures that users accessing the Windows Azure Service Management API (e.g., Azure PowerShell, Azure CLI, Azure Resource Manager API) are required to authenticate using multi-factor authentication (MFA) before accessing resources.", + "AdditionalInformation": "Administrative access to the Azure Service Management API should be secured with enhanced authentication measures to prevent unauthorized changes. Enforcing MFA helps mitigate the risk of credential compromise, privilege abuse, and unauthorized modifications to administrative settings. IMPORTANT: While exceptions for specific users or groups may be configured, they should be carefully tracked and periodically reviewed through an Access Review process. The policy should apply to all users by default, ensuring that only explicitly exempted accounts bypass MFA.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.1.5", + "Description": "Ensure Multi-factor Authentication is Required to access Microsoft Admin Portals", + "Checks": [ + "defender_ensure_defender_for_server_is_on" + ], + "Attributes": [ + { + "Title": "MFA Required to access Microsoft Admin Portals", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "This recommendation ensures that users accessing Microsoft Admin Portals (such as Microsoft 365 Admin, Microsoft 365 Defender, Exchange Admin Center, and Azure Portal) must authenticate using multi-factor authentication (MFA) before logging in.", + "AdditionalInformation": "Microsoft Admin Portals provide privileged access to critical settings and resources, requiring enhanced security measures. Enforcing MFA reduces the risk of unauthorized access, credential compromise, and administrative abuse, preventing intruders from making unauthorized changes to administrative settings.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.1.6", + "Description": "Ensure only MFA enabled identities can access privileged Virtual Machine", + "Checks": [ + "entra_user_with_vm_access_has_mfa" + ], + "Attributes": [ + { + "Title": "Only MFA enabled identities can access privileged Virtual Machine", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Ensure that privileged virtual machines do not allow logins from identities without multi-factor authentication (MFA) and that access is restricted to necessary permissions only. Unauthorized access can enable attackers to move laterally and misuse the virtual machine’s managed identity for further privilege escalation or unauthorized operations.", + "AdditionalInformation": "Requiring MFA for privileged VM access reduces the risk of credential compromise, lateral movement, and unauthorized access to cloud resources. Attackers can exploit valid accounts to access virtual machines and escalate privileges using the managed identity. Enforcing MFA and least privilege access helps mitigate these risks by preventing unauthorized logins and restricting administrative permissions to only what is necessary.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.2.1", + "Description": "Ensure that 'Restrict non-admin users from creating tenants' is set to 'Yes'", + "Checks": [ + "entra_policy_ensure_default_user_cannot_create_tenants" + ], + "Attributes": [ + { + "Title": "Restrict non-admin users from creating tenants is set to Yes", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Restrict the ability to create new Microsoft Entra ID or Azure AD B2C tenants to administrators or appropriately delegated users.", + "AdditionalInformation": "Limiting tenant creation to authorized administrators prevents unauthorized users from creating new tenants, reducing the risk of uncontrolled environments, misconfigurations, and potential security gaps. This ensures that only approved users can establish and manage tenant resources.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.2", + "Description": "Ensure fewer than 5 users have global administrator assignment", + "Checks": [ + "entra_global_admin_in_less_than_five_users" + ], + "Attributes": [ + { + "Title": "Fewer than 5 users have global administrator assignment", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "To maintain security and operational efficiency, it is recommended to have a minimum of two and a maximum of four users assigned the Global Administrator role in Microsoft Entra ID. This ensures redundancy while minimizing the risk of excessive privileged access.", + "AdditionalInformation": "The Global Administrator role holds broad privileges across Microsoft Entra ID services and should not be used for daily tasks. Administrators should have separate accounts for regular activities and privileged actions. Limiting the number of Global Administrators reduces the risk of unauthorized access, human error, and privilege misuse, while ensuring at least two administrators are available to prevent disruptions in case of unavailability. This approach aligns with the principle of least privilege and strengthens the security posture of an Azure tenant.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.3", + "Description": "Ensure That 'Guest users access restrictions' is set to 'Guest user access is restricted to properties and memberships of their own directory objects'", + "Checks": [ + "entra_policy_guest_users_access_restrictions" + ], + "Attributes": [ + { + "Title": "Guest users access restrictions' is set to 'Guest user access is restricted to properties and memberships of their own directory objects'", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Restrict guest user permissions to prevent unauthorized access to directory resources and administrative roles.", + "AdditionalInformation": "Limiting guest access ensures that guest accounts cannot enumerate users, groups, or directory objects and prevents them from being assigned administrative roles. Guest access has three levels of restriction, with the most secure option being: “Guest user access is restricted to their own directory object” (most restrictive). This setting minimizes the risk of unauthorized access and ensures guests only interact with their assigned resources.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.4", + "Description": "Ensure `User consent for applications` is set to `Do not allow user consent` ", + "Checks": [ + "entra_policy_restricts_user_consent_for_apps" + ], + "Attributes": [ + { + "Title": "`User consent for applications` is set to `Do not allow user consent` ", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Require administrators to provide consent before applications can access Microsoft Entra ID resources, preventing unauthorized or malicious app integrations.", + "AdditionalInformation": "Allowing users to grant application permissions without restrictions increases the risk of data exfiltration and privilege abuse by malicious applications. Restricting app consent to administrators ensures that only verified and approved applications gain access, protecting sensitive data and privileged accounts from unauthorized use.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.5", + "Description": "Ensure ‘User consent for applications’ Is Set To ‘Allow for Verified Publishers’", + "Checks": [ + "entra_policy_user_consent_for_verified_apps" + ], + "Attributes": [ + { + "Title": "‘User consent for applications’ Is Set To ‘Allow for Verified Publishers’", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Allow users to grant consent only for selected permissions when the request comes from a verified publisher, ensuring tighter control over third-party application access.", + "AdditionalInformation": "Restricting app consent to verified publishers helps mitigate the risk of unauthorized data access and privilege abuse. Malicious applications may attempt to exploit user-granted permissions, so ensuring only trusted, verified applications can request access enhances security while maintaining flexibility for approved integrations.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.2.6", + "Description": "Ensure Trusted Locations Are Defined", + "Checks": [ + "entra_trusted_named_locations_exists" + ], + "Attributes": [ + { + "Title": "Trusted Locations Are Defined", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Microsoft Entra ID Conditional Access allows organizations to define Named Locations and categorize them as trusted or untrusted. These locations can be based on geographical regions, specific IP addresses, or IP ranges to enhance access control policies.", + "AdditionalInformation": "Defining trusted IP addresses or ranges enables organizations to enforce Conditional Access policies based on user location. Users authenticating from trusted locations may receive fewer access restrictions, while those from untrusted sources can be subject to stricter security controls, reducing the risk of unauthorized access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.1", + "Description": "Ensure that 'Users can create security groups in Azure portals, API or PowerShell' is set to 'No'", + "Checks": [ + "entra_policy_default_users_cannot_create_security_groups" + ], + "Attributes": [ + { + "Title": "Users can create security groups in Azure portals, API or PowerShell' is set to 'No'", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Limit the ability to create security groups to administrators only, preventing unauthorized users from managing group memberships.", + "AdditionalInformation": "Allowing all users to create and manage security groups can lead to uncontrolled access, misconfigurations, and potential security risks. Unless business requirements justify broader delegation, restricting security group creation to administrators ensures that group permissions and memberships are properly managed, reducing the risk of unauthorized access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.2", + "Description": "Ensure That ‘Users Can Register Applications’ Is Set to ‘No’", + "Checks": [ + "entra_policy_ensure_default_user_cannot_create_apps" + ], + "Attributes": [ + { + "Title": "‘Users Can Register Applications’ Is Set to ‘No’", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Restrict the ability to register third-party applications to administrators or appropriately delegated users, ensuring better security control over app integrations.", + "AdditionalInformation": "Allowing unrestricted application registration can expose Microsoft Entra ID data to security risks. Requiring administrator approval ensures that custom-developed applications undergo a formal security review before being granted access. Organizations may delegate permissions to developers or high-request users as needed, but policies should be reviewed to align with security best practices and operational needs.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.3", + "Description": "Ensure that 'Guest invite restrictions' is set to 'Only users assigned to specific admin roles can invite guest users'", + "Checks": [ + "entra_policy_guest_invite_only_for_admin_roles" + ], + "Attributes": [ + { + "Title": "Guest invite restrictions' is set to 'Only users assigned to specific admin roles can invite guest users'", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Limit the ability to send invitations to users with specific administrative roles, preventing unauthorized guest access to cloud resources.", + "AdditionalInformation": "Restricting guest invitations to designated administrators helps enforce “Need to Know” permissions, reducing the risk of unintended data exposure. By default, anyone in the organization—including guests and non-admins—can invite external users, which can lead to unauthorized access. Restricting this capability ensures that only approved accounts can extend access to the tenant, strengthening security and access control.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "1.3.4", + "Description": "Ensure that 'Users can create Microsoft 365 groups in Azure portals, API or PowerShell' is set to 'No'", + "Checks": [ + "entra_users_cannot_create_microsoft_365_groups" + ], + "Attributes": [ + { + "Title": "Users can create Microsoft 365 groups in Azure portals, API or PowerShell' is set to 'No'", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Limit Microsoft 365 group creation to administrators only, ensuring that group management remains under centralized control.", + "AdditionalInformation": "Restricting group creation to administrators prevents unauthorized or unnecessary group creation, ensuring that only appropriate groups are created and managed. This reduces the risk of misconfigurations, access issues, and uncontrolled resource sharing within the organization.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.1", + "Description": "Ensure that RDP access from the Internet is evaluated and restricted", + "Checks": [ + "network_rdp_internet_access_restricted" + ], + "Attributes": [ + { + "Title": "RDP access from the Internet is evaluated and restricted", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive rules, especially those exposing ports and protocols to the public internet. Any unnecessary exposure should be restricted to reduce security risks.", + "AdditionalInformation": "Exposing Remote Desktop Protocol (RDP) or other critical services to the internet increases the risk of brute-force attacks, which could lead to unauthorized access to Azure Virtual Machines. Compromised VMs can be used to launch attacks on other networked resources, both inside and outside Azure. Periodic NSG evaluations help minimize attack surfaces and ensure that only explicitly required access is permitted.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.1.2", + "Description": "Ensure that SSH access from the Internet is evaluated and restricted", + "Checks": [ + "network_ssh_internet_access_restricted" + ], + "Attributes": [ + { + "Title": "SSH access from the Internet is evaluated and restricted", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive rules, particularly those exposing SSH (port 22) or other critical services to the internet. Any unnecessary exposure should be restricted to reduce security risks.", + "AdditionalInformation": "Exposing SSH over the internet increases the risk of brute-force attacks, allowing attackers to gain unauthorized access to Azure Virtual Machines. Once compromised, a VM can be used as a pivot point to attack other machines within the Azure Virtual Network or even external systems. Periodic NSG evaluations help minimize attack surfaces and ensure that only explicitly required access is permitted.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.1.3", + "Description": "Ensure that UDP access from the Internet is evaluated and restricted", + "Checks": [ + "network_udp_internet_access_restricted" + ], + "Attributes": [ + { + "Title": "UDP access from the Internet is evaluated and restricted", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive UDP rules, especially those exposing UDP-based services to the internet. Any unnecessary exposure should be restricted to prevent security risks.", + "AdditionalInformation": "Exposing UDP services to the internet increases the risk of Distributed Denial-of-Service (DDoS) amplification attacks, where attackers can reflect and amplify spoofed traffic from Azure Virtual Machines. Commonly exploited UDP services include DNS, NTP, SSDP, SNMP, and CLDAP, which can be used to disrupt services or launch attacks against other networked systems inside and outside Azure. Regular NSG evaluations help minimize attack surfaces and prevent VMs from being leveraged in DDoS attacks.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.4", + "Description": "Ensure that HTTP(S) access from the Internet is evaluated and restricted", + "Checks": [ + "network_http_internet_access_restricted" + ], + "Attributes": [ + { + "Title": "HTTP(S) access from the Internet is evaluated and restricted", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive HTTP(S) rules, ensuring that exposure to the internet is necessary and narrowly configured. Restrict access where it is not explicitly required.", + "AdditionalInformation": "Exposing HTTP(S) services to the internet increases the risk of brute-force attacks, credential stuffing, and exploitation of vulnerabilities in web applications or services. If compromised, an attacker can use the affected resource as a pivot point to escalate privileges, move laterally, and compromise other Azure resources within the tenant. Periodic NSG evaluations help reduce attack surfaces and enforce least privilege access.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.5", + "Description": "Ensure an Azure Bastion Host Exists", + "Checks": [ + "network_bastion_host_exists" + ], + "Attributes": [ + { + "Title": "Ensure Azure Bastion Host Exists", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Enable Azure Bastion to securely access Azure Virtual Machines (VMs) over the internet without exposing RDP (3389/TCP) or SSH (22/TCP) ports directly to the public network. Azure Bastion provides remote access through TLS (443/TCP) while integrating with Azure Active Directory (AAD) security policies.", + "AdditionalInformation": "Using Azure Bastion eliminates the need to assign public IP addresses to VMs, reducing exposure to brute-force attacks and unauthorized access. It enhances security by enabling browser-based RDP/SSH access, leveraging Azure Active Directory controls, and supporting Multi-Factor Authentication (MFA), Conditional Access Policies, and other hardening measures for a centralized and secure access solution.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.1", + "Description": "Ensure that Microsoft Entra authentication is Configured for SQL Servers", + "Checks": [ + "sqlserver_azuread_administrator_enabled" + ], + "Attributes": [ + { + "Title": "Microsoft Entra authentication is Configured for SQL Servers", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Use Microsoft Entra authentication for SQL Database to centralize identity and credential management, enhancing security and simplifying access control. By leveraging Microsoft Entra ID, organizations can manage database users, permissions, and authentication policies in a single location, reducing complexity and improving security.", + "AdditionalInformation": "Microsoft Entra authentication eliminates the need for separate SQL authentication, preventing identity sprawl and simplifying password management. It enables centralized permission management, supports token-based authentication, integrates with Active Directory Federation Services (ADFS), and enhances security with Multi-Factor Authentication (MFA). Using Entra ID authentication also eliminates the need to store passwords, enabling secure, modern authentication methods while ensuring seamless access control for users and applications.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.2", + "Description": "Ensure 'Allow public access from any Azure service within Azure to this server' for PostgreSQL flexible server is disabled ", + "Checks": [ + "postgresql_flexible_server_allow_access_services_disabled" + ], + "Attributes": [ + { + "Title": "Allow public access from any Azure service within Azure to this server' for PostgreSQL flexible server is disabled ", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Disable access from Azure services to PostgreSQL flexible servers to restrict inbound connections and enhance security.", + "AdditionalInformation": "Allowing access from all Azure services bypasses firewall restrictions and permits connections from any Azure resource, including those outside your subscription. This can expose the database to unauthorized access and security risks. Instead, configure firewall rules or Virtual Network (VNet) rules to allow only specific, trusted network ranges, ensuring tighter access control.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.3", + "Description": "Ensure That 'Firewalls & Networks' Is Limited to Use Selected Networks Instead of All Networks", + "Checks": [ + "cosmosdb_account_firewall_use_selected_networks" + ], + "Attributes": [ + { + "Title": "Firewalls & Networks' Is Limited to Use Selected Networks Instead of All Networks", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Restrict Cosmos DB access to whitelisted networks to minimize exposure and reduce potential attack vectors.", + "AdditionalInformation": "Limiting Cosmos DB communication to specific trusted networks prevents unauthorized access from unapproved sources, including the public internet. This enhances security by reducing the attack surface and ensuring only designated networks can interact with the database.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.4", + "Description": "Ensure That Private Endpoints Are Used Where Possible", + "Checks": [ + "cosmosdb_account_use_private_endpoints" + ], + "Attributes": [ + { + "Title": "Private Endpoints Are Used", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Use private endpoints for Cosmos DB to restrict network traffic to approved sources, ensuring secure and private communication.", + "AdditionalInformation": "For sensitive data, private endpoints provide granular control over which services can access Cosmos DB, preventing exposure to unauthorized networks. This setup ensures that all traffic remains within a private network, reducing security risks and enhancing data protection.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.5", + "Description": "Use Entra ID Client Authentication and Azure RBAC where possible", + "Checks": [ + "cosmosdb_account_use_aad_and_rbac" + ], + "Attributes": [ + { + "Title": "Entra ID Client Authentication and Azure RBAC where possible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Use Microsoft Entra ID for Cosmos DB client authentication, leveraging Azure RBAC for enhanced security, centralized management, and MFA support.", + "AdditionalInformation": "Entra ID authentication is significantly more secure than token-based authentication, as tokens must be stored persistently on the client, increasing the risk of compromise. Entra ID eliminates this risk by securely handling credentials, integrating seamlessly with Azure RBAC, and supporting multi-factor authentication (MFA) for stronger access control.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.6", + "Description": "Ensure that 'Public Network Access' is 'Disabled' for storage accounts", + "Checks": [ + "storage_blob_public_access_level_is_disabled" + ], + "Attributes": [ + { + "Title": "Public Network Access' is 'Disabled' for storage accounts", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Disable public network access for Azure Storage accounts, ensuring that access settings for individual containers cannot override this restriction. This applies to Azure Resource Manager deployment model storage accounts, as classic deployment model accounts will be retired by August 31, 2024.", + "AdditionalInformation": "By default, Azure Storage accounts allow users with appropriate permissions to enable public access to containers and blobs, granting read-only access without requiring authentication. To minimize the risk of unauthorized data exposure, public access should be restricted unless explicitly required. Instead, use Azure AD RBAC or shared access signatures (SAS) to grant controlled and time-limited access to storage resources.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.7", + "Description": "Ensure Default Network Access Rule for Storage Accounts is Set to Deny", + "Checks": [ + "storage_default_network_access_rule_is_denied" + ], + "Attributes": [ + { + "Title": "Default Network Access Rule for Storage Accounts is Set to Deny", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Restricting default network access enhances security by preventing unrestricted connections to Azure Storage accounts. By default, storage accounts accept connections from any network, so access should be limited to selected networks by modifying the default configuration.", + "AdditionalInformation": "Storage accounts should deny access from all networks by default, except for explicitly allowed Azure Virtual Networks or specific IP address ranges. This approach creates a secure network boundary, ensuring only authorized applications can connect. Even when network rules allow access, proper authentication (via access keys or SAS tokens) is still required, adding an extra layer of security.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.8", + "Description": "Ensure 'Allow Azure services on the trusted services list to access this storage account' is Enabled for Storage Account Access", + "Checks": [ + "storage_ensure_azure_services_are_trusted_to_access_is_enabled" + ], + "Attributes": [ + { + "Title": "Allow Azure services on the trusted services list to access this storage account' is Enabled for Storage Account Access", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "This recommendation assumes that public network access is set to “Enabled from selected virtual networks and IP addresses” and that the default network access rule for storage accounts is set to deny. Some Azure services require access to storage accounts from networks that cannot be explicitly granted permissions through network rules. To allow these services to function properly, enable the trusted Azure services exception, which allows selected Azure services to bypass network restrictions while still enforcing strong authentication.", + "AdditionalInformation": "Enabling firewall rules on a storage account blocks access to all incoming requests, including those from other Azure services. Allowing access to trusted Azure services ensures that essential Azure functions, such as Azure Backup, Event Grid, Monitor, and Site Recovery, can interact with storage accounts securely without exposing them to broader public access.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.9", + "Description": "Ensure Private Endpoints are used to access Storage Accounts", + "Checks": [ + "storage_ensure_private_endpoints_in_storage_accounts" + ], + "Attributes": [ + { + "Title": "Private Endpoint used to access Storage Accounts", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Use private endpoints for Azure Storage accounts to enable secure, encrypted access over a Private Link. Private endpoints assign an IP address from the Virtual Network (VNet) for each service, ensuring that network traffic remains isolated and encrypted within the VNet. This configuration helps segment network traffic, preventing external access while allowing secure communication between services. Additionally, VNets can extend addressing spaces or act as secure tunnels over public networks, connecting remote infrastructures securely.", + "AdditionalInformation": "Encrypting traffic between services protects sensitive data from interception and unauthorized access. Using private endpoints ensures that data remains within a controlled network environment, reducing exposure to external threats and enhancing overall security posture.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.2.10", + "Description": "Ensure no Azure SQL Databases allow ingress from 0.0.0.0/0", + "Checks": [ + "sqlserver_unrestricted_inbound_access" + ], + "Attributes": [ + { + "Title": "Azure SQL Databases do not allow ingress from 0.0.0.0/0", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Ensure that Azure SQL Databases do not allow ingress from 0.0.0.0/0 (ANY IP), as this would expose them to the public internet. Azure SQL Server includes a firewall that blocks unauthorized connections by default, but it allows defining more granular IP address rules to restrict access. Allowing 0.0.0.0/0 effectively grants open access, increasing security risks.", + "AdditionalInformation": "Leaving Azure SQL firewall rules open to ANY IP significantly increases the attack surface, making databases vulnerable to brute-force attacks and unauthorized access. Instead, firewall rules should be restricted to specific, trusted IP ranges from known datacenters or internal resources. Additionally, enabling Allow Azure services and resources to access this server could allow access from outside your organization’s subscription or region, potentially bypassing SQL Server’s network ACLs and exposing the database to malicious activity.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.3.1", + "Description": "Ensure App Service Authentication is set up for apps in Azure App Service", + "Checks": [ + "app_ensure_auth_is_set_up" + ], + "Attributes": [ + { + "Title": "App Service Authentication is set up for apps in Azure App Service", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Enable Azure App Service Authentication to restrict anonymous HTTP requests and enforce authentication using identity providers before requests reach the application.", + "AdditionalInformation": "Enabling App Service Authentication ensures that all incoming HTTP requests are validated and authenticated before being processed by the application. It integrates with Microsoft Entra ID, Google, Facebook, Microsoft Accounts, and Twitter to manage authentication, token validation, and session handling. Additionally, disabling HTTP Basic Authentication helps mitigate risks from legacy authentication methods, strengthening application security.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.3.2", + "Description": "Ensure 'FTP State' is set to 'FTPS Only' or 'Disabled' ", + "Checks": [ + "app_ftp_deployment_disabled" + ], + "Attributes": [ + { + "Title": "FTP State' is set to 'FTPS Only' or 'Disabled' ", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Ensure that FTP access is disabled for Azure App Services, or if required, enforce FTPS (FTP over SSL/TLS) for secure authentication and data transmission.", + "AdditionalInformation": "FTP transmits data, including credentials, in plaintext, making it vulnerable to interception, credential theft, and data exfiltration. Requiring FTPS ensures that all FTP connections are encrypted, reducing the risk of unauthorized access, persistence, and lateral movement within the environment. If FTP is not needed, disabling it entirely enhances security by eliminating an unnecessary attack vector.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.1.1", + "Description": "Ensure server parameter 'log_checkpoints' is set to 'ON' for PostgreSQL flexible server", + "Checks": [ + "postgresql_flexible_server_log_checkpoints_on" + ], + "Attributes": [ + { + "Title": "log_checkpoints' is set to 'ON' for PostgreSQL flexible server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable log_checkpoints on PostgreSQL flexible servers to log checkpoint activity, generating query and error logs that aid in monitoring and troubleshooting database performance.", + "AdditionalInformation": "Logging checkpoints helps track database activity, errors, and performance issues, providing valuable insights for troubleshooting and optimization. While transaction logs remain inaccessible, query and error logs allow identification and resolution of configuration errors and inefficiencies, improving database reliability and performance.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.2", + "Description": "Ensure server parameter 'connection_throttle.enable' is set to 'ON' for PostgreSQL flexible server", + "Checks": [ + "postgresql_flexible_server_connection_throttling_on" + ], + "Attributes": [ + { + "Title": "connection_throttle.enable' is set to 'ON' for PostgreSQL flexible server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable connection throttling on PostgreSQL flexible servers to regulate concurrent connections and generate logs for monitoring and troubleshooting.", + "AdditionalInformation": "Connection throttling helps prevent Denial of Service (DoS) attacks by limiting excessive connections that could exhaust resources. It also protects against system failures or degraded performance caused by high user loads. Query and error logs provide insights into connection issues, enabling faster troubleshooting and performance optimization.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.3", + "Description": "Ensure server parameter 'log_connections' is set to 'ON' for PostgreSQL single server", + "Checks": [ + "postgresql_flexible_server_log_connections_on" + ], + "Attributes": [ + { + "Title": "log_connections' is set to 'ON' for PostgreSQL single server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable log_connections on PostgreSQL Single Servers to record connection attempts and authentication events for security monitoring and auditing. (Note: This recommendation applies only to Single Server, as Azure PostgreSQL Single Server is planned for retirement.)", + "AdditionalInformation": "Logging connection attempts helps detect unauthorized access, failed authentication attempts, and potential security threats. These logs provide valuable insights for troubleshooting, auditing, and identifying suspicious activities, enhancing overall database security and monitoring.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.4", + "Description": "Ensure server parameter 'log_disconnections' is set to 'ON' for PostgreSQL single server", + "Checks": [ + "postgresql_flexible_server_log_disconnections_on" + ], + "Attributes": [ + { + "Title": "log_disconnections' is set to 'ON' for PostgreSQL single server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable log_disconnections on PostgreSQL Servers to record session termination events, including session duration. (Note: This recommendation applies only to Single Server, as Azure PostgreSQL Single Server is planned for retirement.)", + "AdditionalInformation": "Logging disconnections provides visibility into session activity and duration, helping to analyze user behavior, detect anomalies, and troubleshoot performance issues. These logs contribute to audit trails, security monitoring, and database performance optimization.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.5", + "Description": "Ensure server parameter 'audit_log_enabled' is set to 'ON' for MySQL flexible server", + "Checks": [ + "mysql_flexible_server_audit_log_enabled" + ], + "Attributes": [ + { + "Title": "audit_log_enabled' is set to 'ON' for MySQL flexible server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable audit_log_enabled on MySQL flexible servers to capture logs for connection attempts, DDL/DML activity, and other database events for security and monitoring purposes.", + "AdditionalInformation": "Logging database activity helps detect unauthorized access, troubleshoot issues, and analyze performance bottlenecks. Enabling audit logs enhances security visibility, compliance monitoring, and incident response by providing valuable insights into database operations.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.6", + "Description": "Ensure server parameter 'audit_log_events' has 'CONNECTION' set for MySQL flexible server", + "Checks": [ + "mysql_flexible_server_audit_log_connection_activated" + ], + "Attributes": [ + { + "Title": "audit_log_events' has 'CONNECTION' set for MySQL flexible server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Configure audit_log_events on MySQL flexible servers to include CONNECTION events, ensuring that successful and failed connection attempts are logged.", + "AdditionalInformation": "Logging CONNECTION events provides visibility into authentication attempts, helping to detect unauthorized access, troubleshoot issues, and optimize database performance. These logs are essential for security monitoring, compliance, and identifying potential misconfigurations or attack attempts.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.7", + "Description": "Ensure that logging for Azure Key Vault is 'Enabled' ", + "Checks": [ + "keyvault_logging_enabled" + ], + "Attributes": [ + { + "Title": "Logging for Azure Keyvault enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable AuditEvent logging for Azure Key Vault to track and log interactions with keys, certificates, and other sensitive information.", + "AdditionalInformation": "Logging Key Vault access events provides an audit trail that helps monitor who accessed the vault, when, and how. This enhances security, compliance, and incident response by ensuring logs are stored in a designated Azure Storage account or Log Analytics workspace, allowing centralized monitoring across multiple Key Vaults.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.8", + "Description": "Ensure that Network Security Group Flow logs are captured and sent to Log Analytics", + "Checks": [ + "network_flow_log_captured_sent" + ], + "Attributes": [ + { + "Title": "Network Security Group Flow logs are captured and sent to Log Analytics", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Ensure that network flow logs are collected and sent to a central Log Analytics workspace for monitoring and analysis.", + "AdditionalInformation": "Capturing network flow logs provides visibility into traffic patterns across your network, helping detect anomalies, potential lateral movement, and security threats. These logs integrate with Azure Monitor and Azure Sentinel, enabling advanced analytics and visualization for improved network security and incident response.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.9", + "Description": "Ensure that logging for Azure AppService 'HTTP logs' is enabled", + "Checks": [ + "app_http_logs_enabled" + ], + "Attributes": [ + { + "Title": "Logging for Azure AppService 'HTTP logs' is enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enable AppServiceHTTPLogs for Azure App Service instances to capture and centrally log all HTTP requests.", + "AdditionalInformation": "Logging web requests provides critical data for security monitoring and incident response. Captured logs can be ingested into a SIEM or central log aggregation system, helping security analysts detect anomalies, investigate threats, and enhance application security.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.2.1", + "Description": "Ensure that 'Auditing' Retention is 'greater than 90 days'", + "Checks": [ + "sqlserver_auditing_retention_90_days" + ], + "Attributes": [ + { + "Title": "Auditing' Retention is 'greater than 90 days'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "Configure SQL Server Audit Retention to retain logs for more than 90 days to ensure long-term visibility into database activity and security events.", + "AdditionalInformation": "Maintaining audit logs for over 90 days helps detect anomalies, security breaches, and unauthorized access. Longer retention periods allow organizations to analyze historical data, support compliance requirements, and strengthen forensic investigations.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.2.1", + "Description": "Ensure that Network Security Group Flow Log retention period is 'greater than 90 days'", + "Checks": [ + "network_flow_log_more_than_90_days" + ], + "Attributes": [ + { + "Title": "Network Security Group Flow Log retention period is 'greater than 90 days'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "Enable Network Security Group (NSG) Flow Logs and configure the retention period to at least 90 days to capture and store IP traffic data for security monitoring and analysis.", + "AdditionalInformation": "NSG Flow Logs provide visibility into network traffic, helping detect anomalies, unauthorized access, and potential security breaches. Retaining logs for at least 90 days ensures that historical data is available for incident investigation, compliance, and forensic analysis, strengthening overall network security monitoring.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.2.2", + "Description": "Ensure server parameter 'logfiles.retention_days' is greater than 3 days for PostgreSQL flexible server", + "Checks": [ + "postgresql_flexible_server_log_retention_days_greater_3" + ], + "Attributes": [ + { + "Title": "logfiles.retention_days' is greater than 3 days for PostgreSQL flexible server", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "Ensure that logfiles.retention_days on PostgreSQL flexible servers is set to an appropriate value to maintain access to historical logs for monitoring and troubleshooting.", + "AdditionalInformation": "Setting an appropriate log retention period ensures that query and error logs are available for diagnosing configuration issues, troubleshooting errors, and optimizing performance. Retaining logs for a sufficient duration supports security analysis, compliance requirements, and operational debugging while preventing unnecessary storage consumption.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.2.3", + "Description": "Ensure that a 'Diagnostic Setting' exists for Subscription Activity Logs", + "Checks": [ + "monitor_diagnostic_settings_exists" + ], + "Attributes": [ + { + "Title": "Diagnostic Setting' exists for Subscription Activity Logs", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "Enable Diagnostic Settings to export activity logs for all appropriate resources within a subscription, ensuring long-term visibility into security and operational events.", + "AdditionalInformation": "By default, logs are retained for only 90 days. Configuring diagnostic settings allows logs to be exported and stored for extended periods, enabling security analysis, compliance monitoring, and forensic investigations within an Azure subscription.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.1", + "Description": "Ensure that 'Auditing' is set to 'On' ", + "Checks": [ + "sqlserver_auditing_enabled" + ], + "Attributes": [ + { + "Title": "Auditing' is set to 'On'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Enable auditing on Azure SQL Servers to track and log database events for security and compliance purposes.", + "AdditionalInformation": "Auditing at the server level ensures that all existing and newly created databases inherit auditing policies, providing consistent monitoring across the SQL environment. It does not override database-level auditing policies but complements them. Audit logs are stored in Azure Storage, helping organizations maintain regulatory compliance, monitor database activity, and detect anomalies that may indicate security threats or operational concerns.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.10", + "Description": "Ensure that Activity Log Alert exists for Delete SQL Server Firewall Rule", + "Checks": [ + "monitor_alert_delete_sqlserver_fr" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Delete SQL Server Firewall Rule", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Delete SQL Server Firewall Rule event to monitor and track firewall rule removals in SQL Server.", + "AdditionalInformation": "Monitoring firewall rule deletions helps detect unauthorized or accidental changes that could expose the database to security risks. Timely alerts ensure quick detection and response to prevent unauthorized network access and maintain a secure SQL environment.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.11", + "Description": "Ensure that Activity Log Alert exists for Create or Update Public IP Address rule", + "Checks": [ + "monitor_alert_create_update_public_ip_address_rule" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Create or Update Public IP Address rule", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Create or Update Public IP Address event to monitor changes in public IP configurations.", + "AdditionalInformation": "Tracking public IP address modifications provides visibility into network access changes, helping detect unauthorized or misconfigured public exposure. Timely alerts enable faster detection and response to potential security risks, ensuring a controlled and secure network environment.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.3.12", + "Description": "Ensure that Activity Log Alert exists for Delete Public IP Address rule", + "Checks": [ + "monitor_alert_delete_public_ip_address_rule" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Delete Public IP Address rule", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Delete Public IP Address event to monitor and track the removal of public IP addresses.", + "AdditionalInformation": "Monitoring public IP deletions helps detect unauthorized or accidental changes that could impact network accessibility or security. Timely alerts enable quick investigation and response, ensuring that critical network configurations remain intact and secure.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.13", + "Description": "Ensure Application Insights are Configured", + "Checks": [ + "appinsights_ensure_is_configured" + ], + "Attributes": [ + { + "Title": "Application Insights are Configured", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Application Insights in Azure serves as an Application Performance Monitoring (APM) solution, providing valuable insights into application performance, telemetry data, and trace logs. It helps organizations monitor application health and troubleshoot incidents efficiently.", + "AdditionalInformation": "Enabling Application Insights enhances security monitoring and performance optimization by collecting metrics, telemetry, and trace logs. These logs aid in proactive performance tuning to reduce costs and support incident response by helping identify the root cause of potential security or operational issues. Integrating Application Insights into a broader logging and monitoring strategy strengthens an organization’s overall observability and security posture.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.14", + "Description": "Ensure that Network Watcher is 'Enabled' for Azure Regions that are in use", + "Checks": [ + "network_watcher_enabled" + ], + "Attributes": [ + { + "Title": "Network Watcher 'Enabled' for used Regions", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Enable Network Watcher for all Azure regions within your subscription to monitor and diagnose network activity.", + "AdditionalInformation": "Network Watcher provides network diagnostic and visualization tools that help organizations analyze traffic, troubleshoot connectivity issues, and detect anomalies. Enabling it enhances network visibility, security monitoring, and operational insights across Azure environments.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.15", + "Description": "Ensure that Endpoint Protection for all Virtual Machines is installed", + "Checks": [ + "defender_assessments_vm_endpoint_protection_installed" + ], + "Attributes": [ + { + "Title": "Endpoint Protection for all Virtual Machines is installed", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Ensure endpoint protection is installed on all Azure Virtual Machines (VMs) to provide real-time security monitoring and threat detection.", + "AdditionalInformation": "Endpoint protection helps detect and remove malware, viruses, and other security threats, protecting VMs from compromise and unauthorized access. It also provides configurable alerts for suspicious activity, enhancing security monitoring and incident response across Azure environments.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.16", + "Description": "Ensure that Microsoft Defender Recommendation for 'Apply system updates' status is 'Completed'", + "Checks": [ + "defender_ensure_system_updates_are_applied" + ], + "Attributes": [ + { + "Title": "Defender Recommendation for 'Apply system updates' status is 'Completed'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Ensure that all Windows and Linux Virtual Machines (VMs) have the latest OS patches and security updates applied to mitigate vulnerabilities and improve system stability.", + "AdditionalInformation": "Keeping VMs updated helps address security vulnerabilities, bug fixes, and stability improvements. Microsoft Defender for Cloud continuously checks for missing critical and security updates using Windows Update, WSUS, or Linux package managers. Applying recommended updates reduces the risk of exploits, malware, and performance issues, ensuring a secure and resilient cloud environment.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.17", + "Description": "Ensure that Microsoft Cloud Security Benchmark policies are not set to 'Disabled'", + "Checks": [ + "policy_ensure_asc_enforcement_enabled" + ], + "Attributes": [ + { + "Title": "Microsoft Cloud Security Benchmark policies are not set to 'Disabled'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Ensure that all Microsoft Cloud Security Benchmark (MCSB) policies are enabled to effectively evaluate resource configurations against best practice security recommendations.", + "AdditionalInformation": "The MCSB Policy Initiative enforces security best practices and helps ensure compliance with organizational and regulatory requirements. If a policy’s Effect is set to Disabled, it will not be evaluated, potentially preventing administrators from detecting security misconfigurations. Keeping all MCSB policies enabled allows Microsoft Defender for Cloud to assess relevant resources and provide actionable security insights to strengthen cloud security.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.3.18", + "Description": "Ensure That 'All users with the following roles' is set to 'Owner'", + "Checks": [ + "defender_ensure_notify_emails_to_owners" + ], + "Attributes": [ + { + "Title": "All users with the following roles' is set to 'Owner'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Ensure that security alert emails are enabled for subscription owners to receive notifications about potential security threats and vulnerabilities.", + "AdditionalInformation": "Enabling security alert emails ensures that subscription owners are promptly informed of security incidents, threats, or misconfigurations detected by Microsoft Defender for Cloud. Timely notifications allow administrators to take immediate action, mitigate risks, and maintain a strong security posture within the Azure environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.3.19", + "Description": "Ensure 'Additional email addresses' is Configured with a Security Contact Email", + "Checks": [ + "defender_additional_email_configured_with_a_security_contact" + ], + "Attributes": [ + { + "Title": "Additional email addresses' is Configured with a Security Contact Email", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Configure Microsoft Defender for Cloud to send high-severity security alerts to a designated security contact email in addition to the subscription owner.", + "AdditionalInformation": "By default, Microsoft Defender for Cloud notifies only subscription owners of critical security alerts. Adding a security contact email ensures that the security team is promptly informed of potential threats, allowing for faster response and risk mitigation to maintain a secure Azure environment.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.2", + "Description": "Ensure Diagnostic Setting captures appropriate categories", + "Checks": [ + "monitor_diagnostic_setting_with_appropriate_categories" + ], + "Attributes": [ + { + "Title": "Diagnostic Setting captures appropriate categories", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Ensure that Diagnostic Settings are configured to log relevant control and management plane activities for enhanced visibility and security monitoring. (Note: A Diagnostic Setting must exist for this configuration to be available.)", + "AdditionalInformation": "Diagnostic settings determine how logs are exported and stored. Capturing logs from the control and management plane enables proper alerting, monitoring, and analysis of administrative actions, helping to detect unauthorized changes and security threats.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.20", + "Description": "Ensure That 'Notify about alerts with the following severity' is Set to 'High'", + "Checks": [ + "defender_ensure_notify_alerts_severity_is_high" + ], + "Attributes": [ + { + "Title": "Notify about alerts with the following severity' is Set to 'High'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Enable security alert emails to notify the subscription owner or designated security contacts about potential security threats.", + "AdditionalInformation": "Ensuring security alerts are sent to the appropriate personnel allows for timely detection and response to security incidents. This helps mitigate risks by ensuring that critical threats and vulnerabilities are addressed promptly, improving the overall security posture of the Azure environment.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.21", + "Description": "Ensure That Microsoft Defender for DNS Is Set To 'On'", + "Checks": [ + "defender_ensure_defender_for_dns_is_on" + ], + "Attributes": [ + { + "Title": "Defender for DNS Is Set To 'On'", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Enable Microsoft Defender for DNS to monitor and scan outgoing DNS traffic within a subscription for potential security threats. (Note: As of August 1, 2023, new subscribers receive DNS threat alerts as part of Defender for Servers P2.)", + "AdditionalInformation": "Defender for DNS analyzes DNS queries against a dynamic threat intelligence list to detect potential malicious activity, compromised services, or security breaches. Monitoring DNS lookups helps identify and prevent threats such as data exfiltration, command and control (C2) communications, and phishing attacks, improving overall network security.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.3", + "Description": "Ensure that Activity Log Alert exists for Create Policy Assignment", + "Checks": [ + "monitor_alert_create_policy_assignment" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Create Policy Assignment", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an activity log alert for the Create Policy Assignment event to track changes in Azure Policy assignments.", + "AdditionalInformation": "Monitoring policy assignment events helps detect unauthorized or unintended changes in Azure policies, providing greater visibility and reducing the time required to identify and respond to policy modifications.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.4", + "Description": "Ensure that Activity Log Alert exists for Delete Policy Assignment", + "Checks": [ + "monitor_alert_delete_policy_assignment" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Delete Policy Assignment", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an activity log alert for the Delete Policy Assignment event to monitor and track policy removal activities in Azure Policy assignments.", + "AdditionalInformation": "Monitoring policy deletions helps detect unauthorized or unintended changes, ensuring policy integrity and reducing the time required to identify and respond to security or compliance violations.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.5", + "Description": "Ensure that Activity Log Alert exists for Create or Update Network Security Group", + "Checks": [ + "monitor_alert_create_update_nsg" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Create or Update Network Security Group", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Create or Update Network Security Group (NSG) event to track changes in network security configurations.", + "AdditionalInformation": "Monitoring NSG creation and updates provides visibility into network access changes, helping to detect unauthorized modifications and identify potential security risks in a timely manner.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.6", + "Description": "Ensure that Activity Log Alert exists for Delete Network Security Group", + "Checks": [ + "monitor_alert_delete_nsg" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Delete Network Security Group", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Delete Network Security Group (NSG) event to monitor and track network security group removals.", + "AdditionalInformation": "Monitoring NSG deletions provides visibility into network access changes, helping to detect unauthorized modifications and identify potential security threats before they impact the environment.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.7", + "Description": "Ensure that Activity Log Alert exists for Create or Update Security Solution", + "Checks": [ + "monitor_alert_create_update_security_solution" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Create or Update Security Solution", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Create or Update Security Solution event to track modifications to security solutions in your environment.", + "AdditionalInformation": "Monitoring security solution changes provides visibility into updates, additions, or modifications to active security configurations. This helps detect unauthorized changes or suspicious activity and ensures that security controls remain properly configured.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.3.8", + "Description": "Ensure that Activity Log Alert exists for Delete Security Solution", + "Checks": [ + "monitor_alert_delete_security_solution" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Delete Security Solution", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Delete Security Solution event to monitor and track the removal of security solutions in your environment.", + "AdditionalInformation": "Monitoring security solution deletions helps detect unauthorized removals or suspicious activity, ensuring that critical security controls are not accidentally or maliciously disabled. This improves security visibility and helps maintain a strong security posture.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.9", + "Description": "Ensure that Activity Log Alert exists for Create or Update SQL Server Firewall Rule", + "Checks": [ + "monitor_alert_create_update_sqlserver_fr" + ], + "Attributes": [ + { + "Title": "Activity Log Alert exists for Create or Update SQL Server Firewall Rule", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Create an Activity Log Alert for the Create or Update SQL Server Firewall Rule event to track changes in SQL Server firewall configurations.", + "AdditionalInformation": "Monitoring firewall rule modifications provides visibility into network access changes, helping detect unauthorized updates that could expose the database to security risks. Timely alerts can reduce the time needed to identify and respond to suspicious activity, ensuring a secure SQL environment.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "4.1.1", + "Description": "Ensure server parameter 'require_secure_transport' is set to 'ON' for PostgreSQL flexible server", + "Checks": [ + "postgresql_flexible_server_enforce_ssl_enabled" + ], + "Attributes": [ + { + "Title": "require_secure_transport' is set to 'ON' for PostgreSQL flexible server", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Enable require_secure_transport on PostgreSQL flexible servers to enforce SSL connections between the database server and client applications, ensuring encrypted communication.", + "AdditionalInformation": "Requiring SSL encryption helps protect data in transit from man-in-the-middle attacks by securing the connection between the database server and client applications. Enforcing secure transport prevents unauthorized access and strengthens data integrity and confidentiality.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.2", + "Description": "Ensure server parameter 'require_secure_transport' is set to 'ON' for MySQL flexible server", + "Checks": [ + "postgresql_flexible_server_enforce_ssl_enabled" + ], + "Attributes": [ + { + "Title": "require_secure_transport' is set to 'ON' for MySQL flexible server", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Enable require_secure_transport on MySQL flexible servers to enforce SSL encryption between the database server and client applications, ensuring secure communication.", + "AdditionalInformation": "Requiring SSL connectivity protects data in transit from man-in-the-middle attacks by encrypting communication between client applications and the database server. Enforcing secure transport helps prevent unauthorized access and data interception, strengthening the overall security posture of the database.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.3", + "Description": "Ensure the 'Minimum TLS version' for storage accounts is set to 'Version 1.2'", + "Checks": [ + "storage_ensure_minimum_tls_version_12" + ], + "Attributes": [ + { + "Title": "Minimum TLS version' for storage accounts is set to 'Version 1.2'", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Ensure that the minimum TLS version for Azure Storage is set to TLS 1.2 or higher to mitigate security vulnerabilities associated with older TLS versions.", + "AdditionalInformation": "TLS 1.0 is outdated and has known security vulnerabilities that can expose data in transit to attacks and interception. Upgrading to TLS 1.2 or later enhances encryption security, ensuring secure communication between clients and Azure Storage while reducing the risk of data compromise.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "4.1.4", + "Description": "Ensure 'HTTPS Only' is set to `On`", + "Checks": [ + "app_ensure_http_is_redirected_to_https" + ], + "Attributes": [ + { + "Title": "`HTTPS Only' is set to `On`", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Configure Azure App Service to enforce HTTPS-only traffic, ensuring that all HTTP requests are automatically redirected to HTTPS for secure communication.", + "AdditionalInformation": "Enforcing HTTPS protects data in transit by using TLS/SSL encryption, preventing man-in-the-middle attacks and ensuring secure authentication. Redirecting non-secure HTTP traffic to HTTPS enhances the confidentiality and integrity of application communications, reducing exposure to security vulnerabilities.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.5", + "Description": "Ensure Web App is using the latest version of TLS encryption", + "Checks": [ + "app_minimum_tls_version_12" + ], + "Attributes": [ + { + "Title": "Web App is using the latest version of TLS encryption", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Ensure that Azure App Service is configured to use TLS 1.2 or higher to secure data transmission and align with industry security standards.", + "AdditionalInformation": "TLS 1.0 and 1.1 are outdated protocols with known vulnerabilities that expose web applications to security threats, including data interception and man-in-the-middle attacks. TLS 1.2 is the recommended encryption standard by industry frameworks such as PCI DSS, providing stronger encryption and improved security for web app connections.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.6", + "Description": "Ensure that 'HTTP20enabled' is set to 'true'", + "Checks": [ + "app_ensure_using_http20" + ], + "Attributes": [ + { + "Title": "HTTP20enabled' is set to 'true'", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Ensure that Azure App Service is configured to use the latest supported HTTP version to benefit from security improvements, performance enhancements, and new functionalities.", + "AdditionalInformation": "Newer HTTP versions provide security enhancements, performance optimizations, and better resource management. HTTP/2, for example, improves efficiency by addressing issues such as head-of-line blocking, header compression, and request prioritization. Keeping apps updated to the latest HTTP version ensures they leverage modern security protocols and enhanced data transmission capabilities while maintaining compatibility and performance standards.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "4.2.1", + "Description": "Ensure Storage for Critical Data are Encrypted with Customer Managed Keys (CMK)", + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys" + ], + "Attributes": [ + { + "Title": "Storage for Critical Data are Encrypted CMK", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Enable encryption at rest using Customer Managed Keys (CMK) instead of Microsoft Managed Keys to maintain greater control over sensitive data encryption in Azure Storage accounts.", + "AdditionalInformation": "By default, Azure encrypts all storage resources (blobs, disks, files, queues, and tables) using Microsoft Managed Keys. However, using Customer Managed Keys (CMK) allows organizations to control, manage, and rotate their encryption keys through Azure Key Vault, ensuring compliance with security policies. CMKs also support automatic key version updates for enhanced security. Since this recommendation applies specifically to storage accounts holding critical data, its assessment remains manual rather than automated.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.2.2", + "Description": "Ensure SQL server's Transparent Data Encryption (TDE) protector is encrypted with Customer-managed key", + "Checks": [ + "sqlserver_tde_encrypted_with_cmk" + ], + "Attributes": [ + { + "Title": "SQL server's TDE protector is encrypted with CMK", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Enable Transparent Data Encryption (TDE) with Customer-Managed Keys (CMK) to enhance control, security, and separation of duties over the TDE Protector. TDE encrypts data at rest using a database encryption key (DEK). Traditionally, DEKs were protected by Azure SQL Service-managed certificates, but with Customer-Managed Key support, the DEK can now be secured using an asymmetric key stored in Azure Key Vault. Azure Key Vault provides centralized key management, FIPS 140-2 Level 2 validated HSMs, and additional security through key and data separation. Based on business needs and data sensitivity, organizations should use Customer-Managed Keys for TDE encryption.", + "AdditionalInformation": "Using Customer-Managed Keys for TDE gives organizations full control over encryption keys, including who can access them and when. Azure Key Vault serves as a secure external key management system, ensuring that TDE encryption keys remain protected from unauthorized access. When configured, the asymmetric key is set at the server level and inherited by all databases under that server, providing consistent encryption management across the environment.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.2.3", + "Description": "Ensure that 'Data encryption' is set to 'On' on a SQL Database", + "Checks": [ + "sqlserver_tde_encryption_enabled" + ], + "Attributes": [ + { + "Title": "Data encryption' is set to 'On' on a SQL Database", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Enable Transparent Data Encryption (TDE) on all Azure SQL Servers to ensure real-time encryption and decryption of databases, backups, and transaction logs at rest. TDE operates seamlessly without requiring modifications to applications.", + "AdditionalInformation": "TDE helps protect Azure SQL Databases from unauthorized access and malicious activity by encrypting data at rest. This ensures that database files, backups, and logs remain secure, reducing the risk of data breaches while maintaining operational transparency.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "4.2.4", + "Description": "Ensure the storage account containing the container with activity logs is encrypted with Customer Managed Key (CMK)", + "Checks": [ + "monitor_storage_account_with_activity_logs_cmk_encrypted" + ], + "Attributes": [ + { + "Title": "Storage account with logs encrypted with CMK", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Configure storage accounts used for activity log exports to use Customer Managed Keys (CMK) for enhanced security and access control.", + "AdditionalInformation": "Using CMKs for log storage provides additional confidentiality controls, ensuring that only users with read access to the storage account and explicit decrypt permissions can access the logs. This enhances data security by restricting unauthorized access and strengthening compliance with encryption and access management policies.", + "LevelOfRisk": 3 + } + ] + } + ] +} diff --git a/prowler/compliance/gcp/prowler_threatscore_gcp.json b/prowler/compliance/gcp/prowler_threatscore_gcp.json new file mode 100644 index 0000000000..2ccb7e7add --- /dev/null +++ b/prowler/compliance/gcp/prowler_threatscore_gcp.json @@ -0,0 +1,977 @@ +{ + "Framework": "ProwlerThreatScore", + "Version": "1.0", + "Provider": "GCP", + "Description": "Prowler ThreatScore Compliance Framework for GCP ensures that the GCP project is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + "Requirements": [ + { + "Id": "1.1.1", + "Description": "Ensure User-Managed/External Keys for Service Accounts Are Rotated Every 90 Days or Fewer", + "Checks": [ + "iam_sa_user_managed_key_rotate_90_days" + ], + "Attributes": [ + { + "Title": "User-Managed/External Keys for Service Accounts Are Rotated Every 90 Days or Fewer", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "Service account keys consist of a key ID (private_key_id) and a private key, which are used to authenticate programmatic requests to Google Cloud services. It is recommended to regularly rotate service account keys to enhance security and reduce the risk of unauthorized access.", + "AdditionalInformation": "Regularly rotating service account keys minimizes the risk of a compromised, lost, or stolen key being used to access cloud resources. Google-managed keys are automatically rotated daily for internal authentication, ensuring strong security. For user-managed (external) keys, users are responsible for key security, storage, and rotation. Since Google does not retain private keys once generated, proper key management practices must be followed. Google Cloud allows up to 10 external keys per service account, making it easier to rotate them without disruption. Implementing regular key rotation ensures that old keys are not left active, reducing the potential attack surface.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.1.2", + "Description": "Ensure API Keys Only Exist for Active Services", + "Checks": [ + "apikeys_key_exists" + ], + "Attributes": [ + { + "Title": "API Keys Only Exist for Active Services", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "API keys should only be used when no other authentication method is available, as they pose significant security risks. Unused API keys with active permissions may still exist within a project, potentially exposing resources to unauthorized access. It is recommended to use standard authentication flows such as OAuth 2.0 or service account authentication instead.", + "AdditionalInformation": "API keys are inherently insecure because they: Are simple encrypted strings that can be easily exposed in browsers, client-side applications, or devices. Do not authenticate users or applications making API requests. Can be accidentally leaked in logs, repositories, or web traffic.To enhance security, API keys should be avoided when possible, and unused keys should be deleted to minimize the risk of unauthorized access.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.1.3", + "Description": "Ensure API Keys Are Rotated Every 90 Days", + "Checks": [ + "apikeys_key_rotated_in_90_days" + ], + "Attributes": [ + { + "Title": "API Keys Are Rotated Every 90 Days", + "Section": "1. IAM", + "SubSection": "1.1 Authentication", + "AttributeDescription": "API keys should only be used when no other authentication method is available. If API keys are in use, it is recommended to rotate them every 90 days to minimize security risks.", + "AdditionalInformation": "API keys are inherently insecure because: They are simple encrypted strings that can be easily exposed. They do not authenticate users or applications making API requests. They are often accessible to clients, increasing the risk of theft and misuse. Unlike credentials with expiration policies, stolen API keys remain valid indefinitely unless revoked or regenerated. Regularly rotating API keys reduces the risk of unauthorized access by ensuring that compromised keys cannot be used for extended periods. To enhance security, API keys should be rotated every 90 days or as part of a proactive security policy.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.1", + "Description": "Ensure That There Are Only GCP-Managed Service Account Keys for Each Service Account", + "Checks": [ + "iam_sa_no_user_managed_keysiam_sa_no_user_managed_keys" + ], + "Attributes": [ + { + "Title": "Only GCP-Managed Service Account Keys for Each Service Account", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Service accounts should not use user-managed keys, as they introduce security risks and require manual management. Instead, use Google Cloud-managed keys, which are automatically rotated and secured by Google.", + "AdditionalInformation": "User-managed keys are downloadable and manually managed, making them vulnerable to leaks, mismanagement, and unauthorized access. In contrast, GCP-managed keys are non-downloadable, automatically rotated weekly, and securely handled by Google Cloud services like App Engine and Compute Engine. Managing user-generated keys requires key storage, distribution, rotation, revocation, and protectionall of which introduce potential security gaps. Common risks include keys being exposed in source code repositories, left in unsecured locations, or unintentionally shared. To minimize security risks, it is recommended to disable user-managed service account keys and rely on GCP-managed keys instead.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.2", + "Description": "Ensure That Service Account Has No Admin Privileges", + "Checks": [ + "iam_sa_no_administrative_privileges" + ], + "Attributes": [ + { + "Title": "SA Has No Admin Privileges", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "A service account is a special Google account assigned to an application or virtual machine (VM) rather than an individual user. It is used to authenticate API requests on behalf of the application. Service accounts should not be granted admin privileges to minimize security risks.", + "AdditionalInformation": "Service accounts control resource access based on their assigned roles. Granting admin privileges to a service account allows full control over applications or VMs, enabling actions like deletion, updates, and configuration changes without user intervention. This increases the risk of misconfigurations, privilege escalation, or potential security breaches. To follow the principle of least privilege, it is recommended to restrict admin access for service accounts and assign only the necessary permissions.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.3", + "Description": "Ensure That Cloud KMS Cryptokeys Are Not Anonymously or Publicly Accessible", + "Checks": [ + "kms_key_not_publicly_accessible" + ], + "Attributes": [ + { + "Title": "Cloud KMS Cryptokeys Are Not Anonymously or Publicly Accessible", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "The IAM policy on Cloud KMS cryptographic keys should not allow anonymous (allUsers) or public (allAuthenticatedUsers) access to prevent unauthorized key usage.", + "AdditionalInformation": "Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the cryptographic keys, which can lead to data exposure, unauthorized encryption/decryption operations, or potential key compromise. This is particularly critical if sensitive data is protected using these keys. To maintain data security and compliance, ensure that Cloud KMS cryptographic keys are only accessible to authorized users, groups, or service accounts and do not have public or anonymous access permissions.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.4", + "Description": "Ensure KMS Encryption Keys Are Rotated Within a Period of 90 Days", + "Checks": [ + "kms_key_rotation_enabled" + ], + "Attributes": [ + { + "Title": "KMS Encryption Keys Are Rotated Within a Period of 90 Days", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "Google Cloud Key Management Service (KMS) organizes cryptographic keys in a hierarchical structure to facilitate secure and efficient access control. Keys should be configured with a defined rotation schedule to ensure their cryptographic strength is maintained over time.", + "AdditionalInformation": "Key rotation ensures that new key versions are automatically generated at regular intervals, reducing the risk of key compromise and unauthorized access. The key material (actual encryption bits) changes over time, even though the keys logical identity remains the same. Since cryptographic keys protect sensitive data, setting a specific rotation period ensures that encrypted data remains secure, minimizes the impact of a potential key leak, and aligns with best security practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.5", + "Description": "Ensure That Separation of Duties Is Enforced While Assigning KMS Related Roles to Users", + "Checks": [ + "iam_role_kms_enforce_separation_of_duties" + ], + "Attributes": [ + { + "Title": "Separation of Duties Is Enforced While Assigning KMS Related Roles to Users", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "The principle of Separation of Duties should be enforced when assigning Google Cloud Key Management Service (KMS) roles to users. This prevents excessive privileges and reduces security risks.", + "AdditionalInformation": "The Cloud KMS Admin role grants the ability to create, delete, and manage keys, while the Cloud KMS CryptoKey Encrypter/Decrypter, Encrypter, and Decrypter roles control encryption and decryption of data. Granting both administrative and cryptographic privileges to the same user violates the Separation of Duties principle, potentially allowing unauthorized access to sensitive data. To mitigate risks and prevent privilege escalation, no user should hold the Cloud KMS Admin role along with any of the CryptoKey Encrypter/Decrypter roles. Enforcing Separation of Duties helps ensure secure key management and aligns with security best practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.2.6", + "Description": "Ensure API Keys Are Restricted to Only APIs That Application Needs Access", + "Checks": [ + "apikeys_api_restrictions_configured" + ], + "Attributes": [ + { + "Title": "API Keys Are Restricted to Only APIs That Application Needs Access", + "Section": "1. IAM", + "SubSection": "1.2 Authorization", + "AttributeDescription": "API keys should only be used when no other authentication method is available, as they pose a higher security risk due to their public visibility. To minimize exposure, API keys should be restricted to access only the specific APIs required by an application.", + "AdditionalInformation": "API keys present several security risks, including: They are simple encrypted strings that can be easily exposed in client-side applications or browsers. They do not authenticate the user or application making API requests. They are often accessible to clients, making them susceptible to discovery and theft. Google recommends using standard authentication methods instead of API keys whenever possible. However, in limited scenarios where API keys are necessary (e.g., mobile applications using Google Cloud Translation API without a backend server), restricting API key access to only the required APIs helps enforce least privilege access and reduces attack surfaces.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "1.3.1", + "Description": "Ensure That IAM Users Are Not Assigned the Service Account User or Service Account Token Creator Roles at Project Level", + "Checks": [ + "iam_no_service_roles_at_project_level" + ], + "Attributes": [ + { + "Title": "IAM Users Are Not Assigned the SA User or SA Token Creator Roles at Project Level", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "It is recommended to assign the Service Account User (iam.serviceAccountUser) and Service Account Token Creator (iam.serviceAccountTokenCreator) roles to users at the service account level rather than granting them project-wide access.", + "AdditionalInformation": "Service accounts are identities used by applications and virtual machines (VMs) to interact with Google Cloud APIs. They also function as resources with IAM policies defining who can use them. Granting service account permissions at the project level allows users to access all service accounts within the project, including any created in the future. This increases the risk of privilege escalation, as users with Compute Instance Admin or App Engine Deployer roles could execute code as a service account, gaining access to additional resources. To enforce the principle of least privilege, users should be assigned service account roles at the specific service account level rather than at the project level. This ensures that each user has access only to the necessary service accounts while preventing unintended privilege escalation.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.3.2", + "Description": "Ensure That Separation of Duties Is Enforced While Assigning Service Account Related Roles to Users", + "Checks": [ + "iam_role_kms_enforce_separation_of_duties" + ], + "Attributes": [ + { + "Title": "Separation of Duties Is Enforced While Assigning Service Account Related Roles to Users", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "It is recommended to enforce the principle of Separation of Duties when assigning service account-related IAM roles to users to prevent excessive privileges and security risks.", + "AdditionalInformation": "The Service Account Admin role allows a user to create, delete, and manage service accounts, while the Service Account User role allows a user to assign service accounts to applications or compute instances. Granting both roles to the same user violates the Separation of Duties principle, as it would allow an individual to create and assign service accounts, potentially leading to unauthorized access or privilege escalation. To minimize security risks, no user should be assigned both Service Account Admin and Service Account User roles simultaneously. Enforcing Separation of Duties ensures better access control, reduces the risk of privilege abuse, and aligns with security best practices.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.3.3", + "Description": "Ensure That Cloud Audit Logging Is Configured Properly", + "Checks": [ + "iam_audit_logs_enabled" + ], + "Attributes": [ + { + "Title": "Cloud Audit Logging Is Configured Properly", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Cloud Audit Logging should be configured to track all administrative activities and read/write access to user data. This ensures comprehensive visibility into who accessed or modified resources within a project, folder, or organization.", + "AdditionalInformation": "Cloud Audit Logging maintains two types of audit logs: 1. Admin Activity Logs Captures API calls and administrative actions that modify configurations or metadata. These logs are enabled by default and cannot be disabled. 2. Data Access Logs Tracks API calls that create, modify, or read user data. These logs are disabled by default and should be enabled for better monitoring. Data Access Logs provide three types of visibility: Admin Read Tracks metadata or configuration reads. Data Read Logs operations where user-provided data is accessed. Data Write Captures modifications to user-provided data.To ensure effective logging, it is recommended to: 1. Enable DATA_READ logs (for user activity tracking) and DATA_WRITE logs (to track modifications). 2. Apply audit logging to all supported services where Data Access logs are available. 3.Avoid exempting users from audit logs to maintain full tracking capabilities. Properly configuring Cloud Audit Logging helps strengthen security, detect unauthorized access, and ensure compliance with security policies.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.3.4", + "Description": "Ensure Log Metric Filter and Alerts Exist for Project Ownership Assignments/Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for Project Ownership Assignments/Changes", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "In order to prevent unnecessary project ownership assignments to users or service accounts and mitigate potential misuse of projects and resources, all role assignments to roles/Owner should be monitored. Users or service accounts assigned the roles/Owner primitive role are considered project owners. The Owner role grants full control over the project, including: full viewer permissions on all GCP services, permissions to modify the state of all services, manage roles and permissions for the project and its resources, and set up billing for the project. Granting the Owner role allows the member to modify the IAM policy, which contains sensitive access control data. To minimize security risks, the Owner role should only be assigned when strictly necessary, and the number of users with this role should be kept to a minimum.", + "AdditionalInformation": "Project ownership has the highest level of privileges within a project, making it a high-risk role if misused. To reduce potential security risks, all project ownership assignments and changes should be monitored and alerted to security teams or relevant recipients. Critical events to monitor include: sending project ownership invitations, acceptance or rejection of ownership invites, assigning the roles/Owner role to a user or service account, and removing a user or service account from the roles/Owner role. Monitoring these activities helps prevent unauthorized access, enforces least privilege principles, and improves security auditing and compliance.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.3.5", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Audit Configuration Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for Audit Configuration Changes", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "Google Cloud Platform (GCP) services generate audit log entries in the Admin Activity and Data Access logs, providing visibility into who performed what action, where, and when within GCP projects. These logs capture key details such as the identity of the API caller, timestamp, source IP address, request parameters, and response data. Cloud audit logging records API calls made through the GCP Console, SDKs, command-line tools, and other GCP services, offering a comprehensive activity history for security monitoring and compliance.", + "AdditionalInformation": "Admin activity and data access logs play a critical role in security analysis, resource change tracking, and compliance auditing. Configuring metric filters and alerts for audit configuration changes ensures that audit logging remains in its recommended state, allowing organizations to detect and respond to unauthorized modifications while ensuring all project activities remain fully auditable at any time.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "1.3.6", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Custom Role Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for Custom Role Changes", + "Section": "1. IAM", + "SubSection": "1.3 Privilege Escalation Prevention", + "AttributeDescription": "It is recommended to set up a metric filter and alarm to track changes to Identity and Access Management (IAM) roles, including their creation, deletion, and updates. Google Cloud IAM provides predefined roles for granular access control but also allows organizations to create custom roles to meet specific needs.", + "AdditionalInformation": "IAM role modifications can impact security by granting excessive privileges if not properly managed. Monitoring role creation, deletion, and updates helps detect potential misconfigurations or over-privileged roles early, ensuring that only intended access permissions are assigned within the organization.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.1.1", + "Description": "Ensure That the Default Network Does Not Exist in a Project ", + "Checks": [ + "compute_network_default_in_use" + ], + "Attributes": [ + { + "Title": "Default Network Does Not Exist in a Project ", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "A project should not have a default network to prevent the use of preconfigured and potentially insecure network settings.", + "AdditionalInformation": "The default network automatically creates permissive firewall rules, including unrestricted internal traffic, SSH, RDP, and ICMP access, which increases the risk of unauthorized access. Additionally, it is an auto mode network, limiting flexibility in subnet configuration and restricting the use of Cloud VPN or VPC Network Peering. Organizations should create a custom network tailored to their security and networking needs and remove the default network to minimize exposure.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.1.2", + "Description": "Ensure Legacy Networks Do Not Exist for Older Projects", + "Checks": [ + "compute_network_not_legacy" + ], + "Attributes": [ + { + "Title": "Legacy Networks Do Not Exist for Older Projects", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "Projects should not have a legacy network configured to prevent the use of outdated and inflexible networking models. While new projects can no longer create legacy networks, older projects should be checked to ensure they are not still using them.", + "AdditionalInformation": "Legacy networks use a single global IPv4 prefix and a single gateway IP for the entire network, lacking subnetting capabilities. This design limits flexibility, prevents migration to auto or custom subnet networks, and can create performance bottlenecks or single points of failure for high-traffic workloads. Removing legacy networks and transitioning to modern networking models improves scalability, security, and resilience.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.1.4", + "Description": "Ensure That SSH Access Is Restricted From the Internet", + "Checks": [ + "compute_firewall_ssh_access_from_the_internet_allowed" + ], + "Attributes": [ + { + "Title": "SSH Access Is Restricted From the Internet", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "GCP Firewall Rules control ingress and egress traffic within a VPC Network. These rules define traffic conditions such as ports, protocols, and source/destination IPs. Firewall rules operate at the VPC level and cannot be shared across networks. Only IPv4 addresses are supported, and it is crucial to restrict generic (0.0.0.0/0) incoming traffic, particularly for SSH on Port 22, to prevent unauthorized access.", + "AdditionalInformation": "Firewall rules regulate traffic flow between instances and external networks. Allowing unrestricted inbound SSH access (0.0.0.0/0 on port 22) increases security risks by exposing instances to unauthorized access and brute-force attacks. To minimize threats, internet-facing access should be limited by specifying granular IP ranges and enforcing least privilege access.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.1.5", + "Description": "Ensure That RDP Access Is Restricted From the Internet", + "Checks": [ + "compute_firewall_rdp_access_from_the_internet_allowed" + ], + "Attributes": [ + { + "Title": "RDP Access Is Restricted From the Internet", + "Section": "2. Attack Surface", + "SubSection": "2.1 Network", + "AttributeDescription": "GCP Firewall Rules control incoming (ingress) and outgoing (egress) traffic within a VPC Network. Each rule specifies traffic conditions, including ports, protocols, and source/destination IPs. These rules operate at the VPC level, cannot be shared across networks, and support only IPv4 addresses. To enhance security, unrestricted RDP access (0.0.0.0/0 on port 3389) should be avoided to prevent unauthorized remote connections.", + "AdditionalInformation": "Firewall rules regulate traffic flow between instances and external networks. Allowing unrestricted RDP access from the Internet exposes virtual machines (VMs) to unauthorized access and brute-force attacks. To mitigate risks, internet-facing access should be restricted by enforcing least privilege access, defining specific IP ranges, and implementing secure remote access solutions such as Bastion hosts or VPNs.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.1", + "Description": "Ensure That Cloud Storage Bucket Is Not Anonymously or Publicly Accessible", + "Checks": [ + "cloudstorage_bucket_public_access" + ], + "Attributes": [ + { + "Title": "Cloud Storage Bucket Is Not Anonymously or Publicly Accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "IAM policies on Cloud Storage buckets should not allow anonymous or public access to prevent unauthorized data exposure.", + "AdditionalInformation": "Granting public or anonymous access allows anyone to access the buckets contents, posing a security risk, especially if sensitive data is stored. Restricting access ensures that only authorized users can interact with the bucket, reducing the risk of data breaches.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.2", + "Description": "Ensure 'user Connections' Database Flag for Cloud Sql Sql Server Instance Is Set to a Non-limiting Value", + "Checks": [ + "cloudsql_instance_sqlserver_user_connections_flag" + ], + "Attributes": [ + { + "Title": "user Connections Database Flag for Cloud Sql Sql Server Instance Is Set to a Non-limiting Value", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Verify the user connection limits for Cloud SQL SQL Server instances to ensure they are not unnecessarily restricting the number of simultaneous connections.", + "AdditionalInformation": "The user connections setting controls the maximum number of concurrent user connections allowed on an SQL Server instance. By default, SQL Server dynamically adjusts the number of connections as needed, up to a maximum of 32,767. Setting an artificial limit may prevent new connections from being established, leading to potential data loss or service outages. It is recommended to review and adjust this setting as necessary to avoid disruptions.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "2.2.3", + "Description": "Ensure 'remote access' database flag for Cloud SQL SQL Server instance is set to 'off'", + "Checks": [ + "cloudsql_instance_sqlserver_remote_access_flag" + ], + "Attributes": [ + { + "Title": "remote access database flag for Cloud SQL SQL Server instance is set to off", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Disable the remote access database flag for Cloud SQL SQL Server instances to prevent execution of stored procedures from remote servers.", + "AdditionalInformation": "The remote access option allows stored procedures to be executed from or on remote SQL Server instances. By default, this setting is enabled, which could be exploited for unauthorized query execution or Denial-of-Service (DoS) attacks by offloading processing to a target server. Disabling remote access enhances security by restricting stored procedure execution to the local server, reducing potential attack vectors. This recommendation applies to SQL Server database instances.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "2.2.5", + "Description": "Ensure That Cloud SQL Database Instances Do Not Implicitly Whitelist All Public IP Addresses", + "Checks": [ + "cloudsql_instance_public_access" + ], + "Attributes": [ + { + "Title": "Cloud SQL Database Instances Do Not Implicitly Whitelist All Public IP Addresses", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Restrict database server access to only trusted networks and IP addresses, preventing connections from public IPs.", + "AdditionalInformation": "Allowing unrestricted access to a database server increases the risk of unauthorized access and attacks. To minimize the attack surface, only trusted and necessary IP addresses should be whitelisted. Authorized networks should not be set to 0.0.0.0/0, which permits connections from anywhere. This control applies specifically to instances with public IP addresses.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.2.6", + "Description": "Ensure That Cloud SQL Database Instances Do Not Have Public IPs", + "Checks": [ + "cloudsql_instance_public_access" + ], + "Attributes": [ + { + "Title": "Cloud SQL Database Instances Do Not Have Public IPs", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Configure Second Generation Cloud SQL instances to use private IPs instead of public IPs.", + "AdditionalInformation": "Using private IPs for Cloud SQL databases enhances security by reducing exposure to external threats. It also improves network performance and lowers latency by keeping traffic within the internal network, minimizing the attack surface of the database.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "2.2.7", + "Description": "Ensure That BigQuery Datasets Are Not Anonymously or Publicly Accessible", + "Checks": [ + "bigquery_dataset_public_access" + ], + "Attributes": [ + { + "Title": "BigQuery Datasets Are Not Anonymously or Publicly Accessible", + "Section": "2. Attack Surface", + "SubSection": "2.2 Storage", + "AttributeDescription": "Ensure that IAM policies on BigQuery datasets do not allow anonymous or public access.", + "AdditionalInformation": "Granting access to allUsers or allAuthenticatedUsers permits unrestricted access to the dataset, which can lead to unauthorized data exposure. To protect sensitive information, public or anonymous access should be strictly prohibited.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.3", + "Description": "Ensure That Instances Are Not Configured To Use the Default Service Account With Full Access to All Cloud APIs", + "Checks": [ + "compute_instance_default_service_account_in_use_with_full_api_access" + ], + "Attributes": [ + { + "Title": "Instances Are Not Configured To Use the Default Service Account With Full Access to All Cloud APIs", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "To enforce the principle of least privilege and prevent potential privilege escalation, instances should not be assigned the Compute Engine default service account with the scope Allow full access to all Cloud APIs.", + "AdditionalInformation": "Google Compute Engine provides a default service account for instances to access necessary cloud services. This default service account has the Project Editor role, granting broad permissions over most cloud services except billing. When assigned to an instance, it can operate in three modes: 1.Allow default access Grants minimal required permissions (recommended). 2.Allow full access to all Cloud APIs Grants excessive access to all cloud services (not recommended). 3.Set access for each API Allows administrators to specify required APIs (preferred for least privilege). Assigning an instance the Compute Engine default service account with full access to all APIs can expose cloud operations to unauthorized users based on IAM roles. To reduce security risks, instances should use custom service accounts with minimal required permissions.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.3.4", + "Description": "Ensure Block Project-Wide SSH Keys Is Enabled for VM Instances ", + "Checks": [ + "compute_instance_block_project_wide_ssh_keys_disabled" + ], + "Attributes": [ + { + "Title": "Block Project-Wide SSH Keys Is Enabled for VM Instances ", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Instances should use instance-specific SSH keys instead of project-wide SSH keys to enhance security and reduce the risk of unauthorized access.", + "AdditionalInformation": "Project-wide SSH keys are stored in Compute Project metadata and can be used to access all instances within a project. While this simplifies SSH key management, it also increases security risksif a project-wide SSH key is compromised, all instances in the project could be affected. Using instance-specific SSH keys provides better security by limiting access to individual instances, reducing the attack surface in case of key compromise.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.5", + "Description": "Ensure Enable Connecting to Serial Ports Is Not Enabled for VM Instance", + "Checks": [ + "compute_instance_serial_ports_in_use" + ], + "Attributes": [ + { + "Title": "Enable Connecting to Serial Ports Is Not Enabled for VM Instance", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "The interactive serial console allows direct access to a virtual machines serial ports, similar to using a terminal window. When enabled, it allows connections from any IP address, creating a potential security risk. It is recommended to disable interactive serial console support.", + "AdditionalInformation": "A virtual machine instance has four virtual serial ports, often used by the operating system, BIOS, or other system-level entities for input and output. The first serial port (serial port 1) is commonly referred to as the serial console. Unlike SSH, the interactive serial console does not support IP-based access restrictions, meaning anyone with the correct SSH key, username, project ID, zone, and instance name could gain access. This exposes the instance to unauthorized access. To mitigate this risk, interactive serial console support should be disabled unless absolutely necessary.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "2.3.6", + "Description": "Ensure That IP Forwarding Is Not Enabled on Instances", + "Checks": [ + "compute_instance_ip_forwarding_is_enabled" + ], + "Attributes": [ + { + "Title": "IP Forwarding Is Not Enabled on Instances", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Google Compute Engine instances should not forward data packets unless explicitly required for routing purposes. By default, an instance cannot forward packets unless the source IP matches the instances IP address. Similarly, GCP wont deliver packets if the destination IP does not match the instance. To prevent unauthorized data forwarding, it is recommended to disable IP forwarding.", + "AdditionalInformation": "When IP forwarding is enabled (canIpForward field), an instance can send and receive packets with non-matching source or destination IPs, effectively allowing it to act as a network router. This can lead to data loss, information disclosure, or unauthorized traffic routing. To maintain security and prevent misuse, IP forwarding should be disabled unless explicitly required for network routing configurations.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "2.3.7", + "Description": "Ensure Compute Instances Are Launched With Shielded VM Enabled", + "Checks": [ + "compute_instance_shielded_vm_enabled" + ], + "Attributes": [ + { + "Title": "Compute Instances Are Launched With Shielded VM Enabled", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Shielded VMs are hardened virtual machines on Google Cloud Platform (GCP) designed to protect against rootkits, bootkits, and other low-level attacks. They ensure verifiable integrity using Secure Boot, virtual Trusted Platform Module (vTPM)-enabled Measured Boot, and integrity monitoring.", + "AdditionalInformation": "Shielded VMs use signed and verified firmware from Googles Certificate Authority to establish a root of trust. Secure Boot ensures only authentic software runs by verifying digital signatures, preventing unauthorized modifications. Integrity monitoring helps detect unexpected changes in the VMs boot process, while vTPM-enabled Measured Boot provides a baseline to compare against future boots. Enabling Shielded VMs enhances security by protecting against malware, unauthorized firmware changes, and persistent threats.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "2.3.8", + "Description": "Ensure That Compute Instances Do Not Have Public IP Addresses", + "Checks": [ + "compute_instance_public_ip" + ], + "Attributes": [ + { + "Title": "That Compute Instances Do Not Have Public IP Addresses", + "Section": "2. Attack Surface", + "SubSection": "2.3 Application", + "AttributeDescription": "Compute instances should not be assigned external IP addresses to minimize exposure to the internet and reduce security risks.", + "AdditionalInformation": "Public IP addresses increase the attack surface of Compute instances, making them more vulnerable to threats. Instead, instances should be placed behind load balancers or use private networking to control access and reduce the risk of unauthorized exposure.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "3.1.1", + "Description": "Ensure That Sinks Are Configured for All Log Entries", + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ], + "Attributes": [ + { + "Title": "Sinks Are Configured for All Log Entries", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to create a log sink to export and store copies of all log entries. This enables log aggregation across multiple projects and allows integration with a Security Information and Event Management (SIEM) system for centralized monitoring.", + "AdditionalInformation": "Cloud Logging retains logs for a limited period. To ensure long-term storage and better security analysis, logs should be exported to a destination such as Cloud Storage, BigQuery, or Cloud Pub/Sub. A log sink allows you to: Aggregate logs from multiple projects, folders, or billing accounts. Extend log retention beyond Cloud Loggings default retention period. Send logs to a SIEM system for real-time monitoring and threat detection. To ensure all logs are captured and exported: 1.Create a sink without filters to capture all log entries. 2.Choose an appropriate destination (e.g., Cloud Storage for long-term storage, BigQuery for analysis, or Pub/Sub for real-time processing). 3.Apply logging at the organization level to cover all associated projects. Implementing log sinks enhances security visibility, forensic capabilities, and compliance adherence across cloud environments.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.1.2", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Firewall Rule Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for VPC Network Firewall Rule Changes", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) Network Firewall rule changes. Tracking modifications to firewall rules helps ensure that unauthorized or unintended changes do not compromise network security.", + "AdditionalInformation": "Firewall rules control ingress and egress traffic within a VPC. Monitoring create or update events provides visibility into network access changes and helps quickly detect potential security threats or misconfigurations, reducing the risk of unauthorized access.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.3", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Route Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for VPC Network Route Changes", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network route changes. Keeping track of modifications ensures that unauthorized or unintended changes do not disrupt expected network traffic flow.", + "AdditionalInformation": "GCP routes define how network traffic is directed between VM instances and external destinations. Monitoring route table changes helps ensure that traffic follows the intended path, preventing misconfigurations or malicious alterations that could lead to data exposure or connectivity issues.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.4", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for VPC Network Changes", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network changes. This helps track modifications to VPC configurations and peer connections, ensuring that network traffic remains secure and follows the intended paths.", + "AdditionalInformation": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network changes. This helps track modifications to VPC configurations and peer connections, ensuring that network traffic remains secure and follows the intended paths.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.1.5", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Cloud Storage IAM Permission Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for Cloud Storage IAM Permission Changes", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to set up a metric filter and alarm to monitor Cloud Storage Bucket IAM changes. This ensures that any modifications to bucket permissions are tracked and reviewed in a timely manner.", + "AdditionalInformation": "Monitoring changes to Cloud Storage IAM policies helps detect and correct unauthorized access or overly permissive configurations. This reduces the risk of data exposure or breaches by ensuring that sensitive storage buckets and their contents remain properly secured.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.6", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for SQL Instance Configuration Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled" + ], + "Attributes": [ + { + "Title": "Log Metric Filter and Alerts Exist for SQL Instance Configuration Changes", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "It is recommended to configure a metric filter and alarm to track SQL instance configuration changes. This helps in detecting and addressing misconfigurations that may impact security, availability, and compliance.", + "AdditionalInformation": "Monitoring SQL instance configuration changes ensures that critical security settings remain properly configured. Misconfigurations, such as disabling auto backups, allowing untrusted networks, or modifying high availability settings, can lead to data loss, security vulnerabilities, or operational disruptions. Early detection of such changes helps maintain a secure and resilient SQL environment.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.7", + "Description": "Ensure Logging is enabled for HTTP(S) Load Balancer ", + "Checks": [ + "compute_loadbalancer_logging_enabled" + ], + "Attributes": [ + { + "Title": "Logging is enabled for HTTP(S) Load Balancer ", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Enabling logging on an HTTPS Load Balancer captures all network traffic and its destination, providing visibility into requests made to your web applications.", + "AdditionalInformation": "Logging HTTPS network traffic helps monitor access patterns, troubleshoot issues, and enhance security by detecting suspicious activity or unauthorized access attempts.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.8", + "Description": "Ensure that VPC Flow Logs is Enabled for Every Subnet in a VPC Network", + "Checks": [ + "compute_subnet_flow_logs_enabled" + ], + "Attributes": [ + { + "Title": "VPC Flow Logs is Enabled for Every Subnet in a VPC Network", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Flow Logs capture and record IP traffic to and from network interfaces within VPC subnets. These logs are stored in Stackdriver Logging, allowing users to analyze traffic patterns, detect anomalies, and optimize network performance. It is recommended to enable Flow Logs for all critical VPC subnets to enhance network visibility and security.", + "AdditionalInformation": "VPC Flow Logs provide detailed insights into inbound and outbound traffic for virtual machines (VMs), whether they communicate with other VMs, on-premises data centers, Google services, or external networks. Enabling Flow Logs supports: Network monitoring Traffic analysis and cost optimization Incident investigation and forensics Real-time security threat detection For effective monitoring, Flow Logs should be configured to capture all traffic, use granular logging intervals, avoid log filtering, and include metadata for detailed investigations. Note that subnets reserved for internal HTTP(S) load balancing do not support Flow Logs.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.9", + "Description": "Ensure That the Log_connections Database Flag for Cloud SQL PostgreSQL Instance Is Set to On", + "Checks": [ + "cloudsql_instance_postgres_log_connections_flag" + ], + "Attributes": [ + { + "Title": "Log_connections Database Flag for Cloud SQL PostgreSQL Instance Is Set to On", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_connections setting should be enabled to log all attempted connections to the PostgreSQL server, including successful client authentication.", + "AdditionalInformation": "By default, PostgreSQL does not log connection attempts, making it harder to detect unauthorized access. Enabling log_connections provides visibility into all connection attempts, aiding in troubleshooting and identifying unusual or suspicious access patterns. This is particularly useful for security monitoring and incident response.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.10", + "Description": "Ensure That the Log_disconnections Database Flag for Cloud SQL PostgreSQL Instance Is Set to On", + "Checks": [ + "cloudsql_instance_postgres_log_disconnections_flag" + ], + "Attributes": [ + { + "Title": "Log_disconnections Database Flag for Cloud SQL PostgreSQL Instance Is Set to On", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_disconnections setting should be enabled to log the end of each PostgreSQL session, including session duration.", + "AdditionalInformation": "By default, PostgreSQL does not log session termination details, making it difficult to track session activity. Enabling log_disconnections helps monitor session durations and detect unusual activity. Combined with log_connections, it provides a complete audit trail of user access, aiding in troubleshooting and security monitoring.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.1.11", + "Description": "Ensure Log_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set Appropriately", + "Checks": [ + "cloudsql_instance_postgres_log_statement_flag" + ], + "Attributes": [ + { + "Title": "Log_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set Appropriately", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_statement setting in PostgreSQL determines which SQL statements are logged. Acceptable values include none, ddl, mod, and all. A recommended setting is ddl, which logs all data definition statements unless otherwise specified by the organizations logging policy.", + "AdditionalInformation": "Proper SQL statement logging is crucial for auditing and forensic analysis. If too many statements are logged, it can become difficult to extract relevant information; if too few are logged, critical details may be missing. Setting log_statement to an appropriate value, such as ddl, ensures a balance between comprehensive auditing and log manageability, aiding in database security and compliance.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.12", + "Description": "Ensure that the Log_min_messages Flag for a Cloud SQL PostgreSQL Instance is set at minimum to 'Warning'", + "Checks": [ + "cloudsql_instance_postgres_log_min_messages_flag" + ], + "Attributes": [ + { + "Title": "Log_min_messages Flag for a Cloud SQL PostgreSQL Instance is set at minimum to Warning", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_min_messages setting in PostgreSQL defines the minimum severity level for messages to be logged as errors. Accepted values range from DEBUG5 (least severe) to PANIC (most severe). Best practice is to set this value to ERROR, ensuring that only critical issues are logged unless an organizations policy requires a different threshold.", + "AdditionalInformation": "Proper logging is essential for troubleshooting and forensic analysis. If log_min_messages is not configured correctly, important error messages may be missed or unnecessary logs may clutter records. Setting this parameter to ERROR helps maintain a balance between capturing relevant issues and avoiding excessive log noise, improving system monitoring and security.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.1.13", + "Description": "Ensure Log_min_error_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set to Error or Stricter", + "Checks": [ + "cloudsql_instance_postgres_log_min_error_statement_flag" + ], + "Attributes": [ + { + "Title": "Log_min_error_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set to Error or Stricter", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_min_error_statement setting in PostgreSQL defines the minimum severity level for statements to be logged as errors. Valid values range from DEBUG5 (least severe) to PANIC (most severe). It is recommended to set this value to ERROR or stricter to ensure only relevant error statements are logged.", + "AdditionalInformation": "Proper logging aids in troubleshooting and forensic analysis. If log_min_error_statement is set too leniently, excessive log entries may make it difficult to identify actual errors. Conversely, if it is set too strictly, important errors may be missed. Setting this parameter to ERROR or higher ensures that significant issues are recorded while avoiding unnecessary log clutter.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.14", + "Description": "Ensure That the Log_min_duration_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set to '-1' (Disabled) ", + "Checks": [ + "cloudsql_instance_postgres_log_min_duration_statement_flag" + ], + "Attributes": [ + { + "Title": "Log_min_duration_statement Database Flag for Cloud SQL PostgreSQL Instance Is Set to -1 (Disabled) ", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "The log_min_duration_statement setting in PostgreSQL determines the minimum execution time (in milliseconds) required for a statement to be logged. It is recommended to disable this setting by setting its value to -1.", + "AdditionalInformation": "Logging SQL statements may expose sensitive information, which could lead to security risks if recorded in logs. Disabling this setting ensures that confidential data is not inadvertently captured. This recommendation applies to PostgreSQL database instances.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "3.1.15", + "Description": "Ensure That 'cloudsql.enable_pgaudit' Database Flag for each Cloud Sql Postgresql Instance Is Set to 'on' For Centralized Logging", + "Checks": [ + "cloudsql_instance_postgres_enable_pgaudit_flag" + ], + "Attributes": [ + { + "Title": "cloudsql.enable_pgaudit Database Flag for each Cloud Sql Postgresql Instance Is Set to on For Centralized Logging", + "Section": "3. Logging and Monitoring", + "SubSection": "3.1 Logging", + "AttributeDescription": "Ensure that the cloudsql.enable_pgaudit database flag is set to on for Cloud SQL PostgreSQL instances to enable centralized logging and auditing.", + "AdditionalInformation": "Enabling the pgaudit extension provides detailed session and object-level logging, which helps organizations comply with security standards such as government, financial, and ISO regulations. This logging capability enhances threat detection by monitoring security events on the database instance. Additionally, enabling this flag allows logs to be sent to Google Logs Explorer for centralized access and monitoring. This recommendation applies specifically to PostgreSQL database instances.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.2.1", + "Description": "Ensure That Retention Policies on Cloud Storage Buckets Used for Exporting Logs Are Configured Using Bucket Lock", + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ], + "Attributes": [ + { + "Title": "Retention Policies on Cloud Storage Buckets Used for Exporting Logs Are Configured Using Bucket Lock ", + "Section": "3. Logging and Monitoring", + "SubSection": "3.2 Retention", + "AttributeDescription": "Enabling retention policies on log storage buckets prevents logs from being overwritten or accidentally deleted. It is recommended to configure retention policies and enable Bucket Lock for all storage buckets used as log sinks.", + "AdditionalInformation": "Cloud Logging allows logs to be exported to storage buckets through sinks. Without a retention policy, logs can be altered or deleted, making it difficult to perform security investigations or comply with audit requirements. To ensure logs remain intact for forensics and security analysis: 1.Set a retention policy on log storage buckets to prevent early deletion. 2.Enable Bucket Lock to make the policy immutable, ensuring logs cannot be altered even by privileged users. 3.Apply appropriate access controls to protect logs from unauthorized access. By implementing retention policies and Bucket Lock, organizations preserve critical security logs, prevent attackers from covering their tracks, and enhance compliance with security regulations.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "3.3.1", + "Description": "Ensure Cloud Asset Inventory Is Enabled", + "Checks": [ + "iam_cloud_asset_inventory_enabled" + ], + "Attributes": [ + { + "Title": "Cloud Asset Inventory Enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "Google Cloud Asset Inventory provides a historical view of GCP resources and IAM policies using a time-series database. It captures metadata on cloud resources, policy configurations, and runtime data. Enabling Cloud Asset Inventory allows for efficient searching and exporting of asset data.", + "AdditionalInformation": "Cloud Asset Inventory enhances security analysis, resource change tracking, and compliance auditing by maintaining a detailed history of GCP resources and their configurations. Enabling it across all GCP projects ensures visibility into changes, helping organizations detect misconfigurations, track policy changes, and strengthen security posture.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "3.3.2", + "Description": "Ensure 'Access Approval' is 'Enabled'", + "Checks": [ + "iam_account_access_approval_enabled" + ], + "Attributes": [ + { + "Title": "Access Aproval Enabled", + "Section": "3. Logging and Monitoring", + "SubSection": "3.3 Monitoring", + "AttributeDescription": "GCP Access Approval allows organizations to require explicit approval before Google support personnel can access their projects. Administrators can assign security roles in IAM to specific users who can review and approve these requests. Notifications of access requests, including the requesting Google employees details, are sent via email or Pub/Sub messages, providing transparency and control.", + "AdditionalInformation": "Managing who accesses your organizations data is critical for information security. While Google support may require access for troubleshooting, Access Approval ensures that access is only granted when explicitly authorized. This feature adds an additional layer of security and logging, ensuring that only approved Google personnel can access sensitive information.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.1.1", + "Description": "Ensure That DNSSEC Is Enabled for Cloud DNS ", + "Checks": [ + "dns_dnssec_disabled" + ], + "Attributes": [ + { + "Title": "DNSSEC Is Enabled for Cloud DNS ", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Cloud DNS provides a scalable and reliable domain name system (DNS) service. Domain Name System Security Extensions (DNSSEC) enhance DNS security by protecting domains against DNS hijacking, man-in-the-middle attacks, and other threats.", + "AdditionalInformation": "DNSSEC cryptographically signs DNS records, ensuring the integrity and authenticity of DNS responses. Without DNSSEC, attackers can manipulate DNS lookups, redirecting users to malicious websites through DNS hijacking or spoofing attacks. Enabling DNSSEC helps prevent unauthorized modifications to DNS records, reducing the risk of phishing, malware distribution, and other cyber threats.", + "LevelOfRisk": 1 + } + ] + }, + { + "Id": "4.1.2", + "Description": "Ensure That the Cloud SQL Database Instance Requires All Incoming Connections To Use SSL", + "Checks": [ + "cloudsql_instance_ssl_connections" + ], + "Attributes": [ + { + "Title": "Cloud SQL Database Instance Requires All Incoming Connections To Use SSL", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "Require all incoming connections to SQL database instances to use SSL encryption.", + "AdditionalInformation": "Unencrypted SQL database connections are vulnerable to man-in-the-middle (MITM) attacks, which can expose sensitive data such as credentials, queries, and results. Enforcing SSL ensures secure communication by encrypting data in transit, protecting against interception and unauthorized access. This recommendation applies to PostgreSQL, MySQL (Generation 1 and 2), and SQL Server 2017 instances.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.1.3", + "Description": "Ensure That RSASHA1 Is Not Used for the Key-Signing Key in Cloud DNS DNSSEC", + "Checks": [ + "dns_rsasha1_in_use_to_key_sign_in_dnssec" + ], + "Attributes": [ + { + "Title": "RSASHA1 Is Not Used for the Key-Signing Key in Cloud DNS DNSSEC", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "DNSSEC (Domain Name System Security Extensions) relies on cryptographic algorithms to ensure the integrity and authenticity of DNS responses. It is important to use strong and recommended algorithms for key signing to maintain robust security. SHA-1 is deprecated and requires explicit approval from Google if used.", + "AdditionalInformation": "DNSSEC signing algorithms play a critical role in securing DNS transactions. Using weak or outdated algorithms can expose DNS infrastructure to spoofing, hijacking, and other attacks. Organizations should select recommended and secure algorithms when enabling DNSSEC to protect DNS records from unauthorized modifications. If adjustments to DNSSEC settings are required, DNSSEC must be disabled and re-enabled with the updated configurations.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.1.4", + "Description": "Ensure That RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC", + "Checks": [ + "dns_rsasha1_in_use_to_zone_sign_in_dnssec" + ], + "Attributes": [ + { + "Title": "RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC", + "Section": "4. Encryption", + "SubSection": "4.1 In-Transit", + "AttributeDescription": "DNSSEC (Domain Name System Security Extensions) enhances DNS security by using cryptographic algorithms for zone signing and transaction security. It is essential to use strong and recommended algorithms for key signing. SHA-1 has been deprecated and requires Googles explicit approval and a support contract if used.", + "AdditionalInformation": "Using weak or outdated cryptographic algorithms compromises DNS integrity and exposes systems to threats like spoofing and hijacking. Organizations should ensure that DNSSEC settings use strong, recommended algorithms. If DNSSEC is already enabled and changes are needed, it must be disabled and re-enabled with updated configurations to apply the changes effectively.", + "LevelOfRisk": 2 + } + ] + }, + { + "Id": "4.2.1", + "Description": "Ensure VM Disks for Critical VMs Are Encrypted With Customer-Supplied Encryption Keys (CSEK)", + "Checks": [ + "compute_instance_encryption_with_csek_enabled" + ], + "Attributes": [ + { + "Title": "VM Disks for Critical VMs Are Encrypted With Customer-Supplied Encryption Keys (CSEK)", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Customer-Supplied Encryption Keys (CSEK) is a feature available in Google Cloud Storage and Google Compute Engine, allowing users to supply their own encryption keys. When you provide your key, Google uses it to protect the Google-generated keys that are responsible for encrypting and decrypting your data. By default, Google Compute Engine encrypts all data at rest automatically, managing this encryption for you with no additional action required. However, if you wish to have full control over the encryption process, you can choose to supply your own encryption keys.", + "AdditionalInformation": "By default, Compute Engine automatically encrypts all data at rest, with the service managing the encryption without any further input required from you or your application. However, if you require complete control over encryption, you have the option to provide your own encryption keys to manage the encryption of instance disks.", + "LevelOfRisk": 5 + } + ] + }, + { + "Id": "4.2.2", + "Description": "Ensure that Dataproc Cluster is encrypted using Customer-Managed Encryption Key", + "Checks": [ + "dataproc_encrypted_with_cmks_disabled" + ], + "Attributes": [ + { + "Title": "Dataproc Cluster is encrypted using Customer-Managed Encryption Key", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "When using Dataproc, the data associated with clusters and jobs is stored on Persistent Disks (PDs) linked to the Compute Engine VMs in your cluster and in a Cloud Storage staging bucket. This data is encrypted using a Google-generated Data Encryption Key (DEK) and a Key Encryption Key (KEK). The Customer-Managed Encryption Keys (CMEK) feature allows you to create, use, and revoke the KEK, although Google still controls the DEK used to encrypt the data.", + "AdditionalInformation": "Dataproc cluster data is encrypted using Google-managed keys: the Data Encryption Key (DEK) and the Key Encryption Key (KEK). If you wish to have control over the encryption of your cluster data, you can use your own Customer-Managed Keys (CMKs). Cloud KMS Customer-Managed Keys can add an extra layer of security and are commonly used in environments with strict compliance and security requirements.", + "LevelOfRisk": 4 + } + ] + }, + { + "Id": "4.2.3", + "Description": "Ensure BigQuery datasets are encrypted with Customer-Managed Keys (CMKs).", + "Checks": [ + "bigquery_dataset_cmk_encryption" + ], + "Attributes": [ + { + "Title": "BigQuery datasets are encrypted with Customer-Managed Keys (CMKs).", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Ensure that BigQuery datasets are encrypted using Customer-Managed Keys (CMKs) to gain more granular control over the data encryption and decryption process.", + "AdditionalInformation": "For enhanced control over encryption, Customer-Managed Encryption Keys (CMEK) can be implemented as a key management solution for BigQuery datasets.", + "LevelOfRisk": 3 + } + ] + }, + { + "Id": "4.2.4", + "Description": "Ensure BigQuery tables are encrypted with Customer-Managed Keys (CMKs).", + "Checks": [ + "bigquery_table_cmk_encryption" + ], + "Attributes": [ + { + "Title": "BigQuery tables are encrypted with Customer-Managed Keys (CMKs).", + "Section": "4. Encryption", + "SubSection": "4.2 At-Rest", + "AttributeDescription": "Ensure that BigQuery tables are encrypted using Customer-Managed Keys (CMKs) for more granular control over the data encryption and decryption process.", + "AdditionalInformation": "For greater control over encryption, Customer-Managed Encryption Keys (CMEK) can be utilized as the key management solution for BigQuery tables.", + "LevelOfRisk": 3 + } + ] + } + ] +} diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index fff0d6c38e..92bfaec168 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -183,6 +183,18 @@ class KISA_ISMSP_Requirement_Attribute(BaseModel): NonComplianceCases: Optional[list[str]] +# Prowler ThreatScore Requirement Attribute +class Prowler_ThreatScore_Requirement_Attribute(BaseModel): + """Prowler ThreatScore Requirement Attribute""" + + Title: str + Section: str + SubSection: str + AttributeDescription: str + AdditionalInformation: str + LevelOfRisk: int + + # Base Compliance Model # TODO: move this to compliance folder class Compliance_Requirement(BaseModel): @@ -198,6 +210,7 @@ class Compliance_Requirement(BaseModel): ISO27001_2013_Requirement_Attribute, AWS_Well_Architected_Requirement_Attribute, KISA_ISMSP_Requirement_Attribute, + Prowler_ThreatScore_Requirement_Attribute, # Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework Generic_Compliance_Requirement_Attribute, ] diff --git a/prowler/lib/outputs/compliance/compliance.py b/prowler/lib/outputs/compliance/compliance.py index b74eafa97f..e0c686dd84 100644 --- a/prowler/lib/outputs/compliance/compliance.py +++ b/prowler/lib/outputs/compliance/compliance.py @@ -11,6 +11,9 @@ from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp import get_kisa_ismsp_ from prowler.lib.outputs.compliance.mitre_attack.mitre_attack import ( get_mitre_attack_table, ) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore import ( + get_prowler_threatscore_table, +) def display_compliance_table( @@ -72,6 +75,15 @@ def display_compliance_table( output_directory, compliance_overview, ) + elif "threatscore_" in compliance_framework: + get_prowler_threatscore_table( + findings, + bulk_checks_metadata, + compliance_framework, + output_filename, + output_directory, + compliance_overview, + ) else: get_generic_compliance_table( findings, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/__init__.py b/prowler/lib/outputs/compliance/prowler_threatscore/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/models.py b/prowler/lib/outputs/compliance/prowler_threatscore/models.py new file mode 100644 index 0000000000..2b5f5ada66 --- /dev/null +++ b/prowler/lib/outputs/compliance/prowler_threatscore/models.py @@ -0,0 +1,81 @@ +from typing import Optional + +from pydantic import BaseModel + + +class ProwlerThreatScoreAWSModel(BaseModel): + """ + ProwlerThreatScoreAWSModel generates a finding's output in AWS Prowler ThreatScore Compliance format. + """ + + Provider: str + Description: str + AccountId: str + Region: str + AssessmentDate: str + Requirements_Id: str + Requirements_Description: str + Requirements_Attributes_Title: str + Requirements_Attributes_Section: str + Requirements_Attributes_SubSection: Optional[str] + Requirements_Attributes_AttributeDescription: str + Requirements_Attributes_AdditionalInformation: str + Requirements_Attributes_LevelOfRisk: int + Status: str + StatusExtended: str + ResourceId: str + ResourceName: str + CheckId: str + Muted: bool + + +class ProwlerThreatScoreAzureModel(BaseModel): + """ + ProwlerThreatScoreAzureModel generates a finding's output in Azure Prowler ThreatScore Compliance format. + """ + + Provider: str + Description: str + SubscriptionId: str + Location: str + AssessmentDate: str + Requirements_Id: str + Requirements_Description: str + Requirements_Attributes_Title: str + Requirements_Attributes_Section: str + Requirements_Attributes_SubSection: Optional[str] + Requirements_Attributes_AttributeDescription: str + Requirements_Attributes_AdditionalInformation: str + Requirements_Attributes_LevelOfRisk: int + Status: str + StatusExtended: str + ResourceId: str + ResourceName: str + CheckId: str + Muted: bool + + +class ProwlerThreatScoreGCPModel(BaseModel): + """ + ProwlerThreatScoreGCPModel generates a finding's output in GCP Prowler ThreatScore Compliance format. + """ + + Provider: str + Description: str + ProjectId: str + Location: str + AssessmentDate: str + Requirements_Id: str + Requirements_Description: str + Requirements_Attributes_Title: str + Requirements_Attributes_Section: str + Requirements_Attributes_SubSection: Optional[str] + Requirements_Attributes_AttributeDescription: str + Requirements_Attributes_AdditionalInformation: str + Requirements_Attributes_LevelOfRisk: int + Status: str + StatusExtended: str + ResourceId: str + ResourceName: str + CheckId: str + Muted: bool diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py new file mode 100644 index 0000000000..e041e3e7f3 --- /dev/null +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py @@ -0,0 +1,121 @@ +from colorama import Fore, Style +from tabulate import tabulate + +from prowler.config.config import orange_color + + +def get_prowler_threatscore_table( + findings: list, + bulk_checks_metadata: dict, + compliance_framework: str, + output_filename: str, + output_directory: str, + compliance_overview: bool, +): + pillar_table = { + "Provider": [], + "Pillar": [], + "Status": [], + "Score": [], + "Muted": [], + } + pass_count = [] + fail_count = [] + muted_count = [] + pillars = {} + score_per_pillar = {} + number_findings_per_pillar = {} + for index, finding in enumerate(findings): + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if compliance.Framework == "ProwlerThreatScore": + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + pillar = attribute.Section + + if pillar not in score_per_pillar.keys(): + score_per_pillar[pillar] = 0 + number_findings_per_pillar[pillar] = 0 + if finding.status == "FAIL" and not finding.muted: + score_per_pillar[pillar] += attribute.LevelOfRisk + number_findings_per_pillar[pillar] += 1 + + if pillar not in pillars: + pillars[pillar] = {"FAIL": 0, "PASS": 0, "Muted": 0} + + if finding.muted: + if index not in muted_count: + muted_count.append(index) + pillars[pillar]["Muted"] += 1 + else: + if finding.status == "FAIL" and index not in fail_count: + fail_count.append(index) + pillars[pillar]["FAIL"] += 1 + elif finding.status == "PASS" and index not in pass_count: + pass_count.append(index) + pillars[pillar]["PASS"] += 1 + + pillars = dict(sorted(pillars.items())) + for pillar in pillars: + pillar_table["Provider"].append(compliance.Provider) + pillar_table["Pillar"].append(pillar) + if number_findings_per_pillar[pillar] == 0: + pillar_table["Score"].append( + f"{Style.BRIGHT}{Fore.GREEN}0{Style.RESET_ALL}" + ) + else: + pillar_table["Score"].append( + f"{Style.BRIGHT}{Fore.RED}{score_per_pillar[pillar] / number_findings_per_pillar[pillar]:.2f}/5{Style.RESET_ALL}" + ) + if pillars[pillar]["FAIL"] > 0: + pillar_table["Status"].append( + f"{Fore.RED}FAIL({pillars[pillar]['FAIL']}){Style.RESET_ALL}" + ) + else: + pillar_table["Status"].append( + f"{Fore.GREEN}PASS({pillars[pillar]['PASS']}){Style.RESET_ALL}" + ) + pillar_table["Muted"].append( + f"{orange_color}{pillars[pillar]['Muted']}{Style.RESET_ALL}" + ) + + if ( + len(fail_count) + len(pass_count) + len(muted_count) > 1 + ): # If there are no resources, don't print the compliance table + print( + f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" + ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) + overview_table = [ + [ + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + if len(fail_count) > 0 and len(pillar_table["Pillar"]) > 0: + print( + f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:" + ) + + print( + tabulate( + pillar_table, + tablefmt="rounded_grid", + headers="keys", + ) + ) + + print( + f"{Style.BRIGHT}\n=== Risk Score Guide ===\nScore ranges from 1 (lowest risk) to 5 (highest risk), indicating the severity of the potential impact.{Style.RESET_ALL}" + ) + print( + f"{Style.BRIGHT}(Only sections containing results appear, the score is calculated as the sum of the level of risk of the failed findings divided by the number of failed findings){Style.RESET_ALL}" + ) + print(f"\nDetailed results of {compliance_framework.upper()} are in:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py new file mode 100644 index 0000000000..2d876b402f --- /dev/null +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py @@ -0,0 +1,91 @@ +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreAWSModel, +) +from prowler.lib.outputs.finding import Finding + + +class ProwlerThreatScoreAWS(ComplianceOutput): + """ + This class represents the AWS Prowler ThreatScore compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into AWS Prowler ThreatScore compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: Compliance, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into AWS Prowler ThreatScore compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (Compliance): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreAWSModel( + Provider=finding.provider, + Description=compliance.Description, + AccountId=finding.account_uid, + Region=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + # Add manual requirements to the compliance output + for requirement in compliance.Requirements: + if not requirement.Checks: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreAWSModel( + Provider=compliance.Provider.lower(), + Description=compliance.Description, + AccountId="", + Region="", + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status="MANUAL", + StatusExtended="Manual check", + ResourceId="manual_check", + ResourceName="Manual check", + CheckId="manual", + Muted=False, + ) + self._data.append(compliance_row) diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py new file mode 100644 index 0000000000..01786e2d08 --- /dev/null +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py @@ -0,0 +1,91 @@ +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreAzureModel, +) +from prowler.lib.outputs.finding import Finding + + +class ProwlerThreatScoreAzure(ComplianceOutput): + """ + This class represents the Azure Prowler ThreatScore compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into Azure Prowler ThreatScore compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: Compliance, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into Azure Prowler ThreatScore compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (Compliance): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreAzureModel( + Provider=finding.provider, + Description=compliance.Description, + SubscriptionId=finding.account_uid, + Location=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + # Add manual requirements to the compliance output + for requirement in compliance.Requirements: + if not requirement.Checks: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreAzureModel( + Provider=compliance.Provider.lower(), + Description=compliance.Description, + SubscriptionId="", + Location="", + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status="MANUAL", + StatusExtended="Manual check", + ResourceId="manual_check", + ResourceName="Manual check", + CheckId="manual", + Muted=False, + ) + self._data.append(compliance_row) diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py new file mode 100644 index 0000000000..9bf577255f --- /dev/null +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py @@ -0,0 +1,91 @@ +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreGCPModel, +) +from prowler.lib.outputs.finding import Finding + + +class ProwlerThreatScoreGCP(ComplianceOutput): + """ + This class represents the GCP Prowler ThreatScore compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into GCP Prowler ThreatScore compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: Compliance, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into GCP Prowler ThreatScore compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (Compliance): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreGCPModel( + Provider=finding.provider, + Description=compliance.Description, + ProjectId=finding.account_uid, + Location=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + ) + self._data.append(compliance_row) + # Add manual requirements to the compliance output + for requirement in compliance.Requirements: + if not requirement.Checks: + for attribute in requirement.Attributes: + compliance_row = ProwlerThreatScoreGCPModel( + Provider=compliance.Provider.lower(), + Description=compliance.Description, + ProjectId="", + Location="", + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Title=attribute.Title, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Status="MANUAL", + StatusExtended="Manual check", + ResourceId="manual_check", + ResourceName="Manual check", + CheckId="manual", + Muted=False, + ) + self._data.append(compliance_row) diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py index d9177a4ab9..aeb11aebfc 100644 --- a/prowler/lib/outputs/html/html.py +++ b/prowler/lib/outputs/html/html.py @@ -106,8 +106,7 @@ class HTML(Output): """ try: file_descriptor.write( - f""" - + f""" diff --git a/prowler/lib/powershell/powershell.py b/prowler/lib/powershell/powershell.py index 373fc97183..f6e058afcd 100644 --- a/prowler/lib/powershell/powershell.py +++ b/prowler/lib/powershell/powershell.py @@ -4,6 +4,9 @@ import queue import re import subprocess import threading +from typing import Union + +from prowler.lib.logger import logger class PowerShellSession: @@ -100,7 +103,9 @@ class PowerShellSession: ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") return ansi_escape.sub("", text) - def execute(self, command: str) -> dict: + def execute( + self, command: str, json_parse: bool = False, timeout: int = 10 + ) -> Union[str, dict]: """ Send a command to PowerShell and retrieve its output. @@ -120,31 +125,41 @@ class PowerShellSession: """ self.process.stdin.write(f"{command}\n") self.process.stdin.write(f"Write-Output '{self.END}'\n") - return self.json_parse_output(self.read_output()) + self.process.stdin.write(f"Write-Error '{self.END}'\n") + return ( + self.json_parse_output(self.read_output(timeout=timeout)) + if json_parse + else self.read_output(timeout=timeout) + ) def read_output(self, timeout: int = 10, default: str = "") -> str: """ Read output from a process with timeout functionality. - This method reads lines from process stdout until it encounters the END marker - or the stream ends. If reading takes longer than the timeout period, the method - returns a default value while allowing the reading to continue in the background. + This method reads lines from process stdout and stderr in separate threads until it encounters + the END marker for each stream. If reading stdout takes longer than the timeout period, + the method returns a default value while allowing the reading to continue in the background. + + Any errors from stderr are logged but do not affect the return value. Args: - timeout (int, optional): Maximum time in seconds to wait for output. + timeout (int, optional): Maximum time in seconds to wait for stdout output. Defaults to 10. - default (str, optional): Value to return if timeout occurs. + default (str, optional): Value to return if stdout timeout occurs. Defaults to empty string. Returns: - str: Concatenated output lines or default value if timeout occurs. + str: The stdout output if available, otherwise the default value. + Errors from stderr are logged but not returned. Note: - This method uses a daemon thread to read the output asynchronously, + This method uses daemon threads to read stdout and stderr asynchronously, ensuring that the main thread is not blocked. """ output_lines = [] result_queue = queue.Queue() + error_lines = [] + error_queue = queue.Queue() def reader_thread(): try: @@ -154,17 +169,39 @@ class PowerShellSession: break output_lines.append(line) result_queue.put("\n".join(output_lines)) - except Exception as e: - result_queue.put(str(e)) + except Exception as error: + result_queue.put(str(error)) + + def error_reader_thread(): + try: + while True: + line = self.remove_ansi(self.process.stderr.readline().strip()) + if line == f"Write-Error: {self.END}": + break + error_lines.append(line) + error_queue.put("\n".join(error_lines)) + except Exception as error: + error_queue.put(str(error)) thread = threading.Thread(target=reader_thread) thread.daemon = True thread.start() + error_thread = threading.Thread(target=error_reader_thread) + error_thread.daemon = True + error_thread.start() + + error_result = None try: - return result_queue.get(timeout=timeout) + result = result_queue.get(timeout=timeout) or default + error_result = error_queue.get(timeout=1) except queue.Empty: - return default + result = default + + if error_result: + logger.error(f"PowerShell error output: {error_result}") + + return result def json_parse_output(self, output: str) -> dict: """ @@ -179,13 +216,29 @@ class PowerShellSession: Returns: dict: Parsed JSON object if found, otherwise an empty dictionary. + Raises: + JSONDecodeError: If the JSON parsing fails. + Example: >>> json_parse_output('Some text {"key": "value"} more text') {"key": "value"} """ + if output == "": + return {} + json_match = re.search(r"(\[.*\]|\{.*\})", output, re.DOTALL) - if json_match: - return json.loads(json_match.group(1)) + if not json_match: + logger.error( + f"Unexpected PowerShell output: {output}\n", + ) + else: + try: + return json.loads(json_match.group(1)) + except json.JSONDecodeError as error: + logger.error( + f"Error parsing PowerShell output as JSON: {str(error)}\n", + ) + return {} def close(self) -> None: diff --git a/prowler/providers/aws/lib/s3/s3.py b/prowler/providers/aws/lib/s3/s3.py index f399171c98..03308224fe 100644 --- a/prowler/providers/aws/lib/s3/s3.py +++ b/prowler/providers/aws/lib/s3/s3.py @@ -105,6 +105,12 @@ class S3: """ try: uploaded_objects = {"success": {}, "failure": {}} + extension_to_content_type = { + ".html": "text/html", + ".csv": "text/csv", + ".ocsf.json": "application/json", + ".asff.json": "application/json", + } # Keys are regular and/or compliance for key, output_list in outputs.items(): for output in output_list: @@ -115,6 +121,7 @@ class S3: bucket_directory = self.get_object_path(self._output_directory) basename = path.basename(output.file_descriptor.name) + file_extension = output.file_extension if key == "compliance": object_name = f"{bucket_directory}/{key}/{basename}" @@ -128,7 +135,12 @@ class S3: # into the local filesystem because S3 upload file is the recommended way. # https://aws.amazon.com/blogs/developer/uploading-files-to-amazon-s3/ self._session.upload_file( - output.file_descriptor.name, self._bucket_name, object_name + Filename=output.file_descriptor.name, + Bucket=self._bucket_name, + Key=object_name, + ExtraArgs={ + "ContentType": extension_to_content_type[file_extension] + }, ) if output.file_extension in uploaded_objects["success"]: diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 0efde909ef..aaa94b21d1 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -221,6 +221,7 @@ class Provider(ABC): az_cli_auth=arguments.az_cli_auth, browser_auth=arguments.browser_auth, tenant_id=arguments.tenant_id, + init_modules=arguments.init_modules, fixer_config=fixer_config, ) elif "nhn" in provider_class_name.lower(): diff --git a/prowler/providers/m365/exceptions/exceptions.py b/prowler/providers/m365/exceptions/exceptions.py index 9f9c96a178..2b5840dd10 100644 --- a/prowler/providers/m365/exceptions/exceptions.py +++ b/prowler/providers/m365/exceptions/exceptions.py @@ -94,7 +94,7 @@ class M365BaseException(ProwlerException): "message": "Tenant Id is required for Microsoft 365 static credentials. Make sure you are using the correct credentials.", "remediation": "Check the Microsoft 365 Tenant ID and ensure it is properly set up.", }, - (6022, "M365MissingEnvironmentUserCredentialsError"): { + (6022, "M365MissingEnvironmentCredentialsError"): { "message": "User and Password environment variables are needed to use Credentials authentication method.", "remediation": "Ensure your environment variables are properly set up.", }, @@ -102,6 +102,18 @@ class M365BaseException(ProwlerException): "message": "User or Password environment variables are not correct.", "remediation": "Ensure you are using the right credentials.", }, + (6024, "M365NotValidUserError"): { + "message": "The provided M365 User is not valid.", + "remediation": "Check the M365 User and ensure it is a valid user.", + }, + (6025, "M365NotValidEncryptedPasswordError"): { + "message": "The provided M365 Encrypted Password is not valid.", + "remediation": "Check the M365 Encrypted Password and ensure it is a valid password.", + }, + (6026, "M365UserNotBelongingToTenantError"): { + "message": "The provided M365 User does not belong to the specified tenant.", + "remediation": "Check the M365 User email domain and ensure it belongs to the specified tenant.", + }, } def __init__(self, code, file=None, original_exception=None, message=None): @@ -279,7 +291,7 @@ class M365NotTenantIdButClientIdAndClientSecretError(M365CredentialsError): ) -class M365MissingEnvironmentUserCredentialsError(M365CredentialsError): +class M365MissingEnvironmentCredentialsError(M365CredentialsError): def __init__(self, file=None, original_exception=None, message=None): super().__init__( 6022, file=file, original_exception=original_exception, message=message @@ -291,3 +303,24 @@ class M365EnvironmentUserCredentialsError(M365CredentialsError): super().__init__( 6023, file=file, original_exception=original_exception, message=message ) + + +class M365NotValidUserError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6024, file=file, original_exception=original_exception, message=message + ) + + +class M365NotValidEncryptedPasswordError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6025, file=file, original_exception=original_exception, message=message + ) + + +class M365UserNotBelongingToTenantError(M365CredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 6026, file=file, original_exception=original_exception, message=message + ) diff --git a/prowler/providers/m365/lib/arguments/arguments.py b/prowler/providers/m365/lib/arguments/arguments.py index a1980c4bd9..e88ef7982b 100644 --- a/prowler/providers/m365/lib/arguments/arguments.py +++ b/prowler/providers/m365/lib/arguments/arguments.py @@ -35,16 +35,9 @@ def init_parser(self): help="Microsoft 365 Tenant ID to be used with --browser-auth option", ) m365_parser.add_argument( - "--user", - nargs="?", - default=None, - help="Microsoft 365 user email", - ) - m365_parser.add_argument( - "--encypted-password", - nargs="?", - default=None, - help="Microsoft 365 encrypted password", + "--init-modules", + action="store_true", + help="Initialize Microsoft 365 PowerShell modules", ) # Regions m365_regions_subparser = m365_parser.add_argument_group("Regions") diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index 7b272cc748..df4a26dfd8 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -1,6 +1,12 @@ +import os + import msal +from prowler.lib.logger import logger from prowler.lib.powershell.powershell import PowerShellSession +from prowler.providers.m365.exceptions.exceptions import ( + M365UserNotBelongingToTenantError, +) from prowler.providers.m365.models import M365Credentials @@ -21,6 +27,7 @@ class M365PowerShell(PowerShellSession): Attributes: credentials (M365Credentials): The Microsoft 365 credentials used for authentication. + required_modules (list): List of required PowerShell modules for M365 operations. Note: This class requires the Microsoft Teams and Exchange Online PowerShell modules @@ -79,16 +86,14 @@ class M365PowerShell(PowerShellSession): bool: True if credentials are valid and authentication succeeds, False otherwise. """ self.execute( - f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString\n' + f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString' ) self.execute( f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n' ) - self.process.stdin.write( - 'Write-Output "$($credential.GetNetworkCredential().Password)"\n' + decrypted_password = self.execute( + 'Write-Output "$($credential.GetNetworkCredential().Password)"' ) - self.process.stdin.write(f"Write-Output '{self.END}'\n") - decrypted_password = self.read_output() app = msal.ConfidentialClientApplication( client_id=credentials.client_id, @@ -102,7 +107,21 @@ class M365PowerShell(PowerShellSession): scopes=["https://graph.microsoft.com/.default"], ) - return "access_token" in result + if result is None: + return False + + if "access_token" not in result: + return False + + # Validate user credentials belong to tenant + user_domain = credentials.user.split("@")[1] + if not credentials.provider_id.endswith(user_domain): + raise M365UserNotBelongingToTenantError( + file=os.path.basename(__file__), + message="The provided M365 User does not belong to the specified tenant.", + ) + + return True def connect_microsoft_teams(self) -> dict: """ @@ -135,7 +154,9 @@ class M365PowerShell(PowerShellSession): "AllowGoogleDrive": true } """ - return self.execute("Get-CsTeamsClientConfiguration | ConvertTo-Json") + return self.execute( + "Get-CsTeamsClientConfiguration | ConvertTo-Json", json_parse=True + ) def get_global_meeting_policy(self) -> dict: """ @@ -153,7 +174,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json" + "Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json", + json_parse=True, ) def get_user_settings(self) -> dict: @@ -171,7 +193,9 @@ class M365PowerShell(PowerShellSession): "AllowExternalAccess": true } """ - return self.execute("Get-CsTenantFederationConfiguration | ConvertTo-Json") + return self.execute( + "Get-CsTenantFederationConfiguration | ConvertTo-Json", json_parse=True + ) def connect_exchange_online(self) -> dict: """ @@ -203,7 +227,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json" + "Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json", + json_parse=True, ) def get_malware_filter_policy(self) -> dict: @@ -222,7 +247,7 @@ class M365PowerShell(PowerShellSession): "Identity": "Default" } """ - return self.execute("Get-MalwareFilterPolicy | ConvertTo-Json") + return self.execute("Get-MalwareFilterPolicy | ConvertTo-Json", json_parse=True) def get_outbound_spam_filter_policy(self) -> dict: """ @@ -242,7 +267,9 @@ class M365PowerShell(PowerShellSession): "NotifyOutboundSpamRecipients": [] } """ - return self.execute("Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json") + return self.execute( + "Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json", json_parse=True + ) def get_outbound_spam_filter_rule(self) -> dict: """ @@ -259,7 +286,9 @@ class M365PowerShell(PowerShellSession): "State": "Enabled" } """ - return self.execute("Get-HostedOutboundSpamFilterRule | ConvertTo-Json") + return self.execute( + "Get-HostedOutboundSpamFilterRule | ConvertTo-Json", json_parse=True + ) def get_antiphishing_policy(self) -> dict: """ @@ -284,7 +313,7 @@ class M365PowerShell(PowerShellSession): "IsDefault": false } """ - return self.execute("Get-AntiPhishPolicy | ConvertTo-Json") + return self.execute("Get-AntiPhishPolicy | ConvertTo-Json", json_parse=True) def get_antiphishing_rules(self) -> dict: """ @@ -302,7 +331,7 @@ class M365PowerShell(PowerShellSession): "State": Enabled, } """ - return self.execute("Get-AntiPhishRule | ConvertTo-Json") + return self.execute("Get-AntiPhishRule | ConvertTo-Json", json_parse=True) def get_organization_config(self) -> dict: """ @@ -321,7 +350,7 @@ class M365PowerShell(PowerShellSession): "AuditDisabled": false } """ - return self.execute("Get-OrganizationConfig | ConvertTo-Json") + return self.execute("Get-OrganizationConfig | ConvertTo-Json", json_parse=True) def get_mailbox_audit_config(self) -> dict: """ @@ -340,7 +369,64 @@ class M365PowerShell(PowerShellSession): "AuditBypassEnabled": false } """ - return self.execute("Get-MailboxAuditBypassAssociation | ConvertTo-Json") + return self.execute( + "Get-MailboxAuditBypassAssociation | ConvertTo-Json", json_parse=True + ) + + def get_mailbox_policy(self) -> dict: + """ + Get Mailbox Policy. + + Retrieves the current mailbox policy settings for Exchange Online. + + Returns: + dict: Mailbox policy settings in JSON format. + + Example: + >>> get_mailbox_policy() + { + "Id": "OwaMailboxPolicy-Default", + "AdditionalStorageProvidersAvailable": True + } + """ + return self.execute("Get-OwaMailboxPolicy | ConvertTo-Json", json_parse=True) + + def get_external_mail_config(self) -> dict: + """ + Get Exchange Online External Mail Configuration. + + Retrieves the current external mail configuration settings for Exchange Online. + + Returns: + dict: External mail configuration settings in JSON format. + + Example: + >>> get_external_mail_config() + { + "Identity": "MyExternalMail", + "ExternalMailTagEnabled": true + } + """ + return self.execute("Get-ExternalInOutlook | ConvertTo-Json", json_parse=True) + + def get_transport_rules(self) -> dict: + """ + Get Exchange Online Transport Rules. + + Retrieves the current transport rules configured in Exchange Online. + + Returns: + dict: Transport rules in JSON format. + + Example: + >>> get_transport_rules() + { + "Name": "Rule1", + "SetSCL": -1, + "SenderDomainIs": ["example.com"] + } + """ + return self.execute("Get-TransportRule | ConvertTo-Json", json_parse=True) def get_connection_filter_policy(self) -> dict: """ @@ -359,7 +445,8 @@ class M365PowerShell(PowerShellSession): } """ return self.execute( - "Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json" + "Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json", + json_parse=True, ) def get_dkim_config(self) -> dict: @@ -378,4 +465,86 @@ class M365PowerShell(PowerShellSession): "Enabled": true } """ - return self.execute("Get-DkimSigningConfig | ConvertTo-Json") + return self.execute("Get-DkimSigningConfig | ConvertTo-Json", json_parse=True) + + def get_inbound_spam_filter_policy(self) -> dict: + """ + Get Inbound Spam Filter Policy. + + Retrieves the current inbound spam filter policy settings for Exchange Online. + + Returns: + dict: Inbound spam filter policy settings in JSON format. + + Example: + >>> get_inbound_spam_filter_policy() + { + "Identity": "Default", + "AllowedSenderDomains": "[]" + } + """ + return self.execute( + "Get-HostedContentFilterPolicy | ConvertTo-Json", json_parse=True + ) + + +# This function is used to install the required M365 PowerShell modules in Docker containers +def initialize_m365_powershell_modules(): + """ + Initialize required PowerShell modules. + + Checks if the required PowerShell modules are installed and installs them if necessary. + This method ensures that all required modules for M365 operations are available. + + Returns: + bool: True if all modules were successfully initialized, False otherwise + """ + + REQUIRED_MODULES = [ + "ExchangeOnlineManagement", + "MicrosoftTeams", + ] + + 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 + ) + + # Install module if not installed + if not result: + install_result = pwsh.execute( + f'Install-Module -Name "{module}" -Force -AllowClobber -Scope CurrentUser', + timeout=30, + ) + if install_result: + logger.warning( + f"Unexpected output while installing module {module}: {install_result}" + ) + else: + logger.info(f"Successfully installed module {module}") + + # Import module + pwsh.execute(f'Import-Module -Name "{module}" -Force', timeout=1) + + except Exception as error: + logger.error(f"Failed to initialize module {module}: {str(error)}") + return False + + return True + finally: + pwsh.close() + + +def main(): + if initialize_m365_powershell_modules(): + logger.info("M365 PowerShell modules initialized successfully") + else: + logger.error("Failed to initialize M365 PowerShell modules") + + +if __name__ == "__main__": + main() diff --git a/prowler/providers/m365/lib/service/service.py b/prowler/providers/m365/lib/service/service.py index 02aeb2762c..b218aac283 100644 --- a/prowler/providers/m365/lib/service/service.py +++ b/prowler/providers/m365/lib/service/service.py @@ -13,5 +13,7 @@ class M365Service: self.audit_config = provider.audit_config self.fixer_config = provider.fixer_config - if provider.credentials: - self.powershell = M365PowerShell(provider.credentials) + # Initialize PowerShell client only if credentials are available + self.powershell = ( + M365PowerShell(provider.credentials) if provider.credentials else None + ) diff --git a/prowler/providers/m365/m365_provider.py b/prowler/providers/m365/m365_provider.py index cd4438da72..a8e4ce4ec2 100644 --- a/prowler/providers/m365/m365_provider.py +++ b/prowler/providers/m365/m365_provider.py @@ -1,6 +1,5 @@ import asyncio import os -import re from argparse import ArgumentTypeError from os import getenv from uuid import UUID @@ -40,19 +39,24 @@ from prowler.providers.m365.exceptions.exceptions import ( M365HTTPResponseError, M365InteractiveBrowserCredentialError, M365InvalidProviderIdError, - M365MissingEnvironmentUserCredentialsError, + M365MissingEnvironmentCredentialsError, M365NoAuthenticationMethodError, M365NotTenantIdButClientIdAndClientSecretError, M365NotValidClientIdError, M365NotValidClientSecretError, + M365NotValidEncryptedPasswordError, M365NotValidTenantIdError, + M365NotValidUserError, M365SetUpRegionConfigError, M365SetUpSessionError, M365TenantIdAndClientIdNotBelongingToClientSecretError, M365TenantIdAndClientSecretNotBelongingToClientIdError, ) from prowler.providers.m365.lib.mutelist.mutelist import M365Mutelist -from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell +from prowler.providers.m365.lib.powershell.m365_powershell import ( + M365PowerShell, + initialize_m365_powershell_modules, +) from prowler.providers.m365.lib.regions.regions import get_regions_config from prowler.providers.m365.models import ( M365Credentials, @@ -99,21 +103,22 @@ class M365Provider(Provider): _audit_config: dict _region_config: M365RegionConfig _mutelist: M365Mutelist - _credentials: M365Credentials + _credentials: M365Credentials = {} # TODO: this is not optional, enforce for all providers audit_metadata: Audit_Metadata def __init__( self, - sp_env_auth: bool, - env_auth: bool, - az_cli_auth: bool, - browser_auth: bool, + sp_env_auth: bool = False, + env_auth: bool = False, + az_cli_auth: bool = False, + browser_auth: bool = False, tenant_id: str = None, client_id: str = None, client_secret: str = None, user: str = None, encrypted_password: str = None, + init_modules: bool = False, region: str = "M365Global", config_content: dict = None, config_path: str = None, @@ -187,9 +192,6 @@ class M365Provider(Provider): self._region_config, ) - # Set up PowerShell session credentials - self._credentials = self.setup_powershell(env_auth, m365_credentials) - # Set up the identity self._identity = self.setup_identity( az_cli_auth, @@ -199,6 +201,14 @@ class M365Provider(Provider): client_id, ) + # Set up PowerShell session credentials + self._credentials = self.setup_powershell( + env_auth=env_auth, + m365_credentials=m365_credentials, + provider_id=self.identity.tenant_domain, + init_modules=init_modules, + ) + # Audit Config if config_content: self._audit_config = config_content @@ -294,8 +304,8 @@ class M365Provider(Provider): M365BrowserAuthNoTenantIDError: If browser authentication is enabled but the tenant ID is not found. """ - if not client_id and not client_secret and not user and not encrypted_password: - if not browser_auth and tenant_id: + if not client_id and not client_secret: + if not browser_auth and tenant_id and not env_auth: raise M365BrowserAuthNoFlagError( file=os.path.basename(__file__), message="M365 tenant ID error: browser authentication flag (--browser-auth) not found", @@ -315,6 +325,12 @@ class M365Provider(Provider): file=os.path.basename(__file__), message="M365 Tenant ID (--tenant-id) is required for browser authentication mode", ) + elif env_auth: + if not user or not encrypted_password or not tenant_id: + raise M365MissingEnvironmentCredentialsError( + file=os.path.basename(__file__), + message="M365 provider requires AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, M365_USER and M365_ENCRYPTED_PASSWORD environment variables to be set when using --env-auth", + ) else: if not tenant_id: raise M365NotTenantIdButClientIdAndClientSecretError( @@ -362,7 +378,10 @@ class M365Provider(Provider): @staticmethod def setup_powershell( - env_auth: bool = False, m365_credentials: dict = {} + env_auth: bool = False, + m365_credentials: dict = {}, + provider_id: str = None, + init_modules: bool = False, ) -> M365Credentials: """Gets the M365 credentials. @@ -382,6 +401,7 @@ class M365Provider(Provider): client_id=m365_credentials.get("client_id", ""), client_secret=m365_credentials.get("client_secret", ""), tenant_id=m365_credentials.get("tenant_id", ""), + provider_id=provider_id, ) elif env_auth: m365_user = getenv("M365_USER") @@ -394,7 +414,7 @@ class M365Provider(Provider): logger.critical( "M365 provider: Missing M365_USER or M365_ENCRYPTED_PASSWORD environment variables needed for credentials authentication" ) - raise M365MissingEnvironmentUserCredentialsError( + raise M365MissingEnvironmentCredentialsError( file=os.path.basename(__file__), message="Missing M365_USER or M365_ENCRYPTED_PASSWORD environment variables required for credentials authentication.", ) @@ -404,12 +424,15 @@ class M365Provider(Provider): client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, + provider_id=provider_id, ) if credentials: test_session = M365PowerShell(credentials) try: if test_session.test_credentials(credentials): + if init_modules: + initialize_m365_powershell_modules() return credentials raise M365EnvironmentUserCredentialsError( file=os.path.basename(__file__), @@ -434,8 +457,11 @@ class M365Provider(Provider): f"M365 Region: {Fore.YELLOW}{self.region_config.name}{Style.RESET_ALL}", f"M365 Tenant Domain: {Fore.YELLOW}{self._identity.tenant_domain}{Style.RESET_ALL} M365 Tenant ID: {Fore.YELLOW}{self._identity.tenant_id}{Style.RESET_ALL}", f"M365 Identity Type: {Fore.YELLOW}{self._identity.identity_type}{Style.RESET_ALL} M365 Identity ID: {Fore.YELLOW}{self._identity.identity_id}{Style.RESET_ALL}", - f"M365 User: {Fore.YELLOW}{self.credentials.user}{Style.RESET_ALL}", ] + if self.credentials and self.credentials.user: + report_lines.append( + f"M365 User: {Fore.YELLOW}{self.credentials.user}{Style.RESET_ALL}" + ) report_title = ( f"{Style.BRIGHT}Using the M365 credentials below:{Style.RESET_ALL}" ) @@ -466,6 +492,9 @@ class M365Provider(Provider): - tenant_id: The M365 Active Directory tenant ID. - client_id: The M365 client ID. - client_secret: The M365 client secret + - user: The M365 user email + - encrypted_password: The M365 encrypted password + - provider_id: The M365 provider ID (in this case the Tenant ID). region_config (M365RegionConfig): The region configuration object. Returns: @@ -587,15 +616,16 @@ class M365Provider(Provider): browser_auth: bool = False, tenant_id: str = None, region: str = "M365Global", - raise_on_exception=True, - client_id=None, - client_secret=None, - user=None, - encrypted_password=None, + raise_on_exception: bool = True, + client_id: str = None, + client_secret: str = None, + user: str = None, + encrypted_password: str = None, + provider_id: str = None, ) -> Connection: - """Test connection to M365 subscription. + """Test connection to M365 tenant and PowerShell modules. - Test the connection to an M365 subscription using the provided credentials. + Test the connection to an M365 tenant and PowerShell modules using the provided credentials. Args: @@ -610,6 +640,7 @@ class M365Provider(Provider): client_secret (str): The M365 client secret. user (str): The M365 user email. encrypted_password (str): The M365 encrypted_password. + provider_id (str): The M365 provider ID (in this case the Tenant ID). Returns: @@ -622,7 +653,7 @@ class M365Provider(Provider): M365InteractiveBrowserCredentialError: If there is an error in retrieving the M365 credentials using browser authentication. M365HTTPResponseError: If there is an HTTP response error. M365ConfigCredentialsError: If there is an error in configuring the M365 credentials from a dictionary. - + M365InvalidProviderIdError: If the provider ID does not match the application tenant domain. Examples: >>> M365Provider.test_connection(az_cli_auth=True) @@ -649,11 +680,22 @@ class M365Provider(Provider): # Get the dict from the static credentials m365_credentials = None if tenant_id and client_id and client_secret: - m365_credentials = M365Provider.validate_static_credentials( - tenant_id=tenant_id, - client_id=client_id, - client_secret=client_secret, - ) + if not user and not encrypted_password: + m365_credentials = M365Provider.validate_static_credentials( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret, + user="user", + encrypted_password="encrypted_password", + ) + else: + m365_credentials = M365Provider.validate_static_credentials( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret, + user=user, + encrypted_password=encrypted_password, + ) # Set up the M365 session credentials = M365Provider.setup_session( @@ -668,7 +710,30 @@ class M365Provider(Provider): GraphServiceClient(credentials=credentials) - logger.info("M365 provider: Connection to M365 successful") + logger.info("M365 provider: Connection to MSGraph successful") + + # Set up PowerShell credentials + if user and encrypted_password: + M365Provider.setup_powershell( + env_auth, + m365_credentials, + provider_id, + ) + else: + logger.info( + "M365 provider: Connection to PowerShell has not been requested" + ) + + logger.info("M365 provider: Connection to PowerShell successful") + + # Check that user domain, provider_id and Graph client tenant_domain are the same + if user and encrypted_password: + user_domain = user.split("@")[1] + if provider_id and user_domain != provider_id: + raise M365InvalidProviderIdError( + file=os.path.basename(__file__), + message=f"Provider ID {provider_id} does not match Application tenant domain {user_domain}", + ) return Connection(is_connected=True) @@ -896,7 +961,11 @@ class M365Provider(Provider): @staticmethod def validate_static_credentials( - tenant_id: str = None, client_id: str = None, client_secret: str = None + tenant_id: str = None, + client_id: str = None, + client_secret: str = None, + user: str = None, + encrypted_password: str = None, ) -> dict: """ Validates the static credentials for the M365 provider. @@ -905,6 +974,8 @@ class M365Provider(Provider): tenant_id (str): The M365 Active Directory tenant ID. client_id (str): The M365 client ID. client_secret (str): The M365 client secret. + user (str): The M365 user email. + encrypted_password (str): The M365 encrypted password. Raises: M365NotValidTenantIdError: If the provided M365 Tenant ID is not valid. @@ -934,19 +1005,36 @@ class M365Provider(Provider): file=os.path.basename(__file__), message="The provided M365 Client ID is not valid.", ) + # Validate the Client Secret - if not re.match("^[a-zA-Z0-9._~-]+$", client_secret): + if not client_secret: raise M365NotValidClientSecretError( file=os.path.basename(__file__), message="The provided M365 Client Secret is not valid.", ) + # Validate the User + if not user: + raise M365NotValidUserError( + file=os.path.basename(__file__), + message="The provided M365 User is not valid.", + ) + + # Validate the Encrypted Password + if not encrypted_password: + raise M365NotValidEncryptedPasswordError( + file=os.path.basename(__file__), + message="The provided M365 Encrypted Password is not valid.", + ) + try: M365Provider.verify_client(tenant_id, client_id, client_secret) return { "tenant_id": tenant_id, "client_id": client_id, "client_secret": client_secret, + "user": user, + "encrypted_password": encrypted_password, } except M365NotValidTenantIdError as tenant_id_error: logger.error( diff --git a/prowler/providers/m365/models.py b/prowler/providers/m365/models.py index f260653555..a2e3b68151 100644 --- a/prowler/providers/m365/models.py +++ b/prowler/providers/m365/models.py @@ -25,6 +25,7 @@ class M365Credentials(BaseModel): client_id: str = "" client_secret: str = "" tenant_id: str = "" + provider_id: str = "" class M365OutputOptions(ProviderOutputOptions): diff --git a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/__init__.py b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json new file mode 100644 index 0000000000..2d4537c400 --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "defender_antispam_policy_inbound_no_allowed_domains", + "CheckTitle": "Ensure inbound anti-spam policies do not contain allowed domains", + "CheckType": [], + "ServiceName": "defender", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "low", + "ResourceType": "Defender Anti-Spam Policy", + "Description": "Ensure that inbound anti-spam policies do not have any domains listed in the AllowedSenderDomains. Messages from these domains bypass most email protections, increasing the risk of successful phishing attacks.", + "Risk": "Having domains in the AllowedSenderDomains list allows emails from these domains to bypass essential security checks, increasing the risk of phishing attacks and other malicious activities.", + "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/anti-spam-protection-about#allow-and-block-lists-in-anti-spam-policies", + "Remediation": { + "Code": { + "CLI": "Set-HostedContentFilterPolicy -Identity -AllowedSenderDomains @{}", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft 365 Defender (https://security.microsoft.com). 2. Click to expand Email & collaboration and select Policies & rules > Threat policies. 3. Under Policies, select Anti-spam. 4. Open each out-of-compliance inbound anti-spam policy by clicking on it. 5. Click Edit allowed and blocked senders and domains. 6. Select Allow domains. 7. Delete each domain from the domains list. 8. Click Done > Save. 9. Repeat as needed.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that the AllowedSenderDomains list in your inbound anti-spam policies is empty to prevent bypassing essential security checks.", + "Url": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/configure-the-allowed-sender-domains?view=o365-worldwide" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py new file mode 100644 index 0000000000..71dbb7e45f --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py @@ -0,0 +1,42 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.defender.defender_client import defender_client + + +class defender_antispam_policy_inbound_no_allowed_domains(Check): + """ + Check if the inbound anti-spam policies do not contain allowed domains in Microsoft 365 Defender. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check to verify if inbound anti-spam policies do not contain allowed domains. + + This method checks each inbound anti-spam policy to determine if the AllowedSenderDomains + list is empty or undefined. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + for policy in defender_client.inbound_spam_policies: + report = CheckReportM365( + metadata=self.metadata(), + resource=policy, + resource_name="Defender Inbound Spam Policy", + resource_id=policy.identity, + ) + report.status = "PASS" + report.status_extended = f"Inbound anti-spam policy {policy.identity} does not contain allowed domains." + + if policy.allowed_sender_domains: + report.status = "FAIL" + report.status_extended = f"Inbound anti-spam policy {policy.identity} contains allowed domains: {policy.allowed_sender_domains}." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/defender/defender_service.py b/prowler/providers/m365/services/defender/defender_service.py index 51f53a0606..3ed0eee58b 100644 --- a/prowler/providers/m365/services/defender/defender_service.py +++ b/prowler/providers/m365/services/defender/defender_service.py @@ -10,15 +10,26 @@ from prowler.providers.m365.m365_provider import M365Provider class Defender(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) - self.powershell.connect_exchange_online() - self.malware_policies = self._get_malware_filter_policy() - self.outbound_spam_policies = self._get_outbound_spam_filter_policy() - self.outbound_spam_rules = self._get_outbound_spam_filter_rule() - self.antiphishing_policies = self._get_antiphising_policy() - self.antiphising_rules = self._get_antiphising_rules() - self.connection_filter_policy = self._get_connection_filter_policy() - self.dkim_configurations = self._get_dkim_config() - self.powershell.close() + self.malware_policies = [] + self.outbound_spam_policies = {} + self.outbound_spam_rules = {} + self.antiphishing_policies = {} + self.antiphising_rules = {} + self.connection_filter_policy = None + self.dkim_configurations = [] + self.inbound_spam_policies = [] + + if self.powershell: + self.powershell.connect_exchange_online() + self.malware_policies = self._get_malware_filter_policy() + self.outbound_spam_policies = self._get_outbound_spam_filter_policy() + self.outbound_spam_rules = self._get_outbound_spam_filter_rule() + self.antiphishing_policies = self._get_antiphising_policy() + self.antiphising_rules = self._get_antiphising_rules() + self.connection_filter_policy = self._get_connection_filter_policy() + self.dkim_configurations = self._get_dkim_config() + self.inbound_spam_policies = self._get_inbound_spam_filter_policy() + self.powershell.close() def _get_malware_filter_policy(self): logger.info("M365 - Getting Defender malware filter policy...") @@ -179,6 +190,31 @@ class Defender(M365Service): ) return outbound_spam_rules + def _get_inbound_spam_filter_policy(self): + logger.info("Microsoft365 - Getting Defender inbound spam filter policy...") + inbound_spam_policies = [] + try: + inbound_spam_policy = self.powershell.get_inbound_spam_filter_policy() + if not inbound_spam_policy: + return inbound_spam_policies + if isinstance(inbound_spam_policy, dict): + inbound_spam_policy = [inbound_spam_policy] + for policy in inbound_spam_policy: + if policy: + inbound_spam_policies.append( + DefenderInboundSpamPolicy( + identity=policy.get("Identity", ""), + allowed_sender_domains=policy.get( + "AllowedSenderDomains", [] + ), + ) + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return inbound_spam_policies + class MalwarePolicy(BaseModel): enable_file_filter: bool @@ -224,3 +260,8 @@ class OutboundSpamPolicy(BaseModel): class OutboundSpamRule(BaseModel): state: str + + +class DefenderInboundSpamPolicy(BaseModel): + identity: str + allowed_sender_domains: list[str] = [] diff --git a/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/__init__.py b/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.metadata.json b/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.metadata.json new file mode 100644 index 0000000000..a1ddac7890 --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "exchange_external_email_tagging_enabled", + "CheckTitle": "Ensure email from external senders is identified.", + "CheckType": [], + "ServiceName": "exchange", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Exchange External Mail Tagging", + "Description": "Ensure that emails from external senders are identified using the native External tag experience in Outlook clients, which helps users recognize messages originating outside the organization.", + "Risk": "If external email tagging is not enabled, users may be unable to quickly identify emails coming from outside the organization, increasing the risk of phishing or social engineering attacks.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-externalinoutlook?view=exchange-ps", + "Remediation": { + "Code": { + "CLI": "Set-ExternalInOutlook -Enabled $true", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable the External tag for Outlook to help users visually identify emails from outside the organization.", + "Url": "https://techcommunity.microsoft.com/t5/exchange-team-blog/native-external-sender-callouts-on-email-in-outlook/ba-p/2250098" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.py b/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.py new file mode 100644 index 0000000000..5d2c3eeb81 --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled.py @@ -0,0 +1,41 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.exchange.exchange_client import exchange_client + + +class exchange_external_email_tagging_enabled(Check): + """Ensure email from external senders is identified. + + This check verifies that the native "External" sender tag feature is enabled + in Exchange so that messages from outside the organization are automatically marked. + """ + + def execute(self) -> List[CheckReportM365]: + """Run the check to validate that external sender tagging is enabled. + + Iterates through the external mail configuration to determine if the + ExternalInOutlook setting is turned on and generates a report accordingly. + + Returns: + List[CheckReportM365]: A list of reports for each organization identity. + """ + findings = [] + + for mail_config in exchange_client.external_mail_config: + report = CheckReportM365( + metadata=self.metadata(), + resource=mail_config, + resource_name=mail_config.identity, + resource_id=mail_config.identity, + ) + report.status = "FAIL" + report.status_extended = f"External sender tagging is disabled for Exchange identity {mail_config.identity}." + + if mail_config.external_mail_tag_enabled: + report.status = "PASS" + report.status_extended = f"External sender tagging is enabled for Exchange identity {mail_config.identity}." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/__init__.py b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.metadata.json b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.metadata.json new file mode 100644 index 0000000000..1657d50dbd --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "exchange_mailbox_policy_additional_storage_restricted", + "CheckTitle": "Ensure additional storage providers are restricted in Outlook on the web.", + "CheckType": [], + "ServiceName": "exchange", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Exchange Mailboxes Policy", + "Description": "Restrict the availability of additional storage providers (e.g., Box, Dropbox, Google Drive) in Outlook on the web to prevent users from accessing external storage services through the OWA interface.", + "Risk": "Allowing users to access third-party storage providers from Outlook on the web increases the risk of data exfiltration and exposure to untrusted content or malware.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-owamailboxpolicy?view=exchange-ps", + "Remediation": { + "Code": { + "CLI": "Set-OwaMailboxPolicy -Identity OwaMailboxPolicy-Default -AdditionalStorageProvidersAvailable $false", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable access to additional storage providers in Outlook on the web to reduce the risk of data leakage.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-owamailboxpolicy?view=exchange-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py new file mode 100644 index 0000000000..05ecd7cc0d --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py @@ -0,0 +1,44 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.exchange.exchange_client import exchange_client + + +class exchange_mailbox_policy_additional_storage_restricted(Check): + """Check if Exchange mailbox policy restricts additional storage providers. + + This check ensures that the mailbox policy does not allow additional storage providers. + """ + + def execute(self) -> List[CheckReportM365]: + """Run the check to validate Exchange mailbox policy restrictions. + + Iterates through the mailbox policy configuration to determine if additional storage + providers are restricted and generates a report based on the policy status. + + Returns: + List[CheckReportM365]: A list of reports with the restriction status for the mailbox policy. + """ + findings = [] + mailbox_policy = exchange_client.mailbox_policy + if mailbox_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=mailbox_policy, + resource_name="Exchange Mailbox Policy", + resource_id=mailbox_policy.id, + ) + report.status = "FAIL" + report.status_extended = ( + "Exchange mailbox policy allows additional storage providers." + ) + + if not mailbox_policy.additional_storage_enabled: + report.status = "PASS" + report.status_extended = ( + "Exchange mailbox policy restricts additional storage providers." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/exchange/exchange_service.py b/prowler/providers/m365/services/exchange/exchange_service.py index f369edb6b5..dbcc44499f 100644 --- a/prowler/providers/m365/services/exchange/exchange_service.py +++ b/prowler/providers/m365/services/exchange/exchange_service.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic import BaseModel from prowler.lib.logger import logger @@ -8,10 +10,20 @@ from prowler.providers.m365.m365_provider import M365Provider class Exchange(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) - self.powershell.connect_exchange_online() - self.organization_config = self._get_organization_config() - self.mailboxes_config = self._get_mailbox_audit_config() - self.powershell.close() + self.organization_config = None + self.mailboxes_config = [] + self.external_mail_config = [] + self.transport_rules = [] + self.mailbox_policy = None + + if self.powershell: + self.powershell.connect_exchange_online() + self.organization_config = self._get_organization_config() + self.mailboxes_config = self._get_mailbox_audit_config() + self.external_mail_config = self._get_external_mail_config() + self.transport_rules = self._get_transport_rules() + self.mailbox_policy = self._get_mailbox_policy() + self.powershell.close() def _get_organization_config(self): logger.info("Microsoft365 - Getting Exchange Organization configuration...") @@ -53,6 +65,73 @@ class Exchange(M365Service): ) return mailboxes_config + def _get_external_mail_config(self): + logger.info("Microsoft365 - Getting external mail configuration...") + external_mail_config = [] + try: + external_mail_configuration = self.powershell.get_external_mail_config() + if not external_mail_configuration: + return external_mail_config + if isinstance(external_mail_configuration, dict): + external_mail_configuration = [external_mail_configuration] + for external_mail in external_mail_configuration: + if external_mail: + external_mail_config.append( + ExternalMailConfig( + identity=external_mail.get("Identity", ""), + external_mail_tag_enabled=external_mail.get( + "Enabled", False + ), + ) + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return external_mail_config + + def _get_transport_rules(self): + logger.info("Microsoft365 - Getting transport rules configuration...") + transport_rules = [] + try: + rules_data = self.powershell.get_transport_rules() + if not rules_data: + return transport_rules + if isinstance(rules_data, dict): + rules_data = [rules_data] + for rule in rules_data: + if rule: + transport_rules.append( + TransportRule( + name=rule.get("Name", ""), + scl=rule.get("SetSCL", None), + sender_domain_is=rule.get("SenderDomainIs", []), + ) + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return transport_rules + + def _get_mailbox_policy(self): + logger.info("Microsoft365 - Getting mailbox policy configuration...") + mailboxes_policy = None + try: + mailbox_policy = self.powershell.get_mailbox_policy() + if mailbox_policy: + mailboxes_policy = MailboxPolicy( + id=mailbox_policy.get("Id", ""), + additional_storage_enabled=mailbox_policy.get( + "AdditionalStorageProvidersAvailable", True + ), + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return mailboxes_policy + class Organization(BaseModel): name: str @@ -64,3 +143,19 @@ class MailboxAuditConfig(BaseModel): name: str id: str audit_bypass_enabled: bool + + +class ExternalMailConfig(BaseModel): + identity: str + external_mail_tag_enabled: bool + + +class TransportRule(BaseModel): + name: str + scl: Optional[int] + sender_domain_is: list[str] + + +class MailboxPolicy(BaseModel): + id: str + additional_storage_enabled: bool diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/__init__.py b/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.metadata.json b/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.metadata.json new file mode 100644 index 0000000000..d762b3be0e --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "exchange_transport_rules_whitelist_disabled", + "CheckTitle": "Ensure mail transport rules do not whitelist specific domains", + "CheckType": [], + "ServiceName": "exchange", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Exchange Transport Rules", + "Description": "Mail flow rules (transport rules) in Exchange Online are used to identify and take action on messages that flow through the organization.", + "Risk": "Whitelisting domains in transport rules bypasses regular malware and phishing scanning, which can enable an attacker to launch attacks against your users from a safe haven domain.", + "RelatedUrl": "https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/configuration-best-practices", + "Remediation": { + "Code": { + "CLI": "Remove-TransportRule -Identity ", + "NativeIaC": "", + "Other": "1. Navigate to Exchange admin center https://admin.exchange.microsoft.com.. 2. Click to expand Mail Flow and then select Rules. 3. For each rule that whitelists specific domains, select the rule and click the 'Delete' icon.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Remove transport rules that whitelist specific domains to ensure proper scanning.", + "Url": "https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.py b/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.py new file mode 100644 index 0000000000..58cd7b23bc --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled.py @@ -0,0 +1,47 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.exchange.exchange_client import exchange_client + + +class exchange_transport_rules_whitelist_disabled(Check): + """ + Check to ensure that no mail transport rules whitelist specific domains. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check to validate that no transport rules whitelist specific domains. + + This method retrieves all transport rules from the Exchange service and evaluates + whether any of them whitelist specific domains. A report is generated for each + transport rule. + + Returns: + List[CheckReportM365]: A list of findings with the status of each transport rule. + """ + findings = [] + + for rule in exchange_client.transport_rules: + report = CheckReportM365( + metadata=self.metadata(), + resource=rule, + resource_name=rule.name, + resource_id="ExchangeTransportRule", + ) + + report.status = "PASS" + report.status_extended = ( + f"Transport rule {rule.name} does not whitelist any domains." + ) + + if rule.sender_domain_is and rule.scl == -1: + report.status = "FAIL" + report.status_extended = f"Transport rule {rule.name} whitelists domains: {', '.join(rule.sender_domain_is)}." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/purview/purview_service.py b/prowler/providers/m365/services/purview/purview_service.py index 2323b28dce..138305c899 100644 --- a/prowler/providers/m365/services/purview/purview_service.py +++ b/prowler/providers/m365/services/purview/purview_service.py @@ -8,9 +8,12 @@ from prowler.providers.m365.m365_provider import M365Provider class Purview(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) - self.powershell.connect_exchange_online() - self.audit_log_config = self._get_audit_log_config() - self.powershell.close() + self.audit_log_config = None + + if self.powershell: + self.powershell.connect_exchange_online() + self.audit_log_config = self._get_audit_log_config() + self.powershell.close() def _get_audit_log_config(self): logger.info("M365 - Getting Admin Audit Log settings...") diff --git a/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/__init__.py b/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.metadata.json b/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.metadata.json new file mode 100644 index 0000000000..8f08f26c60 --- /dev/null +++ b/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "sharepoint_onedrive_sync_restricted_unmanaged_devices", + "CheckTitle": "Ensure OneDrive sync is restricted for unmanaged devices.", + "CheckType": [], + "ServiceName": "sharepoint", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Sharepoint Settings", + "Description": "Microsoft OneDrive allows users to sign in their cloud tenant account and begin syncing select folders or the entire contents of OneDrive to a local computer. By default, this includes any computer with OneDrive already installed, whether it is Entra Joined, Entra Hybrid Joined or Active Directory Domain joined. The recommended state for this setting is Allow syncing only on computers joined to specific domains Enabled: Specify the AD domain GUID(s).", + "Risk": "Unmanaged devices can pose a security risk by allowing users to sync sensitive data to unauthorized devices, potentially leading to data leakage or unauthorized access.", + "RelatedUrl": "https://learn.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0", + "Remediation": { + "Code": { + "CLI": "Set-SPOTenantSyncClientRestriction -Enable -DomainGuids '; ; ...'", + "NativeIaC": "", + "Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint 2. Click Settings then select OneDrive - Sync. 3. Check the Allow syncing only on computers joined to specific domains. 4. Use the Get-ADDomain PowerShell command on the on-premises server to obtain the GUID for each on-premises domain. 5. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Restrict OneDrive sync to managed devices to prevent unauthorized access to sensitive data.", + "Url": "https://learn.microsoft.com/en-us/sharepoint/allow-syncing-only-on-specific-domains" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.py b/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.py new file mode 100644 index 0000000000..ca6d5415fa --- /dev/null +++ b/prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices.py @@ -0,0 +1,48 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.sharepoint.sharepoint_client import ( + sharepoint_client, +) + + +class sharepoint_onedrive_sync_restricted_unmanaged_devices(Check): + """ + Check if OneDrive sync is restricted for unmanaged devices. + + This check verifies that OneDrive sync is restricted to managed devices only. + Unmanaged devices can pose a security risk by allowing users to sync sensitive data to unauthorized devices, + potentially leading to data leakage or unauthorized access. + + The check fails if OneDrive sync is not restricted to managed devices (AllowedDomainGuidsForSyncApp is empty). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the OneDrive sync restriction check. + + Retrieves the OneDrive sync settings from the Microsoft 365 SharePoint client and + generates a report indicating whether OneDrive sync is restricted to managed devices only. + + Returns: + List[CheckReportM365]: A list containing the report object with the result of the check. + """ + findings = [] + settings = sharepoint_client.settings + if settings: + report = CheckReportM365( + self.metadata(), + resource=settings if settings else {}, + resource_name="SharePoint Settings", + resource_id=sharepoint_client.tenant_domain, + ) + report.status = "PASS" + report.status_extended = "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices." + + if len(settings.allowedDomainGuidsForSyncApp) == 0: + report.status = "FAIL" + report.status_extended = "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/sharepoint/sharepoint_service.py b/prowler/providers/m365/services/sharepoint/sharepoint_service.py index 6040a321b8..02a701821f 100644 --- a/prowler/providers/m365/services/sharepoint/sharepoint_service.py +++ b/prowler/providers/m365/services/sharepoint/sharepoint_service.py @@ -1,3 +1,4 @@ +import uuid from asyncio import gather, get_event_loop from typing import List, Optional @@ -26,7 +27,6 @@ class SharePoint(M365Service): settings = None try: global_settings = await self.client.admin.sharepoint.settings.get() - settings = SharePointSettings( sharingCapability=( str(global_settings.sharing_capability).split(".")[-1] @@ -38,6 +38,7 @@ class SharePoint(M365Service): sharingDomainRestrictionMode=global_settings.sharing_domain_restriction_mode, legacyAuth=global_settings.is_legacy_auth_protocols_enabled, resharingEnabled=global_settings.is_resharing_by_external_users_enabled, + allowedDomainGuidsForSyncApp=global_settings.allowed_domain_guids_for_sync_app, ) except ODataError as error: @@ -60,3 +61,4 @@ class SharePointSettings(BaseModel): sharingDomainRestrictionMode: str resharingEnabled: bool legacyAuth: bool + allowedDomainGuidsForSyncApp: List[uuid.UUID] diff --git a/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.metadata.json new file mode 100644 index 0000000000..38258f5706 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_chat_anonymous_users_disabled", + "CheckTitle": "Ensure meeting chat does not allow anonymous users", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure meeting chat does not allow anonymous users.", + "Risk": "Allowing anonymous users to participate in meeting chat can expose sensitive information and increase the risk of inappropriate content being shared by unverified participants.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -MeetingChatEnabledType 'EnabledExceptAnonymous'", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under meeting engagement verify that Meeting chat is set to On for everyone but anonymous users.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Restrict chat access during meetings to only authenticated and authorized users. Disable chat capabilities for anonymous users to maintain confidentiality and prevent misuse.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.py new file mode 100644 index 0000000000..9eacc6b8af --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled.py @@ -0,0 +1,48 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_chat_anonymous_users_disabled(Check): + """Check if meeting chat does not allow anonymous users. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for meeting chat does not allow anonymous users. + + This method checks if meeting chat does not allow anonymous users. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = "Meeting chat allows anonymous users." + + allowed_meeting_chat_settings = { + "EnabledExceptAnonymous", + "EnabledInMeetingOnlyForAllExceptAnonymous", + } + + if ( + global_meeting_policy.meeting_chat_enabled_type + in allowed_meeting_chat_settings + ): + report.status = "PASS" + report.status_extended = "Meeting chat does not allow anonymous users." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.metadata.json new file mode 100644 index 0000000000..430a0e2277 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_dial_in_lobby_bypass_disabled", + "CheckTitle": "Ensure that dial-in users cannot bypass the lobby in Teams meetings", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure that dial-in users cannot bypass the lobby in Teams meetings", + "Risk": "Allowing dial-in users to bypass the lobby may result in unauthorized or unauthenticated individuals joining sensitive meetings without prior validation, increasing the risk of information leakage or meeting disruptions.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AllowPSTNUsersToBypassLobby $false", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under meeting join & lobby set People dialing in can bypass the lobby to Off.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Require all users dialing in by phone to wait in the lobby until admitted by the meeting organizer, co-organizer, or presenter. This ensures proper vetting before granting access to potentially sensitive discussions.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.py new file mode 100644 index 0000000000..0014be6d61 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled.py @@ -0,0 +1,40 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_dial_in_lobby_bypass_disabled(Check): + """Check if users dialing in can't bypass the lobby. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for users dialing in can't bypass the lobby. + + This method checks if users dialing in can't bypass the lobby. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = "Dial-in users can bypass the lobby." + + if not global_meeting_policy.allow_pstn_users_to_bypass_lobby: + report.status = "PASS" + report.status_extended = "Dial-in users cannot bypass the lobby." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.metadata.json new file mode 100644 index 0000000000..50c5d8d138 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_external_chat_disabled", + "CheckTitle": "Ensure external meeting chat is off", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure users can't read or write messages in external meeting chats with untrusted organizations.", + "Risk": "Allowing chat in external meetings increases the risk of exploits like GIFShell or DarkGate malware being delivered to users through untrusted organizations.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AllowExternalNonTrustedMeetingChat $false", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under meeting engagement set External meeting chat to Off.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable external meeting chat to prevent potential security risks from untrusted organizations. This helps protect against exploits like GIFShell or DarkGate malware.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.py new file mode 100644 index 0000000000..ac4787f7e6 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled.py @@ -0,0 +1,44 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_external_chat_disabled(Check): + """Check if external meeting chat is disabled. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for external meeting chat settings. + + This method checks if external meeting chat is disabled for untrusted organizations. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = ( + "External meeting chat is enabled for untrusted organizations." + ) + + if not global_meeting_policy.allow_external_non_trusted_meeting_chat: + report.status = "PASS" + report.status_extended = ( + "External meeting chat is disabled for untrusted organizations." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.metadata.json new file mode 100644 index 0000000000..342a26dbe1 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_external_control_disabled", + "CheckTitle": "Ensure external participants can't give or request control", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure external participants can't give or request control in Teams meetings.", + "Risk": "Allowing external participants to give or request control during meetings could lead to unauthorized content sharing or malicious actions by external users.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AllowExternalParticipantGiveRequestControl $false", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under content sharing set External participants can give or request control to Off.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable the ability for external participants to give or request control during Teams meetings to prevent unauthorized content sharing and maintain meeting security.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.py new file mode 100644 index 0000000000..c08ee4ee32 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled.py @@ -0,0 +1,46 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_external_control_disabled(Check): + """Check if external participants can't give or request control in meetings. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for external participants' control permissions in meetings. + + This method checks if external participants are prevented from giving or requesting control in meetings. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = ( + "External participants can give or request control." + ) + + if ( + not global_meeting_policy.allow_external_participant_give_request_control + ): + report.status = "PASS" + report.status_extended = ( + "External participants cannot give or request control." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json new file mode 100644 index 0000000000..7bbc9b0a54 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_external_lobby_bypass_disabled", + "CheckTitle": "Ensure only people in the organization can bypass the lobby.", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure only people in the organization can bypass the lobby.", + "Risk": "Allowing external users or unauthenticated participants to bypass the lobby increases the risk of unauthorized access to sensitive meetings and potential disruptions. It may also lead to unscheduled meetings being initiated by external parties.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AutoAdmittedUsers 'EveryoneInCompanyExcludingGuests' ", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under meeting join & lobby set Who can bypass the lobby to People in my org.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that only people within the organization can bypass the lobby, requiring external users and dial-in participants to wait for approval from an organizer, co-organizer, or presenter. This helps secure sensitive meetings and prevents unauthorized access.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py new file mode 100644 index 0000000000..53796008f5 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py @@ -0,0 +1,52 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_external_lobby_bypass_disabled(Check): + """Check if only people in the organization can bypass the lobby. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for only people in the organization can bypass the lobby. + + This method checks if only people in the organization can bypass the lobby. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = ( + "People outside the organization can bypass the lobby." + ) + + allowed_bypass_settings = { + "EveryoneInCompanyExcludingGuests", + "OrganizerOnly", + "InvitedUsers", + } + if ( + global_meeting_policy.allow_external_users_to_bypass_lobby + in allowed_bypass_settings + ): + report.status = "PASS" + report.status_extended = ( + "Only people in the organization can bypass the lobby." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.metadata.json new file mode 100644 index 0000000000..d1117d0e2a --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_presenters_restricted", + "CheckTitle": "Ensure only organizers and co-organizers can present", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensure only organizers and co-organizers can present in a Teams meeting. The recommended state is 'Only organizers and co-organizers'.", + "Risk": "Allowing everyone to present increases the risk that a malicious user can inadvertently show inappropriate content.", + "RelatedUrl": "https://learn.microsoft.com/en-us/microsoftteams/meeting-who-present-request-control", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -DesignatedPresenterRoleMode \"OrganizerOnlyUserOverride\"", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under content sharing set Who can present to Only organizers and co-organizers.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Restrict presentation capabilities to only organizers and co-organizers to reduce the risk of inappropriate content being shown.", + "Url": "https://learn.microsoft.com/en-us/microsoftteams/meeting-who-present-request-control" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.py b/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.py new file mode 100644 index 0000000000..bfbdd169f2 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted.py @@ -0,0 +1,47 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_presenters_restricted(Check): + """Check if only organizers and co-organizers can present. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for meeting presenter settings. + + This method checks if only organizers and co-organizers can present. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = ( + "Not only organizers and co-organizers can present." + ) + + if ( + global_meeting_policy.designated_presenter_role_mode + == "OrganizerOnlyUserOverride" + ): + report.status = "PASS" + report.status_extended = ( + "Only organizers and co-organizers can present." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.metadata.json new file mode 100644 index 0000000000..5ebee7db6e --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "teams_meeting_recording_disabled", + "CheckTitle": "Ensure meeting recording is disabled by default", + "CheckType": [], + "ServiceName": "teams", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Teams Global Meeting Policy", + "Description": "Ensures that only authorized users, such as organizers, co-organizers, and leads, can initiate a recording.", + "Risk": "Allowing meeting recordings by default increases the risk of unauthorized individuals capturing and potentially sharing sensitive meeting content.", + "RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps", + "Remediation": { + "Code": { + "CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AllowCloudRecording $false", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under Recording & transcription set Meeting recording to Off.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable meeting recording in the Global meeting policy to ensure only authorized users can initiate recordings. Create separate policies for users or groups who need recording capabilities.", + "Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.py new file mode 100644 index 0000000000..3da4c89c9c --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled.py @@ -0,0 +1,40 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.teams.teams_client import teams_client + + +class teams_meeting_recording_disabled(Check): + """Check if meeting recording is disabled by default. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """Execute the check for meeting recording settings. + + This method checks if meeting recording is disabled in the Global meeting policy. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + global_meeting_policy = teams_client.global_meeting_policy + if global_meeting_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=global_meeting_policy if global_meeting_policy else {}, + resource_name="Teams Meetings Global (Org-wide default) Policy", + resource_id="teamsMeetingsGlobalPolicy", + ) + report.status = "FAIL" + report.status_extended = "Meeting recording is enabled by default." + + if not global_meeting_policy.allow_cloud_recording: + report.status = "PASS" + report.status_extended = "Meeting recording is disabled by default." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/teams/teams_service.py b/prowler/providers/m365/services/teams/teams_service.py index 7231176b9a..ad2d7b3721 100644 --- a/prowler/providers/m365/services/teams/teams_service.py +++ b/prowler/providers/m365/services/teams/teams_service.py @@ -8,11 +8,16 @@ from prowler.providers.m365.m365_provider import M365Provider class Teams(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) - self.powershell.connect_microsoft_teams() - self.teams_settings = self._get_teams_client_configuration() - self.global_meeting_policy = self._get_global_meeting_policy() - self.user_settings = self._get_user_settings() - self.powershell.close() + self.teams_settings = None + self.global_meeting_policy = None + self.user_settings = None + + if self.powershell: + self.powershell.connect_microsoft_teams() + self.teams_settings = self._get_teams_client_configuration() + self.global_meeting_policy = self._get_global_meeting_policy() + self.user_settings = self._get_user_settings() + self.powershell.close() def _get_teams_client_configuration(self): logger.info("M365 - Getting Teams settings...") @@ -52,6 +57,27 @@ class Teams(M365Service): allow_anonymous_users_to_start_meeting=global_meeting_policy.get( "AllowAnonymousUsersToStartMeeting", True ), + allow_external_participant_give_request_control=global_meeting_policy.get( + "AllowExternalParticipantGiveRequestControl", True + ), + allow_external_users_to_bypass_lobby=global_meeting_policy.get( + "AutoAdmittedUsers", "Everyone" + ), + allow_pstn_users_to_bypass_lobby=global_meeting_policy.get( + "AllowPSTNUsersToBypassLobby", True + ), + allow_external_non_trusted_meeting_chat=global_meeting_policy.get( + "AllowExternalNonTrustedMeetingChat", True + ), + allow_cloud_recording=global_meeting_policy.get( + "AllowCloudRecording", True + ), + designated_presenter_role_mode=global_meeting_policy.get( + "DesignatedPresenterRoleMode", "EveryoneUserOverride" + ), + meeting_chat_enabled_type=global_meeting_policy.get( + "MeetingChatEnabledType", "EnabledForEveryone" + ), ) except Exception as error: logger.error( @@ -95,6 +121,13 @@ class TeamsSettings(BaseModel): class GlobalMeetingPolicy(BaseModel): allow_anonymous_users_to_join_meeting: bool = True allow_anonymous_users_to_start_meeting: bool = True + allow_external_participant_give_request_control: bool = True + allow_external_non_trusted_meeting_chat: bool = True + allow_cloud_recording: bool = True + designated_presenter_role_mode: str = "EveryoneUserOverride" + allow_external_users_to_bypass_lobby: str = "Everyone" + allow_pstn_users_to_bypass_lobby: bool = True + meeting_chat_enabled_type: str = "EnabledForEveryone" class UserSettings(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index b0f76fa808..ce1f648702 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,6 +89,7 @@ coverage = "7.6.12" docker = "7.1.0" flake8 = "7.1.2" freezegun = "1.5.1" +marshmallow = ">=3.15.0,<4.0.0" mock = "5.2.0" moto = {extras = ["all"], version = "5.0.28"} openapi-schema-validator = "0.6.3" diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index f20cf6de85..3c8b71e3cd 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -13,6 +13,7 @@ from prowler.lib.check.compliance_models import ( Mitre_Requirement_Attribute_AWS, Mitre_Requirement_Attribute_Azure, Mitre_Requirement_Attribute_GCP, + Prowler_ThreatScore_Requirement_Attribute, ) CIS_1_4_AWS_NAME = "cis_1.4_aws" @@ -803,3 +804,129 @@ KISA_ISMSP_AWS = Compliance( ), ], ) + +PROWLER_THREATSCORE_AWS_NAME = "prowler_threatscore_aws" +PROWLER_THREATSCORE_AWS = Compliance( + Framework="ProwlerThreatScore", + Version="1.0", + Provider="AWS", + Description="Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + Requirements=[ + Compliance_Requirement( + Id="1.1.1", + Description="Ensure MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="MFA enabled for 'root'", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", + LevelOfRisk=5, + ) + ], + Checks=[ + "iam_root_mfa_enabled", + ], + ), + Compliance_Requirement( + Id="1.1.2", + Description="Ensure hardware MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="CloudTrail logging enabled", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", + LevelOfRisk=3, + ) + ], + Checks=[], + ), + ], +) + +PROWLER_THREATSCORE_AZURE_NAME = "prowler_threatscore_azure" +PROWLER_THREATSCORE_AZURE = Compliance( + Framework="ProwlerThreatScore", + Version="1.0", + Provider="Azure", + Description="Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + Requirements=[ + Compliance_Requirement( + Id="1.1.1", + Description="Ensure MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="MFA enabled for 'root'", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", + LevelOfRisk=5, + ) + ], + Checks=[ + "iam_root_mfa_enabled", + ], + ), + Compliance_Requirement( + Id="1.1.2", + Description="Ensure hardware MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="CloudTrail logging enabled", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", + LevelOfRisk=3, + ) + ], + Checks=[], + ), + ], +) + +PROWLER_THREATSCORE_GCP_NAME = "prowler_threatscore_gcp" +PROWLER_THREATSCORE_GCP = Compliance( + Framework="ProwlerThreatScore", + Version="1.0", + Provider="GCP", + Description="Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + Requirements=[ + Compliance_Requirement( + Id="1.1.1", + Description="Ensure MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="MFA enabled for 'root'", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", + LevelOfRisk=5, + ) + ], + Checks=[ + "iam_root_mfa_enabled", + ], + ), + Compliance_Requirement( + Id="1.1.2", + Description="Ensure hardware MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="CloudTrail logging enabled", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", + LevelOfRisk=3, + ) + ], + Checks=[], + ), + ], +) diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py new file mode 100644 index 0000000000..28cf82ab44 --- /dev/null +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py @@ -0,0 +1,140 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreAWSModel, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_aws import ( + ProwlerThreatScoreAWS, +) +from tests.lib.outputs.compliance.fixtures import ( + PROWLER_THREATSCORE_AWS, + PROWLER_THREATSCORE_AWS_NAME, +) +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 + + +class TestProwlerThreatScoreAWS: + def test_output_transform(self): + findings = [ + generate_finding_output(compliance={"ProwlerThreatScore-1.0": "1.1.1"}) + ] + + output = ProwlerThreatScoreAWS( + findings, PROWLER_THREATSCORE_AWS, PROWLER_THREATSCORE_AWS_NAME + ) + output_data = output.data[0] + assert isinstance(output_data, ProwlerThreatScoreAWSModel) + assert output_data.Provider == "aws" + assert output_data.Description == PROWLER_THREATSCORE_AWS.Description + assert output_data.AccountId == AWS_ACCOUNT_NUMBER + assert output_data.Region == AWS_REGION_EU_WEST_1 + assert output_data.Requirements_Id == PROWLER_THREATSCORE_AWS.Requirements[0].Id + assert ( + output_data.Requirements_Description + == PROWLER_THREATSCORE_AWS.Requirements[0].Description + ) + assert ( + output_data.Requirements_Attributes_Title + == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].Title + ) + assert ( + output_data.Requirements_Attributes_Section + == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].Section + ) + assert ( + output_data.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].SubSection + ) + assert ( + output_data.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_AWS.Requirements[0] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_AWS.Requirements[0] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].LevelOfRisk + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + # Test manual check + output_data_manual = output.data[1] + assert output_data_manual.Provider == "aws" + assert output_data_manual.AccountId == "" + assert output_data_manual.Region == "" + assert ( + output_data_manual.Requirements_Id + == PROWLER_THREATSCORE_AWS.Requirements[1].Id + ) + assert ( + output_data_manual.Requirements_Description + == PROWLER_THREATSCORE_AWS.Requirements[1].Description + ) + assert ( + output_data_manual.Requirements_Attributes_Title + == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].Title + ) + assert ( + output_data_manual.Requirements_Attributes_Section + == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].Section + ) + assert ( + output_data_manual.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].SubSection + ) + assert ( + output_data_manual.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_AWS.Requirements[1] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data_manual.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_AWS.Requirements[1] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data_manual.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].LevelOfRisk + ) + assert output_data_manual.Status == "MANUAL" + assert output_data_manual.StatusExtended == "Manual check" + assert output_data_manual.ResourceId == "manual_check" + assert output_data_manual.ResourceName == "Manual check" + assert output_data_manual.CheckId == "manual" + assert not output_data_manual.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"ProwlerThreatScore-1.0": "1.1.1"}) + ] + output = ProwlerThreatScoreAWS( + findings, PROWLER_THREATSCORE_AWS, PROWLER_THREATSCORE_AWS_NAME + ) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + assert content == expected_csv diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py new file mode 100644 index 0000000000..0dec129f06 --- /dev/null +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py @@ -0,0 +1,150 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreAzureModel, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_azure import ( + ProwlerThreatScoreAzure, +) +from tests.lib.outputs.compliance.fixtures import ( + PROWLER_THREATSCORE_AZURE, + PROWLER_THREATSCORE_AZURE_NAME, +) +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + AZURE_SUBSCRIPTION_NAME, +) + + +class TestProwlerThreatScoreAzure: + def test_output_transform(self): + findings = [ + generate_finding_output( + compliance={"ProwlerThreatScore-1.0": "1.1.1"}, + provider="azure", + account_name=AZURE_SUBSCRIPTION_NAME, + account_uid=AZURE_SUBSCRIPTION_ID, + region="", + ) + ] + + output = ProwlerThreatScoreAzure( + findings, PROWLER_THREATSCORE_AZURE, PROWLER_THREATSCORE_AZURE_NAME + ) + output_data = output.data[0] + assert isinstance(output_data, ProwlerThreatScoreAzureModel) + assert output_data.Provider == "azure" + assert output_data.Description == PROWLER_THREATSCORE_AZURE.Description + assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID + assert output_data.Location == "" + assert ( + output_data.Requirements_Id == PROWLER_THREATSCORE_AZURE.Requirements[0].Id + ) + assert ( + output_data.Requirements_Description + == PROWLER_THREATSCORE_AZURE.Requirements[0].Description + ) + assert ( + output_data.Requirements_Attributes_Title + == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].Title + ) + assert ( + output_data.Requirements_Attributes_Section + == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].Section + ) + assert ( + output_data.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].SubSection + ) + assert ( + output_data.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_AZURE.Requirements[0] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_AZURE.Requirements[0] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].LevelOfRisk + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + # Test manual check + output_data_manual = output.data[1] + assert output_data_manual.Provider == "azure" + assert output_data_manual.SubscriptionId == "" + assert output_data_manual.Location == "" + assert ( + output_data_manual.Requirements_Id + == PROWLER_THREATSCORE_AZURE.Requirements[1].Id + ) + assert ( + output_data_manual.Requirements_Description + == PROWLER_THREATSCORE_AZURE.Requirements[1].Description + ) + assert ( + output_data_manual.Requirements_Attributes_Title + == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].Title + ) + assert ( + output_data_manual.Requirements_Attributes_Section + == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].Section + ) + assert ( + output_data_manual.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].SubSection + ) + assert ( + output_data_manual.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_AZURE.Requirements[1] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data_manual.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_AZURE.Requirements[1] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data_manual.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].LevelOfRisk + ) + assert output_data_manual.Status == "MANUAL" + assert output_data_manual.StatusExtended == "Manual check" + assert output_data_manual.ResourceId == "manual_check" + assert output_data_manual.ResourceName == "Manual check" + assert output_data_manual.CheckId == "manual" + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"ProwlerThreatScore-1.0": "1.1.1"}) + ] + output = ProwlerThreatScoreAzure( + findings, PROWLER_THREATSCORE_AZURE, PROWLER_THREATSCORE_AZURE_NAME + ) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\nazure;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + assert content == expected_csv diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py new file mode 100644 index 0000000000..40a5e60494 --- /dev/null +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py @@ -0,0 +1,147 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreGCPModel, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_gcp import ( + ProwlerThreatScoreGCP, +) +from tests.lib.outputs.compliance.fixtures import ( + PROWLER_THREATSCORE_GCP, + PROWLER_THREATSCORE_GCP_NAME, +) +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID + + +class TestProwlerThreatScoreGCP: + def test_output_transform(self): + findings = [ + generate_finding_output( + compliance={"ProwlerThreatScore-1.0": "1.1.1"}, + provider="gcp", + account_name=GCP_PROJECT_ID, + account_uid=GCP_PROJECT_ID, + region="", + ) + ] + + output = ProwlerThreatScoreGCP( + findings, PROWLER_THREATSCORE_GCP, PROWLER_THREATSCORE_GCP_NAME + ) + output_data = output.data[0] + assert isinstance(output_data, ProwlerThreatScoreGCPModel) + assert output_data.Provider == "gcp" + assert output_data.Description == PROWLER_THREATSCORE_GCP.Description + assert output_data.ProjectId == GCP_PROJECT_ID + assert output_data.Location == "" + assert output_data.Requirements_Id == PROWLER_THREATSCORE_GCP.Requirements[0].Id + assert ( + output_data.Requirements_Description + == PROWLER_THREATSCORE_GCP.Requirements[0].Description + ) + assert ( + output_data.Requirements_Attributes_Title + == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].Title + ) + assert ( + output_data.Requirements_Attributes_Section + == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].Section + ) + assert ( + output_data.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].SubSection + ) + assert ( + output_data.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_GCP.Requirements[0] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_GCP.Requirements[0] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].LevelOfRisk + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + # Test manual check + output_data_manual = output.data[1] + assert output_data_manual.Provider == "gcp" + assert output_data_manual.ProjectId == "" + assert output_data_manual.Location == "" + assert ( + output_data_manual.Requirements_Id + == PROWLER_THREATSCORE_GCP.Requirements[1].Id + ) + assert ( + output_data_manual.Requirements_Description + == PROWLER_THREATSCORE_GCP.Requirements[1].Description + ) + assert ( + output_data_manual.Requirements_Attributes_Title + == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].Title + ) + assert ( + output_data_manual.Requirements_Attributes_Section + == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].Section + ) + assert ( + output_data_manual.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].SubSection + ) + assert ( + output_data_manual.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_GCP.Requirements[1] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data_manual.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_GCP.Requirements[1] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data_manual.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].LevelOfRisk + ) + assert output_data_manual.Status == "MANUAL" + assert output_data_manual.StatusExtended == "Manual check" + assert output_data_manual.ResourceId == "manual_check" + assert output_data_manual.ResourceName == "Manual check" + assert output_data_manual.CheckId == "manual" + assert not output_data_manual.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"ProwlerThreatScore-1.0": "1.1.1"}) + ] + output = ProwlerThreatScoreGCP( + findings, PROWLER_THREATSCORE_GCP, PROWLER_THREATSCORE_GCP_NAME + ) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\ngcp;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + + assert content == expected_csv diff --git a/tests/lib/outputs/html/html_test.py b/tests/lib/outputs/html/html_test.py index c9fe3ddba9..2bc6d9ab07 100644 --- a/tests/lib/outputs/html/html_test.py +++ b/tests/lib/outputs/html/html_test.py @@ -233,8 +233,7 @@ def get_aws_html_header(args: list) -> str: Returns: str: HTML header for AWS """ - aws_html_header = f""" - + aws_html_header = f""" diff --git a/tests/lib/powershell/powershell_test.py b/tests/lib/powershell/powershell_test.py index 6dab9349a0..49108e1cd2 100644 --- a/tests/lib/powershell/powershell_test.py +++ b/tests/lib/powershell/powershell_test.py @@ -58,34 +58,109 @@ class TestPowerShellSession: @patch("subprocess.Popen") def test_execute(self, mock_popen): + """Test the execute method with various scenarios: + - Normal command execution + - JSON parsing enabled + - Timeout handling + - Error handling + """ + # Setup mock_process = MagicMock() mock_popen.return_value = mock_process session = PowerShellSession() - command = "Get-Command" - expected_output = '{"Name": "Get-Command"}' - with patch.object(session, "read_output", return_value=expected_output): - result = session.execute(command) - - mock_process.stdin.write.assert_any_call(f"{command}\n") + # Test 1: Normal command execution + mock_process.stdout.readline.side_effect = ["Hello World\n", f"{session.END}\n"] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + result = session.execute("Get-Command") + assert result == "Hello World" + mock_process.stdin.write.assert_any_call("Get-Command\n") mock_process.stdin.write.assert_any_call(f"Write-Output '{session.END}'\n") - assert result == {"Name": "Get-Command"} + mock_process.stdin.write.assert_any_call(f"Write-Error '{session.END}'\n") + + # Test 2: JSON parsing enabled + mock_process.stdout.readline.side_effect = [ + '{"key": "value"}\n', + f"{session.END}\n", + ] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + with patch.object( + session, "json_parse_output", return_value={"key": "value"} + ) as mock_json_parse: + result = session.execute("Get-Command", json_parse=True) + assert result == {"key": "value"} + mock_json_parse.assert_called_once_with('{"key": "value"}') + + # Test 3: Timeout handling + mock_process.stdout.readline.side_effect = ["test output\n"] # No END marker + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.execute("Get-Command", timeout=0.1) + assert result == "" + + # Test 4: Error handling + mock_process.stdout.readline.side_effect = ["\n", f"{session.END}\n"] + mock_process.stderr.readline.side_effect = [ + "Write-Error: This is an error\n", + f"Write-Error: {session.END}\n", + ] + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.execute("Get-Command") + assert result == "" + mock_error.assert_called_once_with( + "PowerShell error output: Write-Error: This is an error" + ) + session.close() @patch("subprocess.Popen") def test_read_output(self, mock_popen): + """Test the read_output method with various scenarios: + - Normal stdout output + - Error in stderr + - Timeout in stdout + - Empty output + """ + # Setup mock_process = MagicMock() mock_popen.return_value = mock_process session = PowerShellSession() - # Test normal output - with patch.object(session, "read_output", return_value="test@example.com"): - assert session.read_output() == "test@example.com" + # Test 1: Normal stdout output + mock_process.stdout.readline.side_effect = ["Hello World\n", f"{session.END}\n"] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + result = session.read_output() + assert result == "Hello World" + + # Test 2: Error in stderr + mock_process.stdout.readline.side_effect = ["\n", f"{session.END}\n"] + mock_process.stderr.readline.side_effect = [ + "Write-Error: This is an error\n", + f"Write-Error: {session.END}\n", + ] + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.read_output() + assert result == "" + mock_error.assert_called_once_with( + "PowerShell error output: Write-Error: This is an error" + ) + + # Test 3: Timeout in stdout + mock_process.stdout.readline.side_effect = ["test output\n"] # No END marker + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.read_output(timeout=0.1, default="timeout") + assert result == "timeout" + + # Test 4: Empty output + mock_process.stdout.readline.side_effect = [f"{session.END}\n"] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.read_output() + assert result == "" - # Test timeout - mock_process.stdout.readline.return_value = "test output\n" - with patch.object(session, "remove_ansi", return_value="test output"): - assert session.read_output(timeout=0.1, default="") == "" session.close() @patch("subprocess.Popen") @@ -112,6 +187,53 @@ class TestPowerShellSession: assert result == expected session.close() + @patch("subprocess.Popen") + def test_json_parse_output_logging(self, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + session = PowerShellSession() + + # Test warning for non-JSON output + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.json_parse_output("some text without json") + assert result == {} + mock_error.assert_called_once_with( + "Unexpected PowerShell output: some text without json\n" + ) + + session.close() + + @patch("subprocess.Popen") + def test_json_parse_output_with_text_around_json(self, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + session = PowerShellSession() + + # Test JSON extraction from text with surrounding content + result = session.json_parse_output('some text {"key": "value"} more text') + assert result == {"key": "value"} + + result = session.json_parse_output('prefix [{"key": "value"}] suffix') + assert result == [{"key": "value"}] + + # Test non-JSON text returns empty dict + result = session.json_parse_output("just some text") + assert result == {} + + session.close() + + @patch("subprocess.Popen") + def test_json_parse_output_empty(self, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + session = PowerShellSession() + + # Test empty string + result = session.json_parse_output("") + assert result == {} + + session.close() + @patch("subprocess.Popen") def test_close(self, mock_popen): mock_process = MagicMock() diff --git a/tests/providers/aws/lib/s3/s3_test.py b/tests/providers/aws/lib/s3/s3_test.py index 2cede7364d..34c906d69d 100644 --- a/tests/providers/aws/lib/s3/s3_test.py +++ b/tests/providers/aws/lib/s3/s3_test.py @@ -93,7 +93,7 @@ class TestS3: Bucket=S3_BUCKET_NAME, Key=uploaded_object_name, )["ContentType"] - == "binary/octet-stream" + == "text/csv" ) @mock_aws @@ -132,7 +132,7 @@ class TestS3: Bucket=S3_BUCKET_NAME, Key=uploaded_object_name, )["ContentType"] - == "binary/octet-stream" + == "text/csv" ) remove(f"{CURRENT_DIRECTORY}/{csv_file}") @@ -171,7 +171,7 @@ class TestS3: Bucket=S3_BUCKET_NAME, Key=uploaded_object_name, )["ContentType"] - == "binary/octet-stream" + == "application/json" ) @mock_aws @@ -209,7 +209,7 @@ class TestS3: Bucket=S3_BUCKET_NAME, Key=uploaded_object_name, )["ContentType"] - == "binary/octet-stream" + == "text/html" ) @mock_aws @@ -290,7 +290,7 @@ class TestS3: Bucket=S3_BUCKET_NAME, Key=uploaded_object_name, )["ContentType"] - == "binary/octet-stream" + == "text/csv" ) def test_get_get_object_path_with_prowler(self): diff --git a/tests/providers/m365/lib/powershell/m365_powershell_test.py b/tests/providers/m365/lib/powershell/m365_powershell_test.py index f32cc1c780..66094aa581 100644 --- a/tests/providers/m365/lib/powershell/m365_powershell_test.py +++ b/tests/providers/m365/lib/powershell/m365_powershell_test.py @@ -1,5 +1,11 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, call, patch +import pytest + +from prowler.lib.powershell.powershell import PowerShellSession +from prowler.providers.m365.exceptions.exceptions import ( + M365UserNotBelongingToTenantError, +) from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell from prowler.providers.m365.models import M365Credentials @@ -84,30 +90,87 @@ class Testm365PowerShell: } credentials = M365Credentials( - user="test@example.com", + user="test@contoso.onmicrosoft.com", passwd="test_password", client_id="test_client_id", client_secret="test_client_secret", tenant_id="test_tenant_id", + provider_id="contoso.onmicrosoft.com", ) session = M365PowerShell(credentials) - session.execute = MagicMock() - session.process.stdin.write = MagicMock() + # Mock read_output to return the decrypted password session.read_output = MagicMock(return_value="decrypted_password") - assert session.test_credentials(credentials) is True + # Mock execute to return the result of read_output + session.execute = MagicMock(side_effect=lambda _: session.read_output()) + # Execute the test + result = session.test_credentials(credentials) + assert result is True + + # Verify execute was called with the correct commands session.execute.assert_any_call( - f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString\n' + f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString' ) session.execute.assert_any_call( f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n' ) - session.process.stdin.write.assert_any_call( - 'Write-Output "$($credential.GetNetworkCredential().Password)"\n' + session.execute.assert_any_call( + 'Write-Output "$($credential.GetNetworkCredential().Password)"' + ) + + # Verify MSAL was called with the correct parameters + mock_msal.assert_called_once_with( + client_id="test_client_id", + client_credential="test_client_secret", + authority="https://login.microsoftonline.com/test_tenant_id", + ) + mock_msal_instance.acquire_token_by_username_password.assert_called_once_with( + username="test@contoso.onmicrosoft.com", + password="decrypted_password", + scopes=["https://graph.microsoft.com/.default"], + ) + session.close() + + @patch("subprocess.Popen") + @patch("msal.ConfidentialClientApplication") + def test_test_credentials_user_not_belonging_to_tenant(self, mock_msal, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + mock_msal_instance = MagicMock() + mock_msal.return_value = mock_msal_instance + mock_msal_instance.acquire_token_by_username_password.return_value = { + "access_token": "test_token" + } + + credentials = M365Credentials( + user="user@otherdomain.com", + passwd="test_password", + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id", + provider_id="contoso.onmicrosoft.com", + ) + session = M365PowerShell(credentials) + + # Mock the execute method to return the decrypted password + def mock_execute(command, *args, **kwargs): + if "Write-Output" in command: + return "decrypted_password" + return None + + session.execute = MagicMock(side_effect=mock_execute) + session.process.stdin.write = MagicMock() + session.read_output = MagicMock(return_value="decrypted_password") + + with pytest.raises(M365UserNotBelongingToTenantError) as exception: + session.test_credentials(credentials) + + assert exception.type == M365UserNotBelongingToTenantError + assert "The provided M365 User does not belong to the specified tenant." in str( + exception.value ) - session.process.stdin.write.assert_any_call(f"Write-Output '{session.END}'\n") mock_msal.assert_called_once_with( client_id="test_client_id", @@ -115,12 +178,56 @@ class Testm365PowerShell: authority="https://login.microsoftonline.com/test_tenant_id", ) mock_msal_instance.acquire_token_by_username_password.assert_called_once_with( - username="test@example.com", + username="user@otherdomain.com", password="decrypted_password", scopes=["https://graph.microsoft.com/.default"], ) session.close() + @patch("subprocess.Popen") + @patch("msal.ConfidentialClientApplication") + def test_test_credentials_auth_failure(self, mock_msal, mock_popen): + mock_process = MagicMock() + mock_popen.return_value = mock_process + mock_msal_instance = MagicMock() + mock_msal.return_value = mock_msal_instance + mock_msal_instance.acquire_token_by_username_password.return_value = None + + credentials = M365Credentials( + user="test@contoso.onmicrosoft.com", + passwd="test_password", + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id", + provider_id="contoso.onmicrosoft.com", + ) + session = M365PowerShell(credentials) + + # Mock the execute method to return the decrypted password + def mock_execute(command, *args, **kwargs): + if "Write-Output" in command: + return "decrypted_password" + return None + + session.execute = MagicMock(side_effect=mock_execute) + session.process.stdin.write = MagicMock() + session.read_output = MagicMock(return_value="decrypted_password") + + assert session.test_credentials(credentials) is False + + mock_msal.assert_called_once_with( + client_id="test_client_id", + client_credential="test_client_secret", + authority="https://login.microsoftonline.com/test_tenant_id", + ) + mock_msal_instance.acquire_token_by_username_password.assert_called_once_with( + username="test@contoso.onmicrosoft.com", + password="decrypted_password", + scopes=["https://graph.microsoft.com/.default"], + ) + + session.close() + @patch("subprocess.Popen") def test_remove_ansi(self, mock_popen): credentials = M365Credentials(user="test@example.com", passwd="test_password") @@ -145,31 +252,147 @@ class Testm365PowerShell: credentials = M365Credentials(user="test@example.com", passwd="test_password") session = M365PowerShell(credentials) command = "Get-Command" - expected_output = '{"Name": "Get-Command"}' + expected_output = {"Name": "Get-Command"} - with patch.object(session, "read_output", return_value=expected_output): + with patch.object(session, "execute", return_value=expected_output): result = session.execute(command) - - mock_process.stdin.write.assert_any_call(f"{command}\n") - mock_process.stdin.write.assert_any_call(f"Write-Output '{session.END}'\n") - assert result == {"Name": "Get-Command"} + assert result == expected_output session.close() @patch("subprocess.Popen") def test_read_output(self, mock_popen): + """Test the read_output method with various scenarios: + - Normal stdout output + - Error in stderr + - Timeout in stdout + - Empty output + - Empty queue handling + """ + # Setup mock_process = MagicMock() mock_popen.return_value = mock_process credentials = M365Credentials(user="test@example.com", passwd="test_password") session = M365PowerShell(credentials) - # Test normal output - with patch.object(session, "read_output", return_value="test@example.com"): - assert session.read_output() == "test@example.com" + # Test 1: Normal stdout output + mock_process.stdout.readline.side_effect = [ + "test@example.com\n", + f"{session.END}\n", + ] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + result = session.read_output() + assert result == "test@example.com" + + # Test 2: Error in stderr + mock_process.stdout.readline.side_effect = ["\n", f"{session.END}\n"] + mock_process.stderr.readline.side_effect = [ + "Write-Error: Authentication failed\n", + f"Write-Error: {session.END}\n", + ] + with patch.object(session, "remove_ansi", side_effect=lambda x: x): + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.read_output() + assert result == "" + mock_error.assert_called_once_with( + "PowerShell error output: Write-Error: Authentication failed" + ) + + # Test 3: Timeout in stdout + mock_process.stdout.readline.side_effect = ["test output\n"] # No END marker + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.read_output(timeout=0.1, default="timeout") + assert result == "timeout" + + # Test 4: Empty output + mock_process.stdout.readline.side_effect = [f"{session.END}\n"] + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.read_output() + assert result == "" + + # Test 5: Empty queue handling + mock_process.stdout.readline.side_effect = [] # No output at all + mock_process.stderr.readline.return_value = f"Write-Error: {session.END}\n" + result = session.read_output(timeout=0.1, default="empty_queue") + assert result == "empty_queue" + + # Test 6: Empty error queue handling + mock_process.stdout.readline.side_effect = ["test output\n", f"{session.END}\n"] + mock_process.stderr.readline.side_effect = [] # No error output + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.read_output() + assert result == "test output" + mock_error.assert_not_called() + + # Test 7: Both queues empty + mock_process.stdout.readline.side_effect = [] # No output + mock_process.stderr.readline.side_effect = [] # No error output + result = session.read_output(timeout=0.1, default="both_empty") + assert result == "both_empty" + + session.close() + + @patch("subprocess.Popen") + def test_read_output_queue_empty(self, mock_popen): + """Test read_output when both queues are empty""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + session = M365PowerShell(credentials) + + # Mock process to return empty queues + mock_process.stdout.readline.side_effect = [] # No output + mock_process.stderr.readline.side_effect = [] # No error output + + # Test with default value + result = session.read_output(timeout=0.1, default="empty_queue") + assert result == "empty_queue" + + # Test without default value + result = session.read_output(timeout=0.1) + assert result == "" + + session.close() + + @patch("subprocess.Popen") + def test_read_output_error_queue_empty(self, mock_popen): + """Test read_output when error queue is empty but stdout has content""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + session = M365PowerShell(credentials) + + # Mock process to return content in stdout but empty stderr + mock_process.stdout.readline.side_effect = ["test output\n", f"{session.END}\n"] + mock_process.stderr.readline.side_effect = [] # No error output + + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.read_output() + assert result == "test output" + mock_error.assert_not_called() + + session.close() + + @patch("subprocess.Popen") + def test_read_output_result_queue_empty(self, mock_popen): + """Test read_output when result queue is empty but stderr has content""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + credentials = M365Credentials(user="test@example.com", passwd="test_password") + session = M365PowerShell(credentials) + + # Mock process to return empty stdout but content in stderr + mock_process.stdout.readline.side_effect = [] # No output + mock_process.stderr.readline.side_effect = [ + "Error message\n", + f"Write-Error: {session.END}\n", + ] + + with patch("prowler.lib.logger.logger.error") as mock_error: + result = session.read_output(timeout=0.1, default="default") + assert result == "default" + mock_error.assert_called_once_with("PowerShell error output: Error message") - # Test timeout - mock_process.stdout.readline.return_value = "test output\n" - with patch.object(session, "remove_ansi", return_value="test output"): - assert session.read_output(timeout=0.1, default="") == "" session.close() @patch("subprocess.Popen") @@ -208,3 +431,149 @@ class Testm365PowerShell: mock_process.stdin.flush.assert_called_once() mock_process.terminate.assert_called_once() + + @patch("subprocess.Popen") + def test_initialize_m365_powershell_modules_success(self, mock_popen): + """Test initialize_m365_powershell_modules when all modules are successfully initialized""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + + # Mock the execute method to simulate successful module installation + def mock_execute(command, *args, **kwargs): + if "Get-Module" in command: + return None # Module not installed + elif "Install-Module" in command: + return None # Installation successful + elif "Import-Module" in command: + return None # Import successful + return None + + with ( + patch.object( + PowerShellSession, "execute", side_effect=mock_execute + ) as mock_execute_obj, + patch("prowler.lib.logger.logger.info") as mock_info, + ): + from prowler.providers.m365.lib.powershell.m365_powershell import ( + initialize_m365_powershell_modules, + ) + + result = initialize_m365_powershell_modules() + + # 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 + # Verify success messages were logged + mock_info.assert_any_call( + "Successfully installed module ExchangeOnlineManagement" + ) + mock_info.assert_any_call("Successfully installed module MicrosoftTeams") + + @patch("subprocess.Popen") + def test_initialize_m365_powershell_modules_failure(self, mock_popen): + """Test initialize_m365_powershell_modules when module initialization fails""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + + # Mock the execute method to simulate installation failure + def mock_execute(command, *args, **kwargs): + if "Get-Module" in command: + return None # Module not installed + elif "Install-Module" in command: + raise Exception("Installation failed") + return None + + with ( + patch.object( + PowerShellSession, "execute", side_effect=mock_execute + ) as mock_execute_obj, + patch("prowler.lib.logger.logger.error") as mock_error, + ): + from prowler.providers.m365.lib.powershell.m365_powershell import ( + initialize_m365_powershell_modules, + ) + + result = initialize_m365_powershell_modules() + + # Verify failed initialization + assert result is False + # Verify that execute was called at least twice + assert mock_execute_obj.call_count >= 2 + # Verify error was logged + mock_error.assert_called_with( + "Failed to initialize module ExchangeOnlineManagement: Installation failed" + ) + + @patch("subprocess.Popen") + def test_main_success(self, mock_popen): + """Test main() function when module initialization is successful""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + + # Mock the execute method to simulate successful module installation + def mock_execute(command, *args, **kwargs): + if "Get-Module" in command: + return None # Module not installed + elif "Install-Module" in command: + return None # Installation successful + elif "Import-Module" in command: + return None # Import successful + return None + + with ( + patch.object(PowerShellSession, "execute", side_effect=mock_execute), + patch("prowler.lib.logger.logger.info") as mock_info, + patch("prowler.lib.logger.logger.error") as mock_error, + ): + from prowler.providers.m365.lib.powershell.m365_powershell import main + + main() + + # Verify all info messages were logged in the correct order + assert mock_info.call_count == 3 + mock_info.assert_has_calls( + [ + call("Successfully installed module ExchangeOnlineManagement"), + call("Successfully installed module MicrosoftTeams"), + call("M365 PowerShell modules initialized successfully"), + ] + ) + # Verify no error was logged + mock_error.assert_not_called() + + @patch("subprocess.Popen") + def test_main_failure(self, mock_popen): + """Test main() function when module initialization fails""" + mock_process = MagicMock() + mock_popen.return_value = mock_process + + # Mock the execute method to simulate installation failure + def mock_execute(command, *args, **kwargs): + if "Get-Module" in command: + return None # Module not installed + elif "Install-Module" in command: + raise Exception("Installation failed") + return None + + with ( + patch.object(PowerShellSession, "execute", side_effect=mock_execute), + patch("prowler.lib.logger.logger.info") as mock_info, + patch("prowler.lib.logger.logger.error") as mock_error, + ): + from prowler.providers.m365.lib.powershell.m365_powershell import main + + main() + + # Verify all error messages were logged in the correct order + assert mock_error.call_count == 2 + mock_error.assert_has_calls( + [ + call( + "Failed to initialize module ExchangeOnlineManagement: Installation failed" + ), + call("Failed to initialize M365 PowerShell modules"), + ] + ) + # Verify no info messages were logged + mock_info.assert_not_called() diff --git a/tests/providers/m365/m365_provider_test.py b/tests/providers/m365/m365_provider_test.py index 22f0bef4b0..c891ab2d45 100644 --- a/tests/providers/m365/m365_provider_test.py +++ b/tests/providers/m365/m365_provider_test.py @@ -1,3 +1,4 @@ +import os from unittest.mock import patch from uuid import uuid4 @@ -18,8 +19,15 @@ from prowler.config.config import ( from prowler.providers.common.models import Connection from prowler.providers.m365.exceptions.exceptions import ( M365HTTPResponseError, - M365MissingEnvironmentUserCredentialsError, + M365InvalidProviderIdError, + M365MissingEnvironmentCredentialsError, M365NoAuthenticationMethodError, + M365NotValidClientIdError, + M365NotValidClientSecretError, + M365NotValidEncryptedPasswordError, + M365NotValidTenantIdError, + M365NotValidUserError, + M365UserNotBelongingToTenantError, ) from prowler.providers.m365.m365_provider import M365Provider from prowler.providers.m365.models import ( @@ -333,37 +341,59 @@ class TestM365Provider: assert test_connection.is_connected assert test_connection.error is None - def test_test_connection_tenant_id_client_id_client_secret_user_encrypted_password( + def test_test_connection_tenant_id_client_id_client_secret_no_user_encrypted_password( self, ): - with ( - patch( - "prowler.providers.m365.m365_provider.M365Provider.setup_session" - ) as mock_setup_session, - patch( - "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" - ) as mock_validate_static_credentials, - ): - # Mock setup_session to return a mocked session object - mock_session = MagicMock() - mock_setup_session.return_value = mock_session - - # Mock ValidateStaticCredentials to avoid real API calls - mock_validate_static_credentials.return_value = None - - test_connection = M365Provider.test_connection( - tenant_id=str(uuid4()), - region="M365Global", - raise_on_exception=False, - client_id=str(uuid4()), - client_secret=str(uuid4()), - user="user@user.com", - encrypted_password="AAAA1111", + with patch( + "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" + ) as mock_validate_static_credentials: + mock_validate_static_credentials.side_effect = M365NotValidUserError( + file=os.path.basename(__file__), + message="The provided M365 User is not valid.", ) - assert isinstance(test_connection, Connection) - assert test_connection.is_connected - assert test_connection.error is None + with pytest.raises(M365NotValidUserError) as exception: + M365Provider.test_connection( + tenant_id=str(uuid4()), + region="M365Global", + raise_on_exception=True, + client_id=str(uuid4()), + client_secret=str(uuid4()), + user=None, + encrypted_password="test_password", + ) + + assert exception.type == M365NotValidUserError + assert "The provided M365 User is not valid." in str(exception.value) + + def test_test_connection_tenant_id_client_id_client_secret_user_no_encrypted_password( + self, + ): + with patch( + "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" + ) as mock_validate_static_credentials: + mock_validate_static_credentials.side_effect = ( + M365NotValidEncryptedPasswordError( + file=os.path.basename(__file__), + message="The provided M365 Encrypted Password is not valid.", + ) + ) + + with pytest.raises(M365NotValidEncryptedPasswordError) as exception: + M365Provider.test_connection( + tenant_id=str(uuid4()), + region="M365Global", + raise_on_exception=True, + client_id=str(uuid4()), + client_secret=str(uuid4()), + user="test@example.com", + encrypted_password=None, + ) + + assert exception.type == M365NotValidEncryptedPasswordError + assert "The provided M365 Encrypted Password is not valid." in str( + exception.value + ) def test_test_connection_with_httpresponseerror(self): with patch( @@ -419,12 +449,16 @@ class TestM365Provider: "client_secret": "test_client_secret", } - with patch( - "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", - return_value=True, + with ( + patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", + return_value=True, + ), ): result = M365Provider.setup_powershell( - env_auth=False, m365_credentials=credentials_dict + env_auth=False, + m365_credentials=credentials_dict, + provider_id="test_provider_id", ) assert result.user == credentials_dict["user"] @@ -440,7 +474,7 @@ class TestM365Provider: mock_session.test_credentials.return_value = False mock_powershell.return_value = mock_session - with pytest.raises(M365MissingEnvironmentUserCredentialsError) as exc_info: + with pytest.raises(M365MissingEnvironmentCredentialsError) as exc_info: M365Provider.setup_powershell( env_auth=True, m365_credentials=credentials ) @@ -450,3 +484,227 @@ class TestM365Provider: in str(exc_info.value) ) mock_session.test_credentials.assert_not_called() + + def test_test_connection_user_not_belonging_to_tenant( + self, + ): + with patch( + "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" + ) as mock_validate_static_credentials: + mock_validate_static_credentials.side_effect = M365UserNotBelongingToTenantError( + file=os.path.basename(__file__), + message="The provided M365 User does not belong to the specified tenant.", + ) + + with pytest.raises(M365UserNotBelongingToTenantError) as exception: + M365Provider.test_connection( + tenant_id="contoso.onmicrosoft.com", + region="M365Global", + raise_on_exception=True, + client_id=str(uuid4()), + client_secret=str(uuid4()), + user="user@otherdomain.com", + encrypted_password="test_password", + ) + + assert exception.type == M365UserNotBelongingToTenantError + assert ( + "The provided M365 User does not belong to the specified tenant." + in str(exception.value) + ) + + def test_validate_static_credentials_invalid_tenant_id(self): + with pytest.raises(M365NotValidTenantIdError) as exception: + M365Provider.validate_static_credentials( + tenant_id="invalid-tenant-id", + client_id="12345678-1234-5678-1234-567812345678", + client_secret="test_secret", + user="test@example.com", + encrypted_password="test_password", + ) + assert "The provided M365 Tenant ID is not valid." in str(exception.value) + + def test_validate_static_credentials_missing_client_id(self): + with pytest.raises(M365NotValidClientIdError) as exception: + M365Provider.validate_static_credentials( + tenant_id="12345678-1234-5678-1234-567812345678", + client_id="", + client_secret="test_secret", + user="test@example.com", + encrypted_password="test_password", + ) + assert "The provided M365 Client ID is not valid." in str(exception.value) + + def test_validate_static_credentials_missing_client_secret(self): + with pytest.raises(M365NotValidClientSecretError) as exception: + M365Provider.validate_static_credentials( + tenant_id="12345678-1234-5678-1234-567812345678", + client_id="12345678-1234-5678-1234-567812345678", + client_secret="", + user="test@example.com", + encrypted_password="test_password", + ) + assert "The provided M365 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="", + encrypted_password="test_password", + ) + assert "The provided M365 User is not valid." in str(exception.value) + + def test_validate_static_credentials_missing_encrypted_password(self): + with pytest.raises(M365NotValidEncryptedPasswordError) 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", + encrypted_password="", + ) + assert "The provided M365 Encrypted 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( + az_cli_auth=False, + sp_env_auth=False, + env_auth=True, + browser_auth=False, + tenant_id=None, + client_id="test_client_id", + client_secret="test_secret", + user=None, + encrypted_password=None, + ) + assert ( + "M365 provider requires AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, M365_USER and M365_ENCRYPTED_PASSWORD environment variables to be set when using --env-auth" + in str(exception.value) + ) + + def test_test_connection_invalid_provider_id(self): + with ( + patch( + "prowler.providers.m365.m365_provider.M365Provider.setup_session" + ) as mock_setup_session, + patch( + "prowler.providers.m365.m365_provider.M365Provider.validate_static_credentials" + ) as mock_validate_static_credentials, + ): + # Mock setup_session to return a mocked session object + mock_session = MagicMock() + mock_setup_session.return_value = mock_session + + # Mock ValidateStaticCredentials to avoid real API calls + mock_validate_static_credentials.return_value = None + + user_domain = "contoso.com" + provider_id = "Test.com" + + with pytest.raises(M365InvalidProviderIdError) as exception: + M365Provider.test_connection( + tenant_id=str(uuid4()), + region="M365Global", + raise_on_exception=True, + client_id=str(uuid4()), + client_secret=str(uuid4()), + user=f"user@{user_domain}", + encrypted_password="test_password", + provider_id=provider_id, + ) + + assert exception.type == M365InvalidProviderIdError + assert ( + f"Provider ID {provider_id} does not match Application tenant domain {user_domain}" + in str(exception.value) + ) + + def test_provider_init_modules_false(self): + """Test that initialize_m365_powershell_modules is not called when init_modules is False""" + credentials_dict = { + "user": "test@example.com", + "encrypted_password": "test_password", + "client_id": "test_client_id", + "tenant_id": "test_tenant_id", + "client_secret": "test_client_secret", + } + + with ( + patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", + return_value=True, + ), + patch( + "prowler.providers.m365.m365_provider.initialize_m365_powershell_modules" + ) as mock_init_modules, + ): + M365Provider.setup_powershell( + env_auth=False, + m365_credentials=credentials_dict, + provider_id="test_provider_id", + init_modules=False, + ) + mock_init_modules.assert_not_called() + + def test_provider_init_modules_true(self): + """Test that initialize_m365_powershell_modules is called when init_modules is True""" + credentials_dict = { + "user": "test@example.com", + "encrypted_password": "test_password", + "client_id": "test_client_id", + "tenant_id": "test_tenant_id", + "client_secret": "test_client_secret", + } + + with ( + patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", + return_value=True, + ), + patch( + "prowler.providers.m365.m365_provider.initialize_m365_powershell_modules" + ) as mock_init_modules, + ): + M365Provider.setup_powershell( + env_auth=False, + m365_credentials=credentials_dict, + provider_id="test_provider_id", + init_modules=True, + ) + mock_init_modules.assert_called_once() + + def test_setup_powershell_init_modules_failure(self): + """Test that setup_powershell handles initialization failures correctly""" + credentials_dict = { + "user": "test@example.com", + "encrypted_password": "test_password", + "client_id": "test_client_id", + "tenant_id": "test_tenant_id", + "client_secret": "test_client_secret", + } + + with ( + patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.test_credentials", + return_value=True, + ), + patch( + "prowler.providers.m365.m365_provider.initialize_m365_powershell_modules", + side_effect=Exception("Module initialization failed"), + ), + ): + with pytest.raises(Exception) as exc_info: + M365Provider.setup_powershell( + env_auth=False, + m365_credentials=credentials_dict, + provider_id="test_provider_id", + init_modules=True, + ) + + assert str(exc_info.value) == "Module initialization failed" diff --git a/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py b/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py new file mode 100644 index 0000000000..feba0c5bb4 --- /dev/null +++ b/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py @@ -0,0 +1,126 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_defender_antispam_policy_inbound_no_allowed_domains: + def test_policy_without_allowed_domains(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains import ( + defender_antispam_policy_inbound_no_allowed_domains, + ) + from prowler.providers.m365.services.defender.defender_service import ( + DefenderInboundSpamPolicy, + ) + + defender_client.inbound_spam_policies = [ + DefenderInboundSpamPolicy( + identity="Policy1", + allowed_sender_domains=[], + ) + ] + + check = defender_antispam_policy_inbound_no_allowed_domains() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Inbound anti-spam policy Policy1 does not contain allowed domains." + ) + assert result[0].resource == defender_client.inbound_spam_policies[0].dict() + assert result[0].resource_name == "Defender Inbound Spam Policy" + assert result[0].resource_id == "Policy1" + assert result[0].location == "global" + + def test_policy_with_allowed_domains(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains import ( + defender_antispam_policy_inbound_no_allowed_domains, + ) + from prowler.providers.m365.services.defender.defender_service import ( + DefenderInboundSpamPolicy, + ) + + defender_client.inbound_spam_policies = [ + DefenderInboundSpamPolicy( + identity="Policy2", + allowed_sender_domains=["bad-domain.com"], + ) + ] + + check = defender_antispam_policy_inbound_no_allowed_domains() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Inbound anti-spam policy Policy2 contains allowed domains: ['bad-domain.com']." + ) + assert result[0].resource == defender_client.inbound_spam_policies[0].dict() + assert result[0].resource_name == "Defender Inbound Spam Policy" + assert result[0].resource_id == "Policy2" + assert result[0].location == "global" + + def test_no_inbound_spam_policies(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains import ( + defender_antispam_policy_inbound_no_allowed_domains, + ) + + defender_client.inbound_spam_policies = [] + + check = defender_antispam_policy_inbound_no_allowed_domains() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/m365/services/defender/m365_defender_service_test.py b/tests/providers/m365/services/defender/m365_defender_service_test.py index 28d9d47919..efce49f843 100644 --- a/tests/providers/m365/services/defender/m365_defender_service_test.py +++ b/tests/providers/m365/services/defender/m365_defender_service_test.py @@ -7,6 +7,7 @@ from prowler.providers.m365.services.defender.defender_service import ( AntiphishingRule, ConnectionFilterPolicy, Defender, + DefenderInboundSpamPolicy, DkimConfig, MalwarePolicy, OutboundSpamPolicy, @@ -70,6 +71,19 @@ def mock_defender_get_antiphising_rules(_): } +def mock_defender_get_inbound_spam_policy(_): + return [ + DefenderInboundSpamPolicy( + identity="Policy1", + allowed_sender_domains=[], + ), + DefenderInboundSpamPolicy( + identity="Policy2", + allowed_sender_domains=["example.com"], + ), + ] + + def mock_defender_get_connection_filter_policy(_): return ConnectionFilterPolicy( ip_allow_list=[], @@ -323,3 +337,23 @@ class Test_Defender_Service: outbound_spam_rules = defender_client.outbound_spam_rules assert outbound_spam_rules["Policy1"].state == "Enabled" assert outbound_spam_rules["Policy2"].state == "Disabled" + + @patch( + "prowler.providers.m365.services.defender.defender_service.Defender._get_inbound_spam_filter_policy", + new=mock_defender_get_inbound_spam_policy, + ) + def test__get_inbound_spam_filter_policy(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + defender_client = Defender( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + inbound_spam_policies = defender_client.inbound_spam_policies + assert inbound_spam_policies[0].allowed_sender_domains == [] + assert inbound_spam_policies[1].allowed_sender_domains == ["example.com"] + defender_client.powershell.close() diff --git a/tests/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled_test.py b/tests/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled_test.py new file mode 100644 index 0000000000..557461d935 --- /dev/null +++ b/tests/providers/m365/services/exchange/exchange_external_email_tagging_enabled/exchange_external_email_tagging_enabled_test.py @@ -0,0 +1,173 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_exchange_external_email_tagging_enabled: + def test_external_tagging_enabled(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled import ( + exchange_external_email_tagging_enabled, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + ExternalMailConfig, + ) + + exchange_client.external_mail_config = [ + ExternalMailConfig(identity="Org1", external_mail_tag_enabled=True) + ] + + check = exchange_external_email_tagging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "External sender tagging is enabled for Exchange identity Org1." + ) + assert result[0].resource == exchange_client.external_mail_config[0].dict() + assert result[0].resource_name == "Org1" + assert result[0].resource_id == "Org1" + assert result[0].location == "global" + + def test_external_tagging_disabled(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled import ( + exchange_external_email_tagging_enabled, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + ExternalMailConfig, + ) + + exchange_client.external_mail_config = [ + ExternalMailConfig(identity="Org2", external_mail_tag_enabled=False) + ] + + check = exchange_external_email_tagging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "External sender tagging is disabled for Exchange identity Org2." + ) + assert result[0].resource == exchange_client.external_mail_config[0].dict() + assert result[0].resource_name == "Org2" + assert result[0].resource_id == "Org2" + assert result[0].location == "global" + + def test_multiple_configs_mixed_status(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled import ( + exchange_external_email_tagging_enabled, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + ExternalMailConfig, + ) + + exchange_client.external_mail_config = [ + ExternalMailConfig( + identity="OrgEnabled", external_mail_tag_enabled=True + ), + ExternalMailConfig( + identity="OrgDisabled", external_mail_tag_enabled=False + ), + ] + + check = exchange_external_email_tagging_enabled() + result = check.execute() + + assert len(result) == 2 + + assert result[0].status == "PASS" + assert result[0].resource_name == "OrgEnabled" + assert ( + result[0].status_extended + == "External sender tagging is enabled for Exchange identity OrgEnabled." + ) + + assert result[1].status == "FAIL" + assert result[1].resource_name == "OrgDisabled" + assert ( + result[1].status_extended + == "External sender tagging is disabled for Exchange identity OrgDisabled." + ) + + def test_no_mail_configs(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_external_email_tagging_enabled.exchange_external_email_tagging_enabled import ( + exchange_external_email_tagging_enabled, + ) + + exchange_client.external_mail_config = [] + + check = exchange_external_email_tagging_enabled() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py b/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py new file mode 100644 index 0000000000..688d68add6 --- /dev/null +++ b/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py @@ -0,0 +1,118 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_exchange_mailbox_policy_additional_storage_restricted: + def test_mailbox_policy_restricts_additional_storage(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted import ( + exchange_mailbox_policy_additional_storage_restricted, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + MailboxPolicy, + ) + + exchange_client.mailbox_policy = MailboxPolicy( + id="OwaMailboxPolicy-Default", additional_storage_enabled=False + ) + + check = exchange_mailbox_policy_additional_storage_restricted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Exchange mailbox policy restricts additional storage providers." + ) + assert result[0].resource == exchange_client.mailbox_policy.dict() + assert result[0].resource_name == "Exchange Mailbox Policy" + assert result[0].resource_id == "OwaMailboxPolicy-Default" + assert result[0].location == "global" + + def test_mailbox_policy_allows_additional_storage(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted import ( + exchange_mailbox_policy_additional_storage_restricted, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + MailboxPolicy, + ) + + exchange_client.mailbox_policy = MailboxPolicy( + id="OwaMailboxPolicy-Default", additional_storage_enabled=True + ) + + check = exchange_mailbox_policy_additional_storage_restricted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Exchange mailbox policy allows additional storage providers." + ) + assert result[0].resource == exchange_client.mailbox_policy.dict() + assert result[0].resource_name == "Exchange Mailbox Policy" + assert result[0].resource_id == "OwaMailboxPolicy-Default" + assert result[0].location == "global" + + def test_no_mailbox_policy(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + exchange_client.mailbox_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted import ( + exchange_mailbox_policy_additional_storage_restricted, + ) + + check = exchange_mailbox_policy_additional_storage_restricted() + result = check.execute() + assert len(result) == 0 diff --git a/tests/providers/m365/services/exchange/exchange_service_test.py b/tests/providers/m365/services/exchange/exchange_service_test.py index 83e1af83a5..42cc16a507 100644 --- a/tests/providers/m365/services/exchange/exchange_service_test.py +++ b/tests/providers/m365/services/exchange/exchange_service_test.py @@ -4,8 +4,11 @@ from unittest.mock import patch from prowler.providers.m365.models import M365IdentityInfo from prowler.providers.m365.services.exchange.exchange_service import ( Exchange, + ExternalMailConfig, MailboxAuditConfig, + MailboxPolicy, Organization, + TransportRule, ) from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider @@ -21,6 +24,41 @@ def mock_exchange_get_mailbox_audit_config(_): ] +def mock_exchange_get_external_mail_config(_): + return [ + ExternalMailConfig( + identity="test", + external_mail_tag_enabled=True, + ), + ExternalMailConfig( + identity="test2", + external_mail_tag_enabled=False, + ), + ] + + +def mock_exchange_get_transport_rules(_): + return [ + TransportRule( + name="test", + scl=-1, + sender_domain_is=["example.com"], + ), + TransportRule( + name="test2", + scl=0, + sender_domain_is=["example.com"], + ), + ] + + +def mock_exchange_get_mailbox_policy(_): + return MailboxPolicy( + id="test", + additional_storage_enabled=True, + ) + + class Test_Exchange_Service: def test_get_client(self): with ( @@ -84,3 +122,72 @@ class Test_Exchange_Service: assert mailbox_audit_config[1].audit_bypass_enabled is True exchange_client.powershell.close() + + @patch( + "prowler.providers.m365.services.exchange.exchange_service.Exchange._get_external_mail_config", + new=mock_exchange_get_external_mail_config, + ) + def test_get_external_mail_config(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + exchange_client = Exchange( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + external_mail_config = exchange_client.external_mail_config + assert len(external_mail_config) == 2 + assert external_mail_config[0].identity == "test" + assert external_mail_config[0].external_mail_tag_enabled is True + assert external_mail_config[1].identity == "test2" + assert external_mail_config[1].external_mail_tag_enabled is False + exchange_client.powershell.close() + + @patch( + "prowler.providers.m365.services.exchange.exchange_service.Exchange._get_transport_rules", + new=mock_exchange_get_transport_rules, + ) + def test_get_transport_rules(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + exchange_client = Exchange( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + transport_rules = exchange_client.transport_rules + assert len(transport_rules) == 2 + assert transport_rules[0].name == "test" + assert transport_rules[0].scl == -1 + assert transport_rules[0].sender_domain_is == ["example.com"] + assert transport_rules[1].name == "test2" + assert transport_rules[1].scl == 0 + assert transport_rules[1].sender_domain_is == ["example.com"] + + exchange_client.powershell.close() + + @patch( + "prowler.providers.m365.services.exchange.exchange_service.Exchange._get_mailbox_policy", + new=mock_exchange_get_mailbox_policy, + ) + def test_get_mailbox_policy(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + exchange_client = Exchange( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + mailbox_policy = exchange_client.mailbox_policy + assert mailbox_policy.id == "test" + assert mailbox_policy.additional_storage_enabled is True + exchange_client.powershell.close() diff --git a/tests/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled_test.py b/tests/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled_test.py new file mode 100644 index 0000000000..daea4fd1de --- /dev/null +++ b/tests/providers/m365/services/exchange/exchange_transport_rules_whitelist_disabled/exchange_transport_rules_whitelist_disabled_test.py @@ -0,0 +1,135 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_exchange_transport_rules_whitelist_disabled: + def test_no_whitelist_domains(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_service import ( + TransportRule, + ) + from prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled import ( + exchange_transport_rules_whitelist_disabled, + ) + + exchange_client.transport_rules = [ + TransportRule(name="Rule1", scl=0, sender_domain_is=[]), + TransportRule(name="Rule2", scl=0, sender_domain_is=["example.com"]), + ] + + check = exchange_transport_rules_whitelist_disabled() + result = check.execute() + + assert len(result) == 2 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Transport rule Rule1 does not whitelist any domains." + ) + assert result[0].resource_name == "Rule1" + assert result[0].resource_id == "ExchangeTransportRule" + assert result[1].status == "PASS" + assert ( + result[1].status_extended + == "Transport rule Rule2 does not whitelist any domains." + ) + assert result[1].resource_name == "Rule2" + + def test_whitelist_detected(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_service import ( + TransportRule, + ) + from prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled import ( + exchange_transport_rules_whitelist_disabled, + ) + + exchange_client.transport_rules = [ + TransportRule( + name="WhitelistRule", scl=-1, sender_domain_is=["whitelist.com"] + ), + TransportRule(name="NoWhitelistRule", scl=-1, sender_domain_is=[]), + ] + + check = exchange_transport_rules_whitelist_disabled() + result = check.execute() + + assert len(result) == 2 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Transport rule WhitelistRule whitelists domains: whitelist.com." + ) + assert result[0].resource_name == "WhitelistRule" + assert result[0].resource_id == "ExchangeTransportRule" + assert result[0].location == "global" + assert result[1].status == "PASS" + assert ( + result[1].status_extended + == "Transport rule NoWhitelistRule does not whitelist any domains." + ) + assert result[1].resource_name == "NoWhitelistRule" + assert result[1].resource_id == "ExchangeTransportRule" + assert result[1].location == "global" + + def test_empty_rule_list(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_transport_rules_whitelist_disabled.exchange_transport_rules_whitelist_disabled import ( + exchange_transport_rules_whitelist_disabled, + ) + + exchange_client.transport_rules = [] + + check = exchange_transport_rules_whitelist_disabled() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py b/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py index 6111ff0993..0ba5bd69af 100644 --- a/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py +++ b/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py @@ -1,3 +1,4 @@ +import uuid from unittest import mock from prowler.providers.m365.services.sharepoint.sharepoint_service import ( @@ -35,6 +36,7 @@ class Test_sharepoint_external_sharing_managed: legacyAuth=True, resharingEnabled=False, sharingDomainRestrictionMode="none", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -80,6 +82,7 @@ class Test_sharepoint_external_sharing_managed: legacyAuth=True, resharingEnabled=False, sharingDomainRestrictionMode="allowList", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -125,6 +128,7 @@ class Test_sharepoint_external_sharing_managed: legacyAuth=True, resharingEnabled=False, sharingDomainRestrictionMode="blockList", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -170,6 +174,7 @@ class Test_sharepoint_external_sharing_managed: legacyAuth=True, resharingEnabled=False, sharingDomainRestrictionMode="allowList", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -215,6 +220,7 @@ class Test_sharepoint_external_sharing_managed: legacyAuth=True, resharingEnabled=False, sharingDomainRestrictionMode="blockList", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN diff --git a/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py b/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py index f238cbf4fa..beb1b4a52a 100644 --- a/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py +++ b/tests/providers/m365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py @@ -1,3 +1,4 @@ +import uuid from unittest import mock from prowler.providers.m365.services.sharepoint.sharepoint_service import ( @@ -35,6 +36,7 @@ class Test_sharepoint_external_sharing_restricted: sharingDomainRestrictionMode="allowList", resharingEnabled=False, legacyAuth=True, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -78,6 +80,7 @@ class Test_sharepoint_external_sharing_restricted: sharingDomainRestrictionMode="allowList", resharingEnabled=False, legacyAuth=True, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN diff --git a/tests/providers/m365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py b/tests/providers/m365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py index 1386278fa6..ba98f79477 100644 --- a/tests/providers/m365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py +++ b/tests/providers/m365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py @@ -1,3 +1,4 @@ +import uuid from unittest import mock from prowler.providers.m365.services.sharepoint.sharepoint_service import ( @@ -35,6 +36,7 @@ class Test_sharepoint_guest_sharing_restricted: sharingDomainRestrictionMode="allowList", legacyAuth=True, resharingEnabled=False, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -79,6 +81,7 @@ class Test_sharepoint_guest_sharing_restricted: sharingDomainRestrictionMode="allowList", legacyAuth=True, resharingEnabled=True, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN diff --git a/tests/providers/m365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py b/tests/providers/m365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py index bf2c23a645..43753558be 100644 --- a/tests/providers/m365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py +++ b/tests/providers/m365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py @@ -1,3 +1,4 @@ +import uuid from unittest import mock from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider @@ -35,6 +36,7 @@ class Test_sharepoint_modern_authentication_required: sharingDomainRestrictionMode="allowList", resharingEnabled=False, legacyAuth=False, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN @@ -81,6 +83,7 @@ class Test_sharepoint_modern_authentication_required: sharingDomainRestrictionMode="allowList", resharingEnabled=False, legacyAuth=True, + allowedDomainGuidsForSyncApp=[uuid.uuid4()], ) sharepoint_client.tenant_domain = DOMAIN diff --git a/tests/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices_test.py b/tests/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices_test.py new file mode 100644 index 0000000000..0788194f8a --- /dev/null +++ b/tests/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/sharepoint_onedrive_sync_restricted_unmanaged_devices_test.py @@ -0,0 +1,129 @@ +import uuid +from unittest import mock + +from prowler.providers.m365.services.sharepoint.sharepoint_service import ( + SharePointSettings, +) +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_sharepoint_onedrive_sync_restricted_unmanaged_devices: + def test_no_allowed_domain_guids(self): + """ + Test when there are no allowed domain guids for OneDrive sync app + + + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import ( + sharepoint_onedrive_sync_restricted_unmanaged_devices, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=["allowed-domain.com"], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="none", + allowedDomainGuidsForSyncApp=[], + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_onedrive_sync_restricted_unmanaged_devices() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == sharepoint_client.settings.dict() + + def test_allowed_domain_guids(self): + """ + Test when there are allowed domain guids for OneDrive sync app + """ + sharepoint_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import ( + sharepoint_onedrive_sync_restricted_unmanaged_devices, + ) + + sharepoint_client.settings = SharePointSettings( + sharingCapability="ExternalUserSharingOnly", + sharingAllowedDomainList=[], + sharingBlockedDomainList=["blocked-domain.com"], + legacyAuth=True, + resharingEnabled=False, + sharingDomainRestrictionMode="allowList", + allowedDomainGuidsForSyncApp=[uuid.uuid4()], + ) + sharepoint_client.tenant_domain = DOMAIN + + check = sharepoint_onedrive_sync_restricted_unmanaged_devices() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices." + ) + assert result[0].resource_id == DOMAIN + assert result[0].location == "global" + assert result[0].resource_name == "SharePoint Settings" + assert result[0].resource == sharepoint_client.settings.dict() + + def test_empty_settings(self): + """ + Test when sharepoint_client.settings is empty: + The check should return an empty list of findings. + """ + sharepoint_client = mock.MagicMock + sharepoint_client.settings = {} + sharepoint_client.tenant_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client", + new=sharepoint_client, + ), + ): + from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import ( + sharepoint_onedrive_sync_restricted_unmanaged_devices, + ) + + check = sharepoint_onedrive_sync_restricted_unmanaged_devices() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/m365/services/sharepoint/sharepoint_service_test.py b/tests/providers/m365/services/sharepoint/sharepoint_service_test.py index 8c3dc72598..3b67f06c28 100644 --- a/tests/providers/m365/services/sharepoint/sharepoint_service_test.py +++ b/tests/providers/m365/services/sharepoint/sharepoint_service_test.py @@ -1,3 +1,4 @@ +import uuid from unittest.mock import patch from prowler.providers.m365.models import M365IdentityInfo @@ -7,6 +8,8 @@ from prowler.providers.m365.services.sharepoint.sharepoint_service import ( ) from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider +uuid_value = uuid.uuid4() + async def mock_sharepoint_get_settings(_): return SharePointSettings( @@ -16,6 +19,7 @@ async def mock_sharepoint_get_settings(_): sharingDomainRestrictionMode="allowList", resharingEnabled=False, legacyAuth=True, + allowedDomainGuidsForSyncApp=[uuid_value], ) @@ -39,3 +43,5 @@ class Test_SharePoint_Service: assert settings.sharingDomainRestrictionMode == "allowList" assert settings.resharingEnabled is False assert settings.legacyAuth is True + assert settings.allowedDomainGuidsForSyncApp == [uuid_value] + assert len(settings.allowedDomainGuidsForSyncApp) == 1 diff --git a/tests/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled_test.py new file mode 100644 index 0000000000..cecc4ea919 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_chat_anonymous_users_disabled/teams_meeting_chat_anonymous_users_disabled_test.py @@ -0,0 +1,159 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_chat_anonymous_users_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled import ( + teams_meeting_chat_anonymous_users_disabled, + ) + + check = teams_meeting_chat_anonymous_users_disabled() + result = check.execute() + assert len(result) == 0 + + def test_meeting_chat_allows_anonymous_users(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled import ( + teams_meeting_chat_anonymous_users_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + meeting_chat_enabled_type="EnabledForEveryone" + ) + + check = teams_meeting_chat_anonymous_users_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Meeting chat allows anonymous users." + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_meeting_chat_does_not_allow_anonymous_users_enabled_except_anonymous(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled import ( + teams_meeting_chat_anonymous_users_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + meeting_chat_enabled_type="EnabledExceptAnonymous" + ) + + check = teams_meeting_chat_anonymous_users_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Meeting chat does not allow anonymous users." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_meeting_chat_does_not_allow_anonymous_users_enabled_in_meeting_only(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_chat_anonymous_users_disabled.teams_meeting_chat_anonymous_users_disabled import ( + teams_meeting_chat_anonymous_users_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + meeting_chat_enabled_type="EnabledInMeetingOnlyForAllExceptAnonymous" + ) + + check = teams_meeting_chat_anonymous_users_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Meeting chat does not allow anonymous users." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled_test.py new file mode 100644 index 0000000000..c5994b6b8e --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_dial_in_lobby_bypass_disabled/teams_meeting_dial_in_lobby_bypass_disabled_test.py @@ -0,0 +1,112 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_dial_in_lobby_bypass_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled import ( + teams_meeting_dial_in_lobby_bypass_disabled, + ) + + check = teams_meeting_dial_in_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 0 + + def test_dial_in_users_can_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled import ( + teams_meeting_dial_in_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_pstn_users_to_bypass_lobby=True + ) + + check = teams_meeting_dial_in_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "Dial-in users can bypass the lobby." + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_dial_in_users_cannot_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_dial_in_lobby_bypass_disabled.teams_meeting_dial_in_lobby_bypass_disabled import ( + teams_meeting_dial_in_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_pstn_users_to_bypass_lobby=False + ) + + check = teams_meeting_dial_in_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == "Dial-in users cannot bypass the lobby." + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled_test.py new file mode 100644 index 0000000000..69da25e0eb --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_external_chat_disabled/teams_meeting_external_chat_disabled_test.py @@ -0,0 +1,118 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_external_chat_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled import ( + teams_meeting_external_chat_disabled, + ) + + check = teams_meeting_external_chat_disabled() + result = check.execute() + assert len(result) == 0 + + def test_external_chat_enabled(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled import ( + teams_meeting_external_chat_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_non_trusted_meeting_chat=True + ) + + check = teams_meeting_external_chat_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "External meeting chat is enabled for untrusted organizations." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_external_chat_disabled(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_chat_disabled.teams_meeting_external_chat_disabled import ( + teams_meeting_external_chat_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_non_trusted_meeting_chat=False + ) + + check = teams_meeting_external_chat_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "External meeting chat is disabled for untrusted organizations." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled_test.py new file mode 100644 index 0000000000..c2a3d783f3 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_external_control_disabled/teams_meeting_external_control_disabled_test.py @@ -0,0 +1,118 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_external_control_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled import ( + teams_meeting_external_control_disabled, + ) + + check = teams_meeting_external_control_disabled() + result = check.execute() + assert len(result) == 0 + + def test_external_participants_can_control(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled import ( + teams_meeting_external_control_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_participant_give_request_control=True + ) + + check = teams_meeting_external_control_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "External participants can give or request control." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_external_participants_cannot_control(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_control_disabled.teams_meeting_external_control_disabled import ( + teams_meeting_external_control_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_participant_give_request_control=False + ) + + check = teams_meeting_external_control_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "External participants cannot give or request control." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py new file mode 100644 index 0000000000..8fe5d384d4 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py @@ -0,0 +1,206 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_external_lobby_bypass_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import ( + teams_meeting_external_lobby_bypass_disabled, + ) + + check = teams_meeting_external_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 0 + + def test_external_users_can_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import ( + teams_meeting_external_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_users_to_bypass_lobby="Everyone" + ) + + check = teams_meeting_external_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "People outside the organization can bypass the lobby." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_only_internal_users_can_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import ( + teams_meeting_external_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests" + ) + + check = teams_meeting_external_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Only people in the organization can bypass the lobby." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_organizer_only_can_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import ( + teams_meeting_external_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_users_to_bypass_lobby="OrganizerOnly" + ) + + check = teams_meeting_external_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Only people in the organization can bypass the lobby." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_invited_users_can_bypass_lobby(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import ( + teams_meeting_external_lobby_bypass_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_external_users_to_bypass_lobby="InvitedUsers" + ) + + check = teams_meeting_external_lobby_bypass_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Only people in the organization can bypass the lobby." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted_test.py b/tests/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted_test.py new file mode 100644 index 0000000000..5c47f1e445 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_presenters_restricted/teams_meeting_presenters_restricted_test.py @@ -0,0 +1,118 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_presenters_restricted: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted import ( + teams_meeting_presenters_restricted, + ) + + check = teams_meeting_presenters_restricted() + result = check.execute() + assert len(result) == 0 + + def test_presenters_not_restricted(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted import ( + teams_meeting_presenters_restricted, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + designated_presenter_role_mode="EveryoneUserOverride" + ) + + check = teams_meeting_presenters_restricted() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Not only organizers and co-organizers can present." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_presenters_restricted(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_presenters_restricted.teams_meeting_presenters_restricted import ( + teams_meeting_presenters_restricted, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + designated_presenter_role_mode="OrganizerOnlyUserOverride" + ) + + check = teams_meeting_presenters_restricted() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Only organizers and co-organizers can present." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled_test.py new file mode 100644 index 0000000000..617d9ebc49 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_recording_disabled/teams_meeting_recording_disabled_test.py @@ -0,0 +1,118 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_teams_meeting_recording_disabled: + def test_no_global_meeting_policy(self): + teams_client = mock.MagicMock() + teams_client.global_meeting_policy = None + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled import ( + teams_meeting_recording_disabled, + ) + + check = teams_meeting_recording_disabled() + result = check.execute() + assert len(result) == 0 + + def test_meeting_recording_enabled(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled import ( + teams_meeting_recording_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_cloud_recording=True + ) + + check = teams_meeting_recording_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended == "Meeting recording is enabled by default." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" + + def test_meeting_recording_disabled(self): + teams_client = mock.MagicMock() + teams_client.audited_tenant = "audited_tenant" + teams_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_microsoft_teams" + ), + mock.patch( + "prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled.teams_client", + new=teams_client, + ), + ): + from prowler.providers.m365.services.teams.teams_meeting_recording_disabled.teams_meeting_recording_disabled import ( + teams_meeting_recording_disabled, + ) + from prowler.providers.m365.services.teams.teams_service import ( + GlobalMeetingPolicy, + ) + + teams_client.global_meeting_policy = GlobalMeetingPolicy( + allow_cloud_recording=False + ) + + check = teams_meeting_recording_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "Meeting recording is disabled by default." + ) + assert result[0].resource == teams_client.global_meeting_policy.dict() + assert ( + result[0].resource_name + == "Teams Meetings Global (Org-wide default) Policy" + ) + assert result[0].resource_id == "teamsMeetingsGlobalPolicy" diff --git a/tests/providers/m365/services/teams/teams_service_test.py b/tests/providers/m365/services/teams/teams_service_test.py index a4b523eaf7..a87e51ca33 100644 --- a/tests/providers/m365/services/teams/teams_service_test.py +++ b/tests/providers/m365/services/teams/teams_service_test.py @@ -28,6 +28,13 @@ def mock_get_global_meeting_policy(_): return GlobalMeetingPolicy( allow_anonymous_users_to_join_meeting=False, allow_anonymous_users_to_start_meeting=False, + allow_external_participant_give_request_control=False, + allow_external_non_trusted_meeting_chat=False, + allow_cloud_recording=False, + designated_presenter_role_mode="EveryoneUserOverride", + allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests", + allow_pstn_users_to_bypass_lobby=False, + meeting_chat_enabled_type="EnabledExceptAnonymous", ) @@ -122,5 +129,12 @@ class Test_Teams_Service: assert teams_client.global_meeting_policy == GlobalMeetingPolicy( allow_anonymous_users_to_join_meeting=False, allow_anonymous_users_to_start_meeting=False, + allow_external_participant_give_request_control=False, + allow_external_non_trusted_meeting_chat=False, + allow_cloud_recording=False, + designated_presenter_role_mode="EveryoneUserOverride", + allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests", + allow_pstn_users_to_bypass_lobby=False, + meeting_chat_enabled_type="EnabledExceptAnonymous", ) teams_client.powershell.close() diff --git a/ui/components/overview/provider-overview/provider-overview.tsx b/ui/components/overview/provider-overview/provider-overview.tsx index 48333c5fbc..41d518eb8a 100644 --- a/ui/components/overview/provider-overview/provider-overview.tsx +++ b/ui/components/overview/provider-overview/provider-overview.tsx @@ -8,6 +8,7 @@ import { AzureProviderBadge, GCPProviderBadge, KS8ProviderBadge, + M365ProviderBadge, } from "@/components/icons/providers-badge"; import { CustomButton } from "@/components/ui/custom/custom-button"; import { ProviderOverviewProps } from "@/types"; @@ -26,6 +27,8 @@ export const ProvidersOverview = ({ return ; case "azure": return ; + case "m365": + return ; case "gcp": return ; case "kubernetes": @@ -38,6 +41,7 @@ export const ProvidersOverview = ({ const providers = [ { id: "aws", name: "AWS" }, { id: "azure", name: "Azure" }, + { id: "m365", name: "M365" }, { id: "gcp", name: "GCP" }, { id: "kubernetes", name: "Kubernetes" }, ]; diff --git a/ui/components/providers/workflow/forms/update-via-role-form.tsx b/ui/components/providers/workflow/forms/update-via-role-form.tsx index 85e00e3cb1..c0d9c24917 100644 --- a/ui/components/providers/workflow/forms/update-via-role-form.tsx +++ b/ui/components/providers/workflow/forms/update-via-role-form.tsx @@ -54,7 +54,7 @@ export const UpdateViaRoleForm = ({ aws_secret_access_key: "", aws_session_token: "", role_session_name: "", - session_duration: 3600, + session_duration: "3600", }), }, }); diff --git a/ui/components/providers/workflow/forms/via-role-form.tsx b/ui/components/providers/workflow/forms/via-role-form.tsx index 917f049c7c..3bed8d3af7 100644 --- a/ui/components/providers/workflow/forms/via-role-form.tsx +++ b/ui/components/providers/workflow/forms/via-role-form.tsx @@ -59,7 +59,7 @@ export const ViaRoleForm = ({ aws_secret_access_key: "", aws_session_token: "", role_session_name: "", - session_duration: 3600, + session_duration: "3600", } : {}), }, diff --git a/ui/package-lock.json b/ui/package-lock.json index 8d222871e0..f0a11642d6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -240,9 +240,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, diff --git a/util/generate_compliance_json_from_csv_threatscope.py b/util/generate_compliance_json_from_csv_threatscope.py new file mode 100644 index 0000000000..47865d0c8d --- /dev/null +++ b/util/generate_compliance_json_from_csv_threatscope.py @@ -0,0 +1,36 @@ +import csv +import json +import sys + +# Convert a CSV file following the ThreatScore CSV format into a Prowler Compliance JSON file +# CSV fields: +# Id, Title, Description, Section, SubSection, AttributeDescription, AdditionalInformation, LevelOfRisk, Checks + +# get the CSV filename to convert from +file_name = sys.argv[1] + +# read the CSV file rows and use the column fields to form the Prowler compliance JSON file 'prowler_threatscore_aws.json' +output = {"Framework": "ProwlerThreatScore", "Version": "1.0", "Requirements": []} +with open(file_name, newline="", encoding="utf-8") as f: + reader = csv.reader(f, delimiter=",") + for row in reader: + attribute = { + "Title": row[1], + "Section": row[3], + "SubSection": row[4], + "AttributeDescription": row[5], + "AdditionalInformation": row[6], + "LevelOfRisk": row[7], + } + output["Requirements"].append( + { + "Id": row[0], + "Description": row[2], + "Checks": list(map(str.strip, row[8].split(","))), + "Attributes": [attribute], + } + ) + +# Write the output Prowler compliance JSON file 'prowler_threatscore_aws.json' locally +with open("prowler_threatscore_azure.json", "w", encoding="utf-8") as outfile: + json.dump(output, outfile, indent=4, ensure_ascii=False) diff --git a/util/get_prowler_threatscore_from_generic_output.py b/util/get_prowler_threatscore_from_generic_output.py new file mode 100644 index 0000000000..d3cd53a6b6 --- /dev/null +++ b/util/get_prowler_threatscore_from_generic_output.py @@ -0,0 +1,52 @@ +import csv +import json +import sys + +file_name_output = sys.argv[1] # It is the output CSV file +file_name_compliance = sys.argv[2] # It is the compliance JSON file + + +number_of_findings_per_pillar = {} +score_per_pillar = {} +# Read the compliance JSON file +with open(file_name_compliance, "r") as file: + data = json.load(file) + +# Read the output CSV file +with open(file_name_output, "r") as file: + reader = csv.reader(file, delimiter=";") + headers = next(reader) + if "CHECK_ID" in headers: + check_id_index = headers.index("CHECK_ID") + if "STATUS" in headers: + status_index = headers.index("STATUS") + if "MUTED" in headers: + muted_index = headers.index("MUTED") + for row in reader: + for requirement in data["Requirements"]: + # Take the column that contains the CHECK_ID + if row[check_id_index] in requirement["Checks"]: + if ( + requirement["Attributes"][0]["Section"] + not in number_of_findings_per_pillar.keys() + ): + number_of_findings_per_pillar[ + requirement["Attributes"][0]["Section"] + ] = 0 + if ( + requirement["Attributes"][0]["Section"] + not in score_per_pillar.keys() + ): + score_per_pillar[requirement["Attributes"][0]["Section"]] = 0 + if row[status_index] == "FAIL" and row[muted_index] != "TRUE": + number_of_findings_per_pillar[ + requirement["Attributes"][0]["Section"] + ] += 1 + score_per_pillar[ + requirement["Attributes"][0]["Section"] + ] += requirement["Attributes"][0]["LevelOfRisk"] + +for key, value in number_of_findings_per_pillar.items(): + print("Pillar:", key) + print("Score:", score_per_pillar[key] / value) + print("--------------------------------")