pax_global_header00006660000000000000000000000064150626051400014511gustar00rootroot0000000000000052 comment=0f6dbea338cf649fd97421b0a6fcbae120d1e558 auth0-auth0-python-0f6dbea/000077500000000000000000000000001506260514000156415ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.codecov.yml000066400000000000000000000006251506260514000200670ustar00rootroot00000000000000coverage: precision: 2 round: down range: "70...100" status: project: default: enabled: true target: auto threshold: 3% if_no_uploads: error patch: default: enabled: true target: 80% threshold: 25% if_no_uploads: error changes: default: enabled: true if_no_uploads: error comment: false auth0-auth0-python-0f6dbea/.flake8000066400000000000000000000000611506260514000170110ustar00rootroot00000000000000[flake8] ignore = E501 F401 max-line-length = 88 auth0-auth0-python-0f6dbea/.github/000077500000000000000000000000001506260514000172015ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/CODEOWNERS000066400000000000000000000000541506260514000205730ustar00rootroot00000000000000* @auth0/project-dx-sdks-engineer-codeowner auth0-auth0-python-0f6dbea/.github/ISSUE_TEMPLATE/000077500000000000000000000000001506260514000213645ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/ISSUE_TEMPLATE/Bug Report.yml000066400000000000000000000047241506260514000240670ustar00rootroot00000000000000name: 🐞 Report a bug description: Have you found a bug or issue? Create a bug report for this library labels: ["bug"] body: - type: markdown attributes: value: | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. - type: checkboxes id: checklist attributes: label: Checklist options: - label: I have looked into the [Readme](https://github.com/auth0/auth0-python#readme) and [Examples](https://github.com/auth0/auth0-python/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. required: true - label: I have looked into the [API documentation](https://auth0-python.readthedocs.io/en/latest/) and have not found a suitable solution or answer. required: true - label: I have searched the [issues](https://github.com/auth0/auth0-python/issues) and have not found a suitable solution or answer. required: true - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. required: true - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). required: true - type: textarea id: description attributes: label: Description description: Provide a clear and concise description of the issue, including what you expected to happen. validations: required: true - type: textarea id: reproduction attributes: label: Reproduction description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. placeholder: | 1. Step 1... 2. Step 2... 3. ... validations: required: true - type: textarea id: additional-context attributes: label: Additional context description: Other libraries that might be involved, or any other relevant information you think would be useful. validations: required: false - type: input id: environment-version attributes: label: auth0-python version validations: required: true - type: input id: environment-python-version attributes: label: Python version validations: required: true auth0-auth0-python-0f6dbea/.github/ISSUE_TEMPLATE/Feature Request.yml000066400000000000000000000041741506260514000251210ustar00rootroot00000000000000name: 🧩 Feature request description: Suggest an idea or a feature for this library labels: ["feature request"] body: - type: checkboxes id: checklist attributes: label: Checklist options: - label: I have looked into the [Readme](https://github.com/auth0/auth0-python#readme) and [Examples](https://github.com/auth0/auth0-python/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. required: true - label: I have looked into the [API documentation](https://auth0-python.readthedocs.io/en/latest/) and have not found a suitable solution or answer. required: true - label: I have searched the [issues](https://github.com/auth0/auth0-python/issues) and have not found a suitable solution or answer. required: true - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. required: true - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). required: true - type: textarea id: description attributes: label: Describe the problem you'd like to have solved description: A clear and concise description of what the problem is. placeholder: I'm always frustrated when... validations: required: true - type: textarea id: ideal-solution attributes: label: Describe the ideal solution description: A clear and concise description of what you want to happen. validations: required: true - type: textarea id: alternatives-and-workarounds attributes: label: Alternatives and current workarounds description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. validations: required: false - type: textarea id: additional-context attributes: label: Additional context description: Add any other context or screenshots about the feature request here. validations: required: false auth0-auth0-python-0f6dbea/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002441506260514000233540ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Auth0 Community url: https://community.auth0.com about: Discuss this SDK in the Auth0 Community forums auth0-auth0-python-0f6dbea/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000025651506260514000230120ustar00rootroot00000000000000### Changes Please describe both what is changing and why this is important. Include: - Endpoints added, deleted, deprecated, or changed - Classes and methods added, deleted, deprecated, or changed - Screenshots of new or changed UI, if applicable - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) - Any alternative designs or approaches considered ### References Please include relevant links supporting this change such as a: - support ticket - community post - StackOverflow post - support forum thread ### Testing Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. - [ ] This change adds unit test coverage - [ ] This change adds integration test coverage - [ ] This change has been tested on the latest version of the platform/language or why not ### Checklist - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) - [ ] All existing and new tests complete without errors auth0-auth0-python-0f6dbea/.github/actions/000077500000000000000000000000001506260514000206415ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/get-prerelease/000077500000000000000000000000001506260514000235455ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/get-prerelease/action.yml000066400000000000000000000013021506260514000255410ustar00rootroot00000000000000name: Return a boolean indicating if the version contains prerelease identifiers # # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. # # TODO: Remove once the common repo is public. # inputs: version: required: true outputs: prerelease: value: ${{ steps.get_prerelease.outputs.PRERELEASE }} runs: using: composite steps: - id: get_prerelease shell: bash run: | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then echo "PRERELEASE=true" >> $GITHUB_OUTPUT else echo "PRERELEASE=false" >> $GITHUB_OUTPUT fi env: VERSION: ${{ inputs.version }}auth0-auth0-python-0f6dbea/.github/actions/get-release-notes/000077500000000000000000000000001506260514000241645ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/get-release-notes/action.yml000066400000000000000000000022571506260514000261720ustar00rootroot00000000000000name: Return the release notes extracted from the PR body # # Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. # # TODO: Remove once the common repo is public. # inputs: version: required: true repo_name: required: false repo_owner: required: true token: required: true outputs: release-notes: value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} runs: using: composite steps: - uses: actions/github-script@v7 id: get_release_notes with: result-encoding: string script: | const { data: pulls } = await github.rest.pulls.list({ owner: process.env.REPO_OWNER, repo: process.env.REPO_NAME, state: 'all', head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, }); core.setOutput('RELEASE_NOTES', pulls[0].body); env: GITHUB_TOKEN: ${{ inputs.token }} REPO_OWNER: ${{ inputs.repo_owner }} REPO_NAME: ${{ inputs.repo_name }} VERSION: ${{ inputs.version }}auth0-auth0-python-0f6dbea/.github/actions/get-version/000077500000000000000000000000001506260514000231035ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/get-version/action.yml000066400000000000000000000006251506260514000251060ustar00rootroot00000000000000name: Return the version extracted from the branch name # # Returns the version from the .version file. # # TODO: Remove once the common repo is public. # outputs: version: value: ${{ steps.get_version.outputs.VERSION }} runs: using: composite steps: - id: get_version shell: bash run: | VERSION=$(head -1 .version) echo "VERSION=${VERSION}" >> $GITHUB_OUTPUTauth0-auth0-python-0f6dbea/.github/actions/release-create/000077500000000000000000000000001506260514000235225ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/release-create/action.yml000066400000000000000000000017401506260514000255240ustar00rootroot00000000000000name: Create a GitHub release # # Creates a GitHub release with the given version. # # TODO: Remove once the common repo is public. # inputs: token: required: true files: required: false name: required: true body: required: true tag: required: true commit: required: true draft: default: false required: false prerelease: default: false required: false fail_on_unmatched_files: default: true required: false runs: using: composite steps: - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 with: body: ${{ inputs.body }} name: ${{ inputs.name }} tag_name: ${{ inputs.tag }} target_commitish: ${{ inputs.commit }} draft: ${{ inputs.draft }} prerelease: ${{ inputs.prerelease }} fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} files: ${{ inputs.files }} env: GITHUB_TOKEN: ${{ inputs.token }}auth0-auth0-python-0f6dbea/.github/actions/rl-scanner/000077500000000000000000000000001506260514000227055ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/rl-scanner/action.yml000066400000000000000000000041411506260514000247050ustar00rootroot00000000000000name: "Reversing Labs Scanner" description: "Runs the Reversing Labs scanner on a specified artifact." inputs: artifact-path: description: "Path to the artifact to be scanned." required: true version: description: "Version of the artifact." required: true runs: using: "composite" steps: - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install Python dependencies shell: bash run: | pip install boto3 requests - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }} aws-region: us-east-1 mask-aws-account-id: true - name: Install RL Wrapper shell: bash run: | pip install rl-wrapper>=1.0.6 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" - name: Run RL Scanner shell: bash env: RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }} RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }} SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }} PYTHONUNBUFFERED: 1 run: | if [ ! -f "${{ inputs.artifact-path }}" ]; then echo "Artifact not found: ${{ inputs.artifact-path }}" exit 1 fi rl-wrapper \ --artifact "${{ inputs.artifact-path }}" \ --name "${{ github.event.repository.name }}" \ --version "${{ inputs.version }}" \ --repository "${{ github.repository }}" \ --commit "${{ github.sha }}" \ --build-env "github_actions" \ --suppress_output # Check the outcome of the scanner if [ $? -ne 0 ]; then echo "RL Scanner failed." echo "scan-status=failed" >> $GITHUB_ENV exit 1 else echo "RL Scanner passed." echo "scan-status=success" >> $GITHUB_ENV fi outputs: scan-status: description: "The outcome of the scan process." value: ${{ env.scan-status }} auth0-auth0-python-0f6dbea/.github/actions/tag-exists/000077500000000000000000000000001506260514000227315ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/actions/tag-exists/action.yml000066400000000000000000000017051506260514000247340ustar00rootroot00000000000000name: Return a boolean indicating if a tag already exists for the repository # # Returns a simple true/false boolean indicating whether the tag exists or not. # # TODO: Remove once the common repo is public. # inputs: token: required: true tag: required: true outputs: exists: description: 'Whether the tag exists or not' value: ${{ steps.tag-exists.outputs.EXISTS }} runs: using: composite steps: - id: tag-exists shell: bash run: | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") if [ "$http_status_code" -ne "404" ] ; then echo "EXISTS=true" >> $GITHUB_OUTPUT else echo "EXISTS=false" >> $GITHUB_OUTPUT fi env: TAG_NAME: ${{ inputs.tag }} GITHUB_TOKEN: ${{ inputs.token }}auth0-auth0-python-0f6dbea/.github/dependabot.yml000066400000000000000000000004531506260514000220330ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" ignore: - dependency-name: "*" update-types: ["version-update:semver-major"] auth0-auth0-python-0f6dbea/.github/stale.yml000066400000000000000000000017251506260514000210410ustar00rootroot00000000000000# Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 90 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. daysUntilClose: 7 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: [] # Set to true to ignore issues with an assignee (defaults to false) exemptAssignees: true # Label to use when marking as stale staleLabel: closed:stale # Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ auth0-auth0-python-0f6dbea/.github/workflows/000077500000000000000000000000001506260514000212365ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/.github/workflows/codeql.yml000066400000000000000000000024061506260514000232320ustar00rootroot00000000000000name: CodeQL on: merge_group: pull_request: types: - opened - synchronize push: branches: - master schedule: - cron: "56 12 * * 1" permissions: actions: read contents: read security-events: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: analyze: name: Check for Vulnerabilities runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [python] steps: - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. - name: Checkout uses: actions/checkout@v5 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" auth0-auth0-python-0f6dbea/.github/workflows/docs.yml000066400000000000000000000024311506260514000227110ustar00rootroot00000000000000name: Build Documentation on: push: branches: - master permissions: contents: read pages: write id-token: write concurrency: group: "documentation" cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 - name: Setup Pages uses: actions/configure-pages@v5 - name: Configure Python uses: actions/setup-python@v6 with: python-version: "3.10" - name: Configure dependencies run: | pip install --user --upgrade pip pip install --user pipx pipx ensurepath pipx install sphinx==5.3.0 pipx inject sphinx pyjwt cryptography sphinx-mdinclude sphinx-rtd-theme sphinx-autodoc-typehints - name: Build documentation run: | sphinx-build ./docs/source ./docs/build --keep-going -n -a -b html - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: path: "./docs/build" deploy: needs: build runs-on: ubuntu-latest environment: name: "github-pages" url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment name: Deploy to GitHub Pages uses: actions/deploy-pages@v4 auth0-auth0-python-0f6dbea/.github/workflows/publish.yml000066400000000000000000000061371506260514000234360ustar00rootroot00000000000000name: Publish Release on: workflow_dispatch: ### TODO: Replace instances of './.github/actions/' with reference to the `dx-sdk-actions` repo is made public and this file is transferred over ### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public. permissions: contents: write id-token: write # Required for trusted publishing to PyPI jobs: rl-scanner: uses: ./.github/workflows/rl-scanner.yml with: python-version: "3.10" artifact-name: "auth0-python.tgz" secrets: RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} publish-pypi: if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) name: "PyPI" runs-on: ubuntu-latest needs: rl-scanner environment: release steps: - name: Checkout code uses: actions/checkout@v5 with: fetch-depth: 0 fetch-tags: true # Get the version from the branch name - id: get_version uses: ./.github/actions/get-version # Get the prerelease flag from the branch name - id: get_prerelease uses: ./.github/actions/get-prerelease with: version: ${{ steps.get_version.outputs.version }} # Get the release notes # This will expose the release notes as env.RELEASE_NOTES - id: get_release_notes uses: ./.github/actions/get-release-notes with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ steps.get_version.outputs.version }} repo_owner: ${{ github.repository_owner }} repo_name: ${{ github.event.repository.name }} # Create a release for the tag - uses: ./.github/actions/release-create with: token: ${{ secrets.GITHUB_TOKEN }} name: ${{ steps.get_version.outputs.version }} body: ${{ steps.get_release_notes.outputs.release-notes }} tag: ${{ steps.get_version.outputs.version }} commit: ${{ github.sha }} prerelease: ${{ steps.get_prerelease.outputs.prerelease }} - name: Configure Python uses: actions/setup-python@v6 with: python-version: "3.9" - name: Configure dependencies run: | pip install --user --upgrade pip pip install --user pipx pipx ensurepath pipx install poetry poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" - name: Build release run: | poetry build - name: Publish release uses: pypa/gh-action-pypi-publish@release/v1 auth0-auth0-python-0f6dbea/.github/workflows/rl-scanner.yml000066400000000000000000000047261506260514000240360ustar00rootroot00000000000000name: RL-Secure Workflow on: workflow_call: inputs: python-version: required: true type: string artifact-name: required: true type: string secrets: RLSECURE_LICENSE: required: true RLSECURE_SITE_KEY: required: true SIGNAL_HANDLER_TOKEN: required: true PRODSEC_TOOLS_USER: required: true PRODSEC_TOOLS_TOKEN: required: true PRODSEC_TOOLS_ARN: required: true jobs: rl-scanner: if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) runs-on: ubuntu-latest outputs: scan-status: ${{ steps.rl-scan-conclusion.outcome }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 fetch-tags: true - name: Configure Python uses: actions/setup-python@v6 with: python-version: ${{ inputs.python-version }} - name: Configure dependencies run: | pip install --user --upgrade pip pip install --user pipx pipx ensurepath pipx install poetry==1.4.2 pip install --upgrade pip pip install boto3 requests poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]==1.1.1" - name: Build release run: | poetry build - name: Create tgz build artifact run: | tar -czvf ${{ inputs.artifact-name }} * - name: Get Artifact Version id: get_version uses: ./.github/actions/get-version - name: Run RL Scanner id: rl-scan-conclusion uses: ./.github/actions/rl-scanner with: artifact-path: "$(pwd)/${{ inputs.artifact-name }}" version: "${{ steps.get_version.outputs.version }}" env: RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} - name: Output scan result run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENVauth0-auth0-python-0f6dbea/.github/workflows/snyk.yml000066400000000000000000000017431506260514000227520ustar00rootroot00000000000000name: Snyk on: merge_group: workflow_dispatch: pull_request: types: - opened - synchronize push: branches: - master schedule: - cron: '30 0 1,15 * *' permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: check: name: Check for Vulnerabilities runs-on: ubuntu-latest steps: - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha || github.ref }} - uses: snyk/actions/python@b98d498629f1c368650224d6d212bf7dfa89e4bf # pin@0.4.0 env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}auth0-auth0-python-0f6dbea/.github/workflows/test.yml000066400000000000000000000044111506260514000227400ustar00rootroot00000000000000name: Build and Test on: merge_group: pull_request: types: - opened - synchronize push: branches: - master permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: run: name: Run runs-on: ubuntu-latest env: BUBBLEWRAP_ARGUMENTS: | --unshare-all \ --clearenv \ --ro-bind / / \ --bind ${{ github.workspace }} ${{ github.workspace }} \ --tmpfs $HOME \ --tmpfs /tmp \ --tmpfs /var \ --dev /dev \ --proc /proc \ --die-with-parent \ --new-session \ strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha || github.ref }} - name: Configure Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: "${{ matrix.python-version }}" - name: Configure dependencies run: | sudo apt install bubblewrap pip install --user --upgrade pip pip install --user pipx pip install --user setuptools pipx ensurepath pipx install poetry poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" - name: Run tests run: | poetry run pytest --cov=auth0 --cov-report=term-missing:skip-covered --cov-report=xml # - name: Run lint # run: | # pipx install black==23.3.0 # pipx install flake8==5.0.4 # pipx install isort==5.11.5 # pipx install pyupgrade==3.3.2 # black . --check # flake8 . --count --show-source --statistics # isort . --diff --profile black # pyupgrade . --py37-plus --keep-runtime-typing - if: ${{ matrix.python-version == '3.10' }} name: Upload coverage uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@5.5.1 with: token: ${{ secrets.CODECOV_TOKEN }}auth0-auth0-python-0f6dbea/.gitignore000066400000000000000000000011571506260514000176350ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ bin/ *.egg-info/ .installed.cfg *.egg .pypirc pyvenv.cfg .python-version # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ .pytest_cache # Translations *.mo *.pot # Sphinx documentation docs/build/ # IDEA .idea/ *.iml # VSCode .vscode/ # OS-specific files .DS_Store auth0-auth0-python-0f6dbea/.pre-commit-config.yaml000066400000000000000000000015471506260514000221310ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.1.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/flake8 rev: 5.0.4 hooks: - id: flake8 - repo: https://github.com/asottile/pyupgrade rev: v3.3.2 hooks: - id: pyupgrade args: [--keep-runtime-typing] - repo: https://github.com/pycqa/isort rev: 5.11.5 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black - repo: https://github.com/python-poetry/poetry rev: 1.4.2 hooks: - id: poetry-check - id: poetry-lock - id: poetry-export args: ["--with", "dev", "--without-hashes", "--format", "requirements.txt", "--output", "requirements.txt"] auth0-auth0-python-0f6dbea/.readthedocs.yaml000066400000000000000000000002031506260514000210630ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/source/conf.py python: version: "3.7" install: - requirements: requirements.txt auth0-auth0-python-0f6dbea/.semgrepignore000066400000000000000000000001001506260514000204770ustar00rootroot00000000000000/.github/ /docs/ /examples/ /auth0/test/ /auth0/test_asyc/ *.md auth0-auth0-python-0f6dbea/.shiprc000066400000000000000000000001021506260514000171230ustar00rootroot00000000000000{ "files": { ".version": [] }, "prefixVersion": false } auth0-auth0-python-0f6dbea/.snyk000066400000000000000000000012131506260514000166230ustar00rootroot00000000000000# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. version: v1.12.0 # ignores vulnerabilities until expiry date; change duration by modifying expiry date ignore: SNYK-PYTHON-REQUESTS-72435: - '*': reason: 'unaffected, only affects https->http authorization header redirection.' expires: 2019-11-05T00:00:00.000Z SNYK-PYTHON-REQUESTS-40470: - '*': reason: 'patched in latest python versions: https://bugs.python.org/issue27568' "snyk:lic:pip:certifi:MPL-2.0": - '*': reason: "Accepting certifi’s MPL-2.0 license for now" expires: "2030-12-31T23:59:59Z" patch: {} auth0-auth0-python-0f6dbea/.version000066400000000000000000000000061506260514000173230ustar00rootroot000000000000004.13.0auth0-auth0-python-0f6dbea/CHANGELOG.md000066400000000000000000000606461506260514000174660ustar00rootroot00000000000000# Change Log ## [4.13.0](https://github.com/auth0/auth0-python/tree/4.13.0) (2025-09-17) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.12.0...4.13.0) **Added** - fix(backchannel): expose headers on `slow_down` errors (HTTP 429s) [\#744](https://github.com/auth0/auth0-python/pull/744) ([pmalouin](https://github.com/pmalouin)) ## [4.12.0](https://github.com/auth0/auth0-python/tree/4.12.0) (2025-09-15) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.11.0...4.12.0) **Added** - Updates for CIBA with email [\#720](https://github.com/auth0/auth0-python/pull/720) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.11.0](https://github.com/auth0/auth0-python/tree/4.11.0) (2025-09-11) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.10.0...4.11.0) **Added** - feat: Support For Network ACL Endpoints [\#706](https://github.com/auth0/auth0-python/pull/706) ([kishore7snehil](https://github.com/kishore7snehil)) **Fixed** - chore: fix workflow syntax errors and update dependencies [\#724](https://github.com/auth0/auth0-python/pull/724) ([kishore7snehil](https://github.com/kishore7snehil)) ## [4.10.0](https://github.com/auth0/auth0-python/tree/4.10.0) (2025-06-05) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.9.0...4.10.0) **Added** - chore: merge community PRs – bugfixes, features, and dependency upgrades [\#696](https://github.com/auth0/auth0-python/pull/696) ([kishore7snehil](https://github.com/kishore7snehil)) **Fixed** - fix: handle `authorization_details` in back_channel_login [\#695](https://github.com/auth0/auth0-python/pull/695) ([kishore7snehil](https://github.com/kishore7snehil)) ## [4.9.0](https://github.com/auth0/auth0-python/tree/4.9.0) (2025-04-01) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.8.1...4.9.0) **Added** - feat: Federated Connections Support [\#682](https://github.com/auth0/auth0-python/pull/682) ([kishore7snehil](https://github.com/kishore7snehil)) - Adding Support For CIBA with RAR [\#679](https://github.com/auth0/auth0-python/pull/679) ([kishore7snehil](https://github.com/kishore7snehil)) ## [4.8.1](https://github.com/auth0/auth0-python/tree/4.8.1) (2025-02-24) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.8.0...4.8.1) **Fixed** - Fix: Unauthorized Access Error For PAR [\#671](https://github.com/auth0/auth0-python/pull/671) ([kishore7snehil](https://github.com/kishore7snehil)) ## [4.8.0](https://github.com/auth0/auth0-python/tree/4.8.0) (2025-01-29) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.7.2...4.8.0) **Added** - Adding Support For RAR and JAR Requests [\#659](https://github.com/auth0/auth0-python/pull/659) ([kishore7snehil](https://github.com/kishore7snehil)) - Adding Support For Back Channel Login [\#643](https://github.com/auth0/auth0-python/pull/643) ([kishore7snehil](https://github.com/kishore7snehil)) **Fixed** - Consolidated Community PRs and Dependency Upgrades [\#660](https://github.com/auth0/auth0-python/pull/660) ([kishore7snehil](https://github.com/kishore7snehil)) - [fix typo in docstring](https://github.com/auth0/auth0-python/pull/637) ([@CarlosEduR ](https://github.com/CarlosEduR)) - [Added support for "include_totals" to all_organization_member_roles](https://github.com/auth0/auth0-python/pull/635) ([@jpayton-cx](https://github.com/jpayton-cx)) - [Fixed Version Table](https://github.com/auth0/auth0-python/pull/633) ([@sanchez](https://github.com/sanchez)) - [Remove upper bounds on all python dependency versions](https://github.com/auth0/auth0-python/pull/628) ([@ngfeldman](https://github.com/ngfeldman)) - [Adding secrets to Codecov Action Upload](https://github.com/auth0/auth0-python/pull/624) ([@developerkunal](https://github.com/developerkunal)) - Updating Dependancies And Workflow Action Versions [\#653](https://github.com/auth0/auth0-python/pull/653) ([kishore7snehil](https://github.com/kishore7snehil)) - Fixing the Github Workflow Issues [\#644](https://github.com/auth0/auth0-python/pull/644) ([kishore7snehil](https://github.com/kishore7snehil)) ## [4.7.2](https://github.com/auth0/auth0-python/tree/4.7.2) (2024-09-10) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.7.1...4.7.2) **Security** - Update cryptography requirements.txt [\#630](https://github.com/auth0/auth0-python/pull/630) ([duedares-rvj](https://github.com/duedares-rvj)) ## [4.7.1](https://github.com/auth0/auth0-python/tree/4.7.1) (2024-02-26) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.7.0...4.7.1) **Security** - Update cryptography requirements.txt [\#597](https://github.com/auth0/auth0-python/pull/597) ([skjensen](https://github.com/skjensen)) ## [4.7.0](https://github.com/auth0/auth0-python/tree/4.7.0) (2023-12-05) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.6.1...4.7.0) **⚠️ BREAKING CHANGES** - Add python 3.12 support, drop 3.7 [\#562](https://github.com/auth0/auth0-python/pull/562) ([adamjmcgrath](https://github.com/adamjmcgrath)) **Added** - [SDK-4138] Add support for Pushed Authorization Requests (PAR) [\#560](https://github.com/auth0/auth0-python/pull/560) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.6.1](https://github.com/auth0/auth0-python/tree/4.6.1) (2023-11-29) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.6.0...4.6.1) **Fixed** - Fix rest_async and async tests [\#556](https://github.com/auth0/auth0-python/pull/556) ([adamjmcgrath](https://github.com/adamjmcgrath)) - fix type hint for link_user_account [\#552](https://github.com/auth0/auth0-python/pull/552) ([tzzh](https://github.com/tzzh)) ## [4.6.0](https://github.com/auth0/auth0-python/tree/4.6.0) (2023-11-09) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.5.0...4.6.0) **Added** - [SDK-4544] Add orgs in client credentials support [\#549](https://github.com/auth0/auth0-python/pull/549) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Authentication API, the Database classs, Add the organization param to the change_password method [\#539](https://github.com/auth0/auth0-python/pull/539) ([shchotse](https://github.com/shchotse)) - Retry all methods on 429 [\#518](https://github.com/auth0/auth0-python/pull/518) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.5.0](https://github.com/auth0/auth0-python/tree/4.5.0) (2023-10-20) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.4.2...4.5.0) **Added** - [SDK-4656] Add fields to all_organization_members [\#537](https://github.com/auth0/auth0-python/pull/537) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.4.2](https://github.com/auth0/auth0-python/tree/4.4.2) (2023-08-31) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.4.1...4.4.2) **Fixed** - Fix python dependency version [\#522](https://github.com/auth0/auth0-python/pull/522) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Revert publishing types [\#521](https://github.com/auth0/auth0-python/pull/521) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.4.1](https://github.com/auth0/auth0-python/tree/4.4.1) (2023-08-21) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.4.0...4.4.1) **Fixed** - Fix for async types [\#515](https://github.com/auth0/auth0-python/pull/515) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.4.0](https://github.com/auth0/auth0-python/tree/4.4.0) (2023-07-25) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.3.0...4.4.0) **Added** - [SDK-4394] Add organization name validation [\#507](https://github.com/auth0/auth0-python/pull/507) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add type hints to `management` [\#497](https://github.com/auth0/auth0-python/pull/497) ([Viicos](https://github.com/Viicos)) **Fixed** - Fix asyncify for users client where token is not required [\#506](https://github.com/auth0/auth0-python/pull/506) ([cgearing](https://github.com/cgearing)) ## [4.3.0](https://github.com/auth0/auth0-python/tree/4.3.0) (2023-06-26) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.2.0...4.3.0) **Added** - Add forwardedFor option to password grant login [\#501](https://github.com/auth0/auth0-python/pull/501) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add connections.all name parameter [\#500](https://github.com/auth0/auth0-python/pull/500) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add type hints to base and `authentication` [\#472](https://github.com/auth0/auth0-python/pull/472) ([Viicos](https://github.com/Viicos)) **Fixed** - Fix async auth client [\#499](https://github.com/auth0/auth0-python/pull/499) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Fix update_template_universal_login [\#495](https://github.com/auth0/auth0-python/pull/495) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.2.0](https://github.com/auth0/auth0-python/tree/4.2.0) (2023-05-02) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.1.1...4.2.0) **Added** - Add cache_ttl param to AsymmetricSignatureVerifier [\#490](https://github.com/auth0/auth0-python/pull/490) ([matei-radu](https://github.com/matei-radu)) ## [4.1.1](https://github.com/auth0/auth0-python/tree/4.1.1) (2023-04-13) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.1.0...4.1.1) **Fixed** - Make pw realm params optional [\#484](https://github.com/auth0/auth0-python/pull/484) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Fix intellisense on Auth0 class [\#486](https://github.com/auth0/auth0-python/pull/486) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [4.1.0](https://github.com/auth0/auth0-python/tree/4.1.0) (2023-03-14) [Full Changelog](https://github.com/auth0/auth0-python/compare/4.0.0...4.1.0) **Added** - Add branding theme endpoints [\#477](https://github.com/auth0/auth0-python/pull/477) ([adamjmcgrath](https://github.com/adamjmcgrath)) - [SDK-4011] Add API2 Factor Management Endpoints [\#476](https://github.com/auth0/auth0-python/pull/476) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Use declarative setup with `pyproject.toml` [\#474](https://github.com/auth0/auth0-python/pull/474) ([Viicos](https://github.com/Viicos)) ## [4.0.0](https://github.com/auth0/auth0-python/tree/4.0.0) (2023-01-19) [Full Changelog](https://github.com/auth0/auth0-python/compare/3.24.1...4.0.0) **Added** - Add support for private_key_jwt [\#456](https://github.com/auth0/auth0-python/pull/456) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add support for managing client credentials [\#459](https://github.com/auth0/auth0-python/pull/459) ([adamjmcgrath](https://github.com/adamjmcgrath)) **Security** - Update pyjwt [\#460](https://github.com/auth0/auth0-python/pull/460) ([adamjmcgrath](https://github.com/adamjmcgrath)) **Changed** - Publish Python Support Schedule [\#454](https://github.com/auth0/auth0-python/pull/454) ([evansims](https://github.com/evansims)) **⚠️ BREAKING CHANGES** - Remove deprecated methods [\#461](https://github.com/auth0/auth0-python/pull/461) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Remove v3 folder [\#462](https://github.com/auth0/auth0-python/pull/462) ([adamjmcgrath](https://github.com/adamjmcgrath)) See the [V4_MIGRATION_GUIDE](https://github.com/auth0/auth0-python/blob/master/V4_MIGRATION_GUIDE.md) for more info. ## [3.24.1](https://github.com/auth0/auth0-python/tree/3.24.1) (2023-01-19) [Full Changelog](https://github.com/auth0/auth0-python/compare/3.24.0...3.24.1) **Fixed** - Remove unnecessary type param from update_template_universal_login [\#463](https://github.com/auth0/auth0-python/pull/463) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [3.24.0](https://github.com/auth0/auth0-python/tree/3.24.0) (2022-10-17) [Full Changelog](https://github.com/auth0/auth0-python/compare/3.23.1...3.24.0) **Added** - [SDK-3714] Async token verifier [\#445](https://github.com/auth0/auth0-python/pull/445) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add AsyncAuth0 to share a session among many services [\#443](https://github.com/auth0/auth0-python/pull/443) ([adamjmcgrath](https://github.com/adamjmcgrath)) **Fixed** - Bugfix 414 missing import [\#442](https://github.com/auth0/auth0-python/pull/442) ([adamjmcgrath](https://github.com/adamjmcgrath)) ## [3.23.1](https://github.com/auth0/auth0-python/tree/3.23.1) (2022-06-10) [Full Changelog](https://github.com/auth0/auth0-python/compare/3.23.0...3.23.1) **Fixed** - Pass rest_options through Auth0 constructor [\#354](https://github.com/auth0/auth0-python/pull/354) ([adamjmcgrath](https://github.com/adamjmcgrath)) 3.23.0 ------------------ **Added** - Asyncio Support [\#312](https://github.com/auth0/auth0-python/pull/312) ([adamjmcgrath](https://github.com/adamjmcgrath)) - Add `/api/v2/branding` endpoints support [\#313](https://github.com/auth0/auth0-python/pull/313) ([evansims](https://github.com/evansims)) 3.22.0 ------------------ **Added** - [SDK-3174] Add `DELETE` method for `/api/v2/users/{id}/authenticators` endpoint [\#301](https://github.com/auth0/auth0-python/pull/301) ([akmjenkins](https://github.com/akmjenkins)) - [SDK-3175] Return token claims in TokenVerifier.verify() [\#273](https://github.com/auth0/auth0-python/pull/273) ([bisguzar](https://github.com/bisguzar)) **Fixed** - [SDK-3173] Default to 'None' for `deployed` on GET /api/v2/actions/actions endpoint [\#309](https://github.com/auth0/auth0-python/pull/309) ([evansims](https://github.com/evansims)) 3.21.0 ------------------ **Added** - Add pagination to device credentials [\#300](https://github.com/auth0/auth0-python/pull/300) ([fionnulak](https://github.com/fionnulak)) 3.20.0 ------------------ **Added** - Add attack protection endpoints [\#303](https://github.com/auth0/auth0-python/pull/303) ([adamjmcgrath](https://github.com/adamjmcgrath)) 3.19.0 ------------------ **Added** - Add actions to Auth0 class [\#293](https://github.com/auth0/auth0-python/pull/293) ([jrzerr](https://github.com/jrzerr)) - Added support for prompts API [\#291](https://github.com/auth0/auth0-python/pull/291) ([lorinkoz](https://github.com/lorinkoz)) **Changed** - Remove references to ID token in generic token classes [\#295](https://github.com/auth0/auth0-python/pull/295) ([lbalmaceda](https://github.com/lbalmaceda)) **Fixed** - Use assertNotEqual instead of assertNotEquals for Python 3.11 compatibility. [\#294](https://github.com/auth0/auth0-python/pull/294) ([tirkarthi](https://github.com/tirkarthi)) 3.18.0 ------------------ **Added** - [SDK-2720] Add support for actions APIs [\#289](https://github.com/auth0/auth0-python/pull/289) ([jimmyjames](https://github.com/jimmyjames)) 3.17.0 ------------------ **Added** - Make the CI fail when the docs syntax is invalid [\#287](https://github.com/auth0/auth0-python/pull/287) ([lbalmaceda](https://github.com/lbalmaceda)) - [SDK-2687] Implement automatic rate-limit handling [\#285](https://github.com/auth0/auth0-python/pull/285) ([evansims](https://github.com/evansims)) - Use Sphinx to generate API docs [\#281](https://github.com/auth0/auth0-python/pull/281) ([lbalmaceda](https://github.com/lbalmaceda)) - Add Passwordless Login function [\#279](https://github.com/auth0/auth0-python/pull/279) ([lbalmaceda](https://github.com/lbalmaceda)) - [SDK 2665] Update endpoint methods to support 'from' and 'take' checkpoint pagination parameters, where appropriate [\#278](https://github.com/auth0/auth0-python/pull/278) ([evansims](https://github.com/evansims)) **Deprecated** - Deprecate /oauth/ro for passwordless [\#280](https://github.com/auth0/auth0-python/pull/280) ([lbalmaceda](https://github.com/lbalmaceda)) 3.16.2 ------------------ **Fixed** - Re-Route Job Results endpoint [\#275](https://github.com/auth0/auth0-python/pull/275) ([lbalmaceda](https://github.com/lbalmaceda)) 3.16.1 ------------------ **Fixed** - Remove requirements.txt file [\#270](https://github.com/auth0/auth0-python/pull/270) ([lbalmaceda](https://github.com/lbalmaceda)) - Repair Organisation get by name URL. [\#269](https://github.com/auth0/auth0-python/pull/269) ([queenvictoria](https://github.com/queenvictoria)) 3.16.0 ------------------ **Added** - Add access token validation guidance for organizations [\#262](https://github.com/auth0/auth0-python/pull/262) ([lbalmaceda](https://github.com/lbalmaceda)) - Add support for Organization MGMT API endpoints [SDK-2439] [\#261](https://github.com/auth0/auth0-python/pull/261) ([lbalmaceda](https://github.com/lbalmaceda)) - Add scope to refresh_token [\#256](https://github.com/auth0/auth0-python/pull/256) ([criles25](https://github.com/criles25)) - Allow configuration of outgoing request protocol [\#254](https://github.com/auth0/auth0-python/pull/254) ([garry-jeromson](https://github.com/garry-jeromson)) 3.15.0 ------------------ **Added** - Add support for organizations feature [\#258](https://github.com/auth0/auth0-python/pull/258) ([jimmyjames](https://github.com/jimmyjames)) 3.14.0 ------------------ **Added** - Adds a new user method invalidate_remembered_browsers [\#248](https://github.com/auth0/auth0-python/pull/248) ([kpurdon](https://github.com/kpurdon)) 3.13.0 ------------------ **Added** - Add support for Log Streams API [\#236](https://github.com/auth0/auth0-python/pull/236) ([lbalmaceda](https://github.com/lbalmaceda)) **Fixed** - Fix imports on the management/__init__.py file [\#235](https://github.com/auth0/auth0-python/pull/235) ([matthewarmand](https://github.com/matthewarmand)) 3.12.0 ------------------ **Added** - Add missing user profile properties to the signup endpoint [\#231](https://github.com/auth0/auth0-python/pull/231) ([lbalmaceda](https://github.com/lbalmaceda)) - Add Hooks management API [\#227](https://github.com/auth0/auth0-python/pull/227) ([guillp](https://github.com/guillp)) - Add missing external_id property to the import users job [\#222](https://github.com/auth0/auth0-python/pull/222) ([lbalmaceda](https://github.com/lbalmaceda)) **Changed** - Remove iat claim value check [\#223](https://github.com/auth0/auth0-python/pull/223) ([lbalmaceda](https://github.com/lbalmaceda)) **Fixed** - Skip sending optional parameters on POST request when unspecified [\#230](https://github.com/auth0/auth0-python/pull/230) ([lbalmaceda](https://github.com/lbalmaceda)) 3.11.0 ------------------ **Added** - Add send_completion_email to users import job [\#220](https://github.com/auth0/auth0-python/pull/220) ([lbalmaceda](https://github.com/lbalmaceda)) - Expose the time at which the Rate Limit will reset [\#219](https://github.com/auth0/auth0-python/pull/219) ([lbalmaceda](https://github.com/lbalmaceda)) **Removed** - Add deprecation note for DELETE /users (all users) [\#217](https://github.com/auth0/auth0-python/pull/217) ([lbalmaceda](https://github.com/lbalmaceda)) 3.10.0 ------------------ **Security** - Improved OIDC compliance [\#213](https://github.com/auth0/auth0-python/pull/213) ([lbalmaceda](https://github.com/lbalmaceda)) **Added** - Add connect/read timeout option [\#215](https://github.com/auth0/auth0-python/pull/215) ([lbalmaceda](https://github.com/lbalmaceda)) 3.9.2 ------------------ **Fixed** - Accept client_secret as passwordless/start param [\#211](https://github.com/auth0/auth0-python/pull/211) ([lbalmaceda](https://github.com/lbalmaceda)) 3.9.1 ------------------ **Changed** - Update minimum "requests" version to 2.14.0 [\#204](https://github.com/auth0/auth0-python/pull/204) ([lbalmaceda](https://github.com/lbalmaceda)) 3.9.0 ------------------ **Added** - Add Roles and Permissions endpoints [\#202](https://github.com/auth0/auth0-python/pull/202) ([lbalmaceda](https://github.com/lbalmaceda)) 3.8.1 ------------------ July 18, 2019: This release included an unintentionally breaking change affecting those users that were manually parsing the response from GET requests. e.g. /userinfo or /authorize. The `AuthenticationBase#get` method was incorrectly parsing the request result into a String. From this release on, making a GET request returns a Dictionary instead. **Breaking Change** - Fix request creation when headers are the default [\#198](https://github.com/auth0/auth0-python/pull/198) ([lbalmaceda](https://github.com/lbalmaceda)). 3.8.0 ------------------ **Fixed** - rules_config.unset fix [\#195](https://github.com/auth0/auth0-python/pull/195) ([jhunken](https://github.com/jhunken)) **Security** - Update requests dependency to latest version [\#196](https://github.com/auth0/auth0-python/pull/196) ([lbalmaceda](https://github.com/lbalmaceda)) 3.7.2 ------------------ **Fixed** - Fix HTTP method used for rotating Client secret [\#191](https://github.com/auth0/auth0-python/pull/191) ([lbalmaceda](https://github.com/lbalmaceda)) 3.7.1 ------------------ **Fixed** - Update telemetry format [\#187](https://github.com/auth0/auth0-python/pull/187) ([lbalmaceda](https://github.com/lbalmaceda)) 3.7.0 ------------------ **Changed** - Remove default value for search_engine [\#185](https://github.com/auth0/auth0-python/pull/185) ([lbalmaceda](https://github.com/lbalmaceda)) 3.6.1 ------------------ **Fixed** - Fixed Management API Grants class instantiation [\#179](https://github.com/auth0/auth0-python/pull/179) ([beck3905](https://github.com/beck3905)) 3.6.0 ------------------ **Added** - Add grants, custom domains, rules_configs to API [\#177](https://github.com/auth0/auth0-python/pull/177) ([sagnew-dg](https://github.com/sagnew-dg)) 3.5.0 ------------------ **Added** - Add Revoke Refresh Token endpoint [\#170](https://github.com/auth0/auth0-python/pull/170) ([lbalmaceda](https://github.com/lbalmaceda)) - Add /dbconnections/signup with username and metadata [\#169](https://github.com/auth0/auth0-python/pull/169) ([lbalmaceda](https://github.com/lbalmaceda)) 3.4.0 ------------------ **Added** - Add `client_id` param to ClientGrants.all [\#159](https://github.com/auth0/auth0-python/pull/159) ([danishprakash](https://github.com/danishprakash)) - Add telemetry headers to AuthenticationBase [\#152](https://github.com/auth0/auth0-python/pull/152) ([crgk](https://github.com/crgk)) - Add pre-commit pypgrade hook and update supported versions [\#124](https://github.com/auth0/auth0-python/pull/124) ([hugovk](https://github.com/hugovk)) - Implemented delete_user_by_email and test for connections [\#122](https://github.com/auth0/auth0-python/pull/122) ([runz0rd](https://github.com/runz0rd)) - Adds user export job creation. [\#112](https://github.com/auth0/auth0-python/pull/112) ([dmark](https://github.com/dmark)) **Changed** - String Formatting Updated [\#141](https://github.com/auth0/auth0-python/pull/141) ([vkmrishad](https://github.com/vkmrishad)) - Uses Python built-in modules to retrieve Python and auth0-python version number [\#125](https://github.com/auth0/auth0-python/pull/125) ([edawine](https://github.com/edawine)) **Fixed** - Stop lower-casing email on user search [\#167](https://github.com/auth0/auth0-python/pull/167) ([helmus](https://github.com/helmus)) - Always include Content-Type header in management requests [\#158](https://github.com/auth0/auth0-python/pull/158) ([crgk](https://github.com/crgk)) 3.3.0 ------------------ **Added** - Add pagination to Clients and Connections - Add pagination to Client Grants, Resource Servers and Rules - Add Email-Templates Management API endpoints **Fixed** - Replace default mutable arguments with None - Fix JSON error message handling for Management API 3.2.2 ------------------ **Fixed** - Upload the correct package contents to Pypi. 3.2.0 ------------------ **Added** - Raise Auth0Error for bad status code [\#98](https://github.com/auth0/auth0-python/pull/98) ([beck3905](https://github.com/beck3905)) **Fixed** - Correctly throw an exception when handing a text response [\#92](https://github.com/auth0/auth0-python/pull/92) ([benbc](https://github.com/benbc)) - Instantiate UserBlocks for consistency [\#90](https://github.com/auth0/auth0-python/pull/90) ([mattdodge](https://github.com/mattdodge)) 3.1.4 ------------------ Authentication API - Improve handling of inconsistent API error responses. 3.1.3 ------------------ Management API - Added `upsert` parameter to `import_users` job. 3.1.2 ------------------ Authentication API - Added `refresh_token` method to get_token 3.1.0 ------------------ Authentication API - Added Logout Functionality 3.0.0 ------------------ Authentication API - Added Support for API Authorization. `oauth/token` endpoint - Client Credentials Grant - Authorization Code Grant - Authorization Code PKCE Grant - Resource Owner Password Realm Grant - Added Support for API Authorization. `authorize` endpoint - Authorization Code Grant Management API v2 - Added Support for Guardian - Added Support to retrieve Logs - Added Support to manage Resource Servers - Added Support to manage Client Grants - Added Support to manage User blocks auth0-auth0-python-0f6dbea/DEVELOPMENT.rst000066400000000000000000000014731506260514000201620ustar00rootroot00000000000000Instructions to upload auth0-python to PyPI =========================================== 1) Create a ``.pypirc`` file in your home directory with the following contents (replace ```` and ```` with your PyPI credentials): .. code-block:: [distutils] index-servers = pypi [pypi] repository: https://pypi.python.org/pypi username= password= 2) Bump the version number in ``auth0/__init__.py``. 3) Make sure you add changes to the `changelog <./CHANGELOG.md>`__. 4) Run the following command: .. code-block:: bash python3 setup.py sdist bdist_wheel --universal twine upload --repository-url https://upload.pypi.org/legacy/ dist/* or do it using docker: .. code-block:: bash sh publish.sh auth0-auth0-python-0f6dbea/EXAMPLES.md000066400000000000000000000171041506260514000174040ustar00rootroot00000000000000# Examples using auth0-python - [Authentication SDK](#authentication-sdk) - [ID token validation](#id-token-validation) - [Authenticating with a application configured to use `private_key_jwt` token endpoint auth method](#authenticating-with-a-application-configured-to-use-private-key-jwt-token-endpoint-auth-method) - [Management SDK](#management-sdk) - [Connections](#connections) - [Error handling](#error-handling) - [Asynchronous environments](#asynchronous-environments) ## Authentication SDK ### ID token validation Upon successful authentication, the credentials received may include an `id_token`, if the authentication request contained the `openid` scope. The `id_token` contains information associated with the authenticated user. You can read more about ID tokens [here](https://auth0.com/docs/tokens/concepts/id-tokens). Before you access its contents, you must verify that the ID token has not been tampered with and that it is meant for your application to consume. The `TokenVerifier` class can be used to perform this verification. To create a `TokenVerifier`, the following arguments are required: - A `SignatureVerifier` instance, which is responsible for verifying the token's algorithm name and signature. - The expected issuer value, which typically matches the Auth0 domain prefixed with `https://` and suffixed with `/`. - The expected audience value, which typically matches the Auth0 application client ID. The type of `SignatureVerifier` used depends upon the signing algorithm used by your Auth0 application. You can view this value in your application settings under `Advanced settings | OAuth | JsonWebToken Signature Algorithm`. Auth0 recommends using the RS256 asymmetric signing algorithm. You can read more about signing algorithms [here](https://auth0.com/docs/tokens/signing-algorithms). For asymmetric algorithms like RS256, use the `AsymmetricSignatureVerifier` class, passing the public URL where the certificates for the public keys can be found. This will typically be your Auth0 domain with the `/.well-known/jwks.json` path appended to it. For example, `https://your-domain.auth0.com/.well-known/jwks.json`. For symmetric algorithms like HS256, use the `SymmetricSignatureVerifier` class, passing the value of the client secret of your Auth0 application. The following example demonstrates the verification of an ID token signed with the RS256 signing algorithm: ```python from auth0.authentication.token_verifier import TokenVerifier, AsymmetricSignatureVerifier domain = 'myaccount.auth0.com' client_id = 'exampleid' # After authenticating id_token = auth_result['id_token'] jwks_url = 'https://{}/.well-known/jwks.json'.format(domain) issuer = 'https://{}/'.format(domain) sv = AsymmetricSignatureVerifier(jwks_url) # Reusable instance tv = TokenVerifier(signature_verifier=sv, issuer=issuer, audience=client_id) tv.verify(id_token) ``` If the token verification fails, a `TokenValidationError` will be raised. In that scenario, the ID token should be deemed invalid and its contents should not be trusted. ### Authenticating with a application configured to use `private_key_jwt` token endpoint auth method ```python from auth0.authentication import GetToken private_key = """-----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEAwfUb0nUC0aKB3WiytFhnCIg455BYC+dR3MUGadWpIg7S6lbi ... 2tjIvH4GN9ZkIGwzxIOP61wkUGwGaIzacOTIWOvqRI0OaYr9U18Ep1trvgGR -----END RSA PRIVATE KEY----- """ get_token = GetToken( "my-domain.us.auth0.com", "my-client-id", client_assertion_signing_key=private_key, ) token = get_token.client_credentials( "https://my-domain.us.auth0.com/api/v2/" ) ``` ## Management SDK ### Connections Let's see how we can use this to get all available connections. (this action requires the token to have the following scope: `read:connections`) ```python auth0.connections.all() ``` Which will yield a list of connections similar to this: ```python [ { 'enabled_clients': [u'rOsnWgtw23nje2QCDuDJNVpxlsCylSLE'], 'id': u'con_ErZf9LpXQDE0cNBr', 'name': u'Amazon-Connection', 'options': {u'profile': True, u'scope': [u'profile']}, 'strategy': u'amazon' }, { 'enabled_clients': [u'rOsnWgtw23nje2QCDuDJNVpxlsCylSLE'], 'id': u'con_i8qF5DPiZ3FdadwJ', 'name': u'Username-Password-Authentication', 'options': {u'brute_force_protection': True}, 'strategy': u'auth0' } ] ``` Modifying an existing connection is equally as easy. Let's change the name of connection `'con_ErZf9LpXQDE0cNBr'`. (The token will need scope: `update:connections` to make this one work) ```python auth0.connections.update('con_ErZf9LpXQDE0cNBr', {'name': 'MyNewName'}) ``` That's it! Using the `get` method of the connections endpoint we can verify that the rename actually happened. ```python modified_connection = auth0.connections.get('con_ErZf9LpXQDE0cNBr') ``` Which returns something like this ```python { 'enabled_clients': [u'rOsnWgtw23nje2QCDuDJNVpxlsCylSLE'], 'id': u'con_ErZf9LpXQDE0cNBr', 'name': u'MyNewName', 'options': {u'profile': True, u'scope': [u'profile']}, 'strategy': u'amazon' } ``` Success! All endpoints follow a similar structure to `connections`, and try to follow as closely as possible the [API documentation](https://auth0.com/docs/api/v2). ## Error handling When consuming methods from the API clients, the requests could fail for a number of reasons: - Invalid data sent as part of the request: An `Auth0Error` is raised with the error code and description. - Global or Client Rate Limit reached: A `RateLimitError` is raised and the time at which the limit resets is exposed in the `reset_at` property. When the header is unset, this value will be `-1`. - Network timeouts: Adjustable by passing a `timeout` argument to the client. See the [rate limit docs](https://auth0.com/docs/policies/rate-limits) for details. ## Asynchronous environments This SDK provides async methods built on top of [asyncio](https://docs.python.org/3/library/asyncio.html). To make them available you must have the [aiohttp](https://docs.aiohttp.org/en/stable/) module installed. Then additional methods with the `_async` suffix will be added to modules created by the `management.Auth0` class or to classes that are passed to the `asyncify` method. For example: ```python import asyncio import aiohttp from auth0.asyncify import asyncify from auth0.management import Auth0, Users, Connections from auth0.authentication import Users as AuthUsers auth0 = Auth0('domain', 'mgmt_api_token') async def main(): # users = auth0.users.all() <= sync users = await auth0.users.all_async() # <= async # To share a session amongst multiple calls to the same service async with auth0.users as users: data = await users.get_async(id) users.update_async(id, data) # To share a session amongst multiple calls to multiple services async with Auth0('domain', 'mgmt_api_token') as auth0: user = await auth0.users.get_async(user_id) connection = await auth0.connections.get_async(connection_id) # Use asyncify directly on services Users = asyncify(Users) Connections = asyncify(Connections) users = Users(domain, mgmt_api_token) connections = Connections(domain, mgmt_api_token) # Create a session and share it among the services session = aiohttp.ClientSession() users.set_session(session) connections.set_session(session) u = await auth0.users.all_async() c = await auth0.connections.all_async() session.close() # Use auth api U = asyncify(AuthUsers) u = U(domain=domain) await u.userinfo_async(access_token) asyncio.run(main()) ``` auth0-auth0-python-0f6dbea/LICENSE000066400000000000000000000021351506260514000166470ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Auth0, Inc. (http://auth0.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. auth0-auth0-python-0f6dbea/README.md000066400000000000000000000202071506260514000171210ustar00rootroot00000000000000![Auth0 SDK for Python](https://cdn.auth0.com/website/sdks/banners/auth0-python-banner.png) ![Release](https://img.shields.io/pypi/v/auth0-python) [![Codecov](https://img.shields.io/codecov/c/github/auth0/auth0-python)](https://codecov.io/gh/auth0/auth0-python) ![Downloads](https://img.shields.io/pypi/dw/auth0-python) [![License](https://img.shields.io/:license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT) [![CircleCI](https://img.shields.io/circleci/build/github/auth0/auth0-python)](https://circleci.com/gh/auth0/auth0-python) Learn how to integrate Auth0 with Python. ## Documentation - [Docs site](https://www.auth0.com/docs) - explore our docs site and learn more about Auth0. ## Getting started ### Installation You can install the auth0 Python SDK using the following command. ``` pip install auth0-python ``` > Requires Python 3.7 or higher. ### Usage #### Authentication SDK The Authentication SDK is organized into components that mirror the structure of the [API documentation](https://auth0.com/docs/auth-api). If you need to sign up a user using their email and password, you can use the Database object. ```python from auth0.authentication import Database database = Database('my-domain.us.auth0.com', 'my-client-id') database.signup(email='user@domain.com', password='secr3t', connection='Username-Password-Authentication') ``` If you need to authenticate a user using their email and password, you can use the `GetToken` object, which enables making requests to the `/oauth/token` endpoint. ```python from auth0.authentication import GetToken token = GetToken('my-domain.us.auth0.com', 'my-client-id', client_secret='my-client-secret') token.login(username='user@domain.com', password='secr3t', realm='Username-Password-Authentication') ``` #### Management SDK To use the management library you will need to instantiate an Auth0 object with a domain and a [Management API v2 token](https://auth0.com/docs/api/management/v2/tokens). Please note that these token last 24 hours, so if you need it constantly you should ask for it programmatically using the client credentials grant with a [non interactive client](https://auth0.com/docs/api/management/v2/tokens#1-create-and-authorize-a-client) authorized to access the API. For example: ```python from auth0.authentication import GetToken domain = 'myaccount.auth0.com' non_interactive_client_id = 'exampleid' non_interactive_client_secret = 'examplesecret' get_token = GetToken(domain, non_interactive_client_id, client_secret=non_interactive_client_secret) token = get_token.client_credentials('https://{}/api/v2/'.format(domain)) mgmt_api_token = token['access_token'] ``` Then use the token you've obtained as follows: ```python from auth0.management import Auth0 domain = 'myaccount.auth0.com' mgmt_api_token = 'MGMT_API_TOKEN' auth0 = Auth0(domain, mgmt_api_token) ``` The `Auth0()` object is now ready to take orders, see our [connections example](https://github.com/auth0/auth0-python/blob/master/EXAMPLES.md#connections) to find out how to use it! For more code samples on how to integrate the auth0-python SDK in your Python application, have a look at our [examples](https://github.com/auth0/auth0-python/blob/master/EXAMPLES.md). ## API reference ### Authentication Endpoints - Database ( `authentication.Database` ) - Delegated ( `authentication.Delegated` ) - Enterprise ( `authentication.Enterprise` ) - API Authorization - Get Token ( `authentication.GetToken`) - BackChannelLogin ( `authentication.BackChannelLogin`) - Passwordless ( `authentication.Passwordless` ) - PushedAuthorizationRequests ( `authentication.PushedAuthorizationRequests` ) - RevokeToken ( `authentication.RevokeToken` ) - Social ( `authentication.Social` ) - Users ( `authentication.Users` ) ### Management Endpoints - Actions() (`Auth0().action`) - AttackProtection() (`Auth0().attack_protection`) - Blacklists() ( `Auth0().blacklists` ) - Branding() ( `Auth0().branding` ) - ClientCredentials() ( `Auth0().client_credentials` ) - ClientGrants() ( `Auth0().client_grants` ) - Clients() ( `Auth0().clients` ) - Connections() ( `Auth0().connections` ) - CustomDomains() ( `Auth0().custom_domains` ) - DeviceCredentials() ( `Auth0().device_credentials` ) - EmailTemplates() ( `Auth0().email_templates` ) - Emails() ( `Auth0().emails` ) - Grants() ( `Auth0().grants` ) - Guardian() ( `Auth0().guardian` ) - Hooks() ( `Auth0().hooks` ) - Jobs() ( `Auth0().jobs` ) - LogStreams() ( `Auth0().log_streams` ) - Logs() ( `Auth0().logs` ) - NetworkAcls() ( `Auth0().network_acls` ) - Organizations() ( `Auth0().organizations` ) - Prompts() ( `Auth0().prompts` ) - ResourceServers() (`Auth0().resource_servers` ) - Roles() ( `Auth0().roles` ) - RulesConfigs() ( `Auth0().rules_configs` ) - Rules() ( `Auth0().rules` ) - SelfServiceProfiles() ( `Auth0().self_service_profiles` ) - Stats() ( `Auth0().stats` ) - Tenants() ( `Auth0().tenants` ) - Tickets() ( `Auth0().tickets` ) - UserBlocks() (`Auth0().user_blocks` ) - UsersByEmail() ( `Auth0().users_by_email` ) - Users() ( `Auth0().users` ) ## Support Policy Our support lifecycle policy mirrors the [Python support schedule](https://devguide.python.org/versions/). We do not support running the SDK on unsupported versions of Python that have ceased to receive security updates. Please ensure your environment remains up to date and running the latest Python version possible. | SDK Version | Python Version | Support Ends | |-------------|----------------|--------------| | 4.x | 3.12 | Oct 2028 | | | 3.11 | Oct 2027 | | | 3.10 | Oct 2026 | | | 3.9 | Oct 2025 | | | 3.8 | Oct 2024 | > As `pip` [reliably avoids](https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata) installing package updates that target incompatible Python versions, we may opt to remove support for [end-of-life](https://en.wikipedia.org/wiki/CPython#Version_history) Python versions during minor SDK updates. These are not considered breaking changes by this SDK. The following is a list of unsupported Python versions, and the last SDK version supporting them: | Python Version | Last SDK Version Supporting | |----------------|-----------------------------| | >= 3.7 | 4.6.1 | | >= 2.0, <= 3.6 | 3.x | You can determine what version of Python you have installed by running: ``` python --version ``` ## Feedback ### Contributing We appreciate feedback and contribution to this repo! Before you get started, please see the following: - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) - [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) ### Raise an issue To provide feedback or report a bug, please [raise an issue on our issue tracker](https://github.com/auth0/auth0-python/issues). ### Vulnerability Reporting Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. ---

Auth0 Logo

Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?

This project is licensed under the MIT license. See the LICENSE file for more info.

auth0-auth0-python-0f6dbea/V4_MIGRATION_GUIDE.md000066400000000000000000000044671506260514000210150ustar00rootroot00000000000000# V4 Migration Guide Guide to migrating from `3.x` to `4.x` - [Python <3.7 is no longer supported](#python-37-is-no-longer-supported) - [The `v3` subfolder has been removed](#the-v3-subfolder-has-been-removed) - [Client ID and client secret are now specified in the constructor for authentication clients](#client-id-and-client-secret-are-now-specified-in-the-constructor-for-authentication-clients) - [AuthorizeClient and Logout have been removed](#authorizeclient-and-logout-have-been-removed) - [Methods that call deprecated endpoints have been removed](#methods-that-call-deprecated-endpoints-have-been-removed) ## Python <3.7 is no longer supported Python <=3.6 and Python 2 are EOL and are no longer supported. Also note the new Python [Support Policy](https://github.com/auth0/auth0-python#support-policy) ## The `v3` subfolder has been removed Versioning the import paths was not necessary and made major upgrades unnecessarily complex, so this has been removed and all files have been moved up a directory. ### Before ```python from auth0.v3.management import Auth0 auth0 = Auth0(domain, mgmt_api_token) ``` ### After ```python from auth0.management import Auth0 auth0 = Auth0(domain, mgmt_api_token) ``` ## Client ID and client secret are now specified in the constructor for authentication clients ### Before ```py from auth0.authentication import GetToken get_token = GetToken('my-domain.us.auth0.com') get_token.client_credentials('my-client-id', 'my-client-secret', 'my-api') ``` ### After ```py from auth0.authentication import GetToken # `client_secret` is optional (you can now use `client_assertion_signing_key` as an alternative) get_token = GetToken('my-domain.us.auth0.com', 'my-client-id', client_secret='my-client-secret') get_token.client_credentials('my-api') ``` ## AuthorizeClient and Logout have been removed The authorize and logout requests need to be done in a user agent, so it didn't make sense to include them in a REST client. ## Methods that call deprecated endpoints have been removed The following methods have been removed: ### Authentication - `database.login` - Use `get_token.login` - `passwordless.sms_login` - Use `get_token.passwordless_login` - `users.tokeninfo` - `users.userinfo` ### Management - `users.delete_all_users` - Use `users.delete` - `jobs.get_results` - Use `jobs.get` auth0-auth0-python-0f6dbea/auth0/000077500000000000000000000000001506260514000166625ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/__init__.py000066400000000000000000000004121506260514000207700ustar00rootroot00000000000000# This value is updated by `poetry_dynamic_versioning` during build time from the latest git tag __version__ = "0.0.0" from auth0.exceptions import Auth0Error, RateLimitError, TokenValidationError __all__ = ("Auth0Error", "RateLimitError", "TokenValidationError") auth0-auth0-python-0f6dbea/auth0/asyncify.py000066400000000000000000000070441506260514000210660ustar00rootroot00000000000000import aiohttp from auth0.authentication import Users from auth0.authentication.base import AuthenticationBase from auth0.rest import RestClientOptions from auth0.rest_async import AsyncRestClient def _gen_async(client, method): m = getattr(client, method) async def closure(*args, **kwargs): return await m(*args, **kwargs) return closure def asyncify(cls): methods = [ func for func in dir(cls) if callable(getattr(cls, func)) and not func.startswith("_") ] class UsersAsyncClient(cls): def __init__( self, domain, telemetry=True, timeout=5.0, protocol="https", ): super().__init__(domain, telemetry, timeout, protocol) self.client = AsyncRestClient(None, telemetry=telemetry, timeout=timeout) class AsyncManagementClient(cls): def __init__( self, domain, token, telemetry=True, timeout=5.0, protocol="https", rest_options=None, ): super().__init__(domain, token, telemetry, timeout, protocol, rest_options) self.client = AsyncRestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) class AsyncAuthenticationClient(cls): def __init__( self, domain, client_id, client_secret=None, client_assertion_signing_key=None, client_assertion_signing_alg=None, telemetry=True, timeout=5.0, protocol="https", ): super().__init__( domain, client_id, client_secret, client_assertion_signing_key, client_assertion_signing_alg, telemetry, timeout, protocol, ) self.client = AsyncRestClient( None, options=RestClientOptions( telemetry=telemetry, timeout=timeout, retries=0 ), ) class Wrapper(cls): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if cls == Users: self._async_client = UsersAsyncClient(*args, **kwargs) elif AuthenticationBase in cls.__bases__: self._async_client = AsyncAuthenticationClient(*args, **kwargs) else: self._async_client = AsyncManagementClient(*args, **kwargs) for method in methods: setattr( self, f"{method}_async", _gen_async(self._async_client, method), ) def set_session(self, session): """Set Client Session to improve performance by reusing session. Args: session (aiohttp.ClientSession): The client session which should be closed manually or within context manager. """ self._session = session self._async_client.client.set_session(self._session) async def __aenter__(self): """Automatically create and set session within context manager.""" self.set_session(aiohttp.ClientSession()) return self async def __aexit__(self, exc_type, exc_val, exc_tb): """Automatically close session within context manager.""" await self._session.close() return Wrapper auth0-auth0-python-0f6dbea/auth0/authentication/000077500000000000000000000000001506260514000217015ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/authentication/__init__.py000066400000000000000000000006301506260514000240110ustar00rootroot00000000000000from .database import Database from .delegated import Delegated from .enterprise import Enterprise from .get_token import GetToken from .passwordless import Passwordless from .revoke_token import RevokeToken from .social import Social from .users import Users __all__ = ( "Database", "Delegated", "Enterprise", "GetToken", "Passwordless", "RevokeToken", "Social", "Users", ) auth0-auth0-python-0f6dbea/auth0/authentication/async_token_verifier.py000066400000000000000000000163711506260514000264730ustar00rootroot00000000000000"""Token Verifier module""" from __future__ import annotations from typing import TYPE_CHECKING, Any from .. import TokenValidationError from ..rest_async import AsyncRestClient from .token_verifier import AsymmetricSignatureVerifier, JwksFetcher, TokenVerifier if TYPE_CHECKING: from aiohttp import ClientSession from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey class AsyncAsymmetricSignatureVerifier(AsymmetricSignatureVerifier): """Async verifier for RSA signatures, which rely on public key certificates. Args: jwks_url (str): The url where the JWK set is located. algorithm (str, optional): The expected signing algorithm. Defaults to "RS256". """ def __init__(self, jwks_url: str, algorithm: str = "RS256") -> None: super().__init__(jwks_url, algorithm) self._fetcher = AsyncJwksFetcher(jwks_url) def set_session(self, session: ClientSession) -> None: """Set Client Session to improve performance by reusing session. Args: session (aiohttp.ClientSession): The client session which should be closed manually or within context manager. """ self._fetcher.set_session(session) async def _fetch_key(self, key_id=None): """Request the JWKS. Args: key_id (str): The key's key id.""" return await self._fetcher.get_key(key_id) async def verify_signature(self, token) -> dict[str, Any]: """Verifies the signature of the given JSON web token. Args: token (str): The JWT to get its signature verified. Raises: TokenValidationError: if the token cannot be decoded, the algorithm is invalid or the token's signature doesn't match the calculated one. """ kid = self._get_kid(token) secret_or_certificate = await self._fetch_key(key_id=kid) return self._decode_jwt(token, secret_or_certificate) class AsyncJwksFetcher(JwksFetcher): """Class that async fetches and holds a JSON web key set. This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it. Args: jwks_url (str): The url where the JWK set is located. cache_ttl (str, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds. """ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._async_client = AsyncRestClient(None) def set_session(self, session: ClientSession) -> None: """Set Client Session to improve performance by reusing session. Args: session (aiohttp.ClientSession): The client session which should be closed manually or within context manager. """ self._async_client.set_session(session) async def _fetch_jwks(self, force: bool = False) -> dict[str, RSAPublicKey]: """Attempts to obtain the JWK set from the cache, as long as it's still valid. When not, it will perform a network request to the jwks_url to obtain a fresh result and update the cache value with it. Args: force (bool, optional): whether to ignore the cache and force a network request or not. Defaults to False. """ if force or self._cache_expired(): self._cache_value = {} try: jwks = await self._async_client.get(self._jwks_url) self._cache_jwks(jwks) except: # noqa: E722 return self._cache_value return self._cache_value self._cache_is_fresh = False return self._cache_value async def get_key(self, key_id: str) -> RSAPublicKey: """Obtains the JWK associated with the given key id. Args: key_id (str): The id of the key to fetch. Returns: the JWK associated with the given key id. Raises: TokenValidationError: when a key with that id cannot be found """ keys = await self._fetch_jwks() if keys and key_id in keys: return keys[key_id] if not self._cache_is_fresh: keys = await self._fetch_jwks(force=True) if keys and key_id in keys: return keys[key_id] raise TokenValidationError(f'RSA Public Key with ID "{key_id}" was not found.') class AsyncTokenVerifier(TokenVerifier): """Class that verifies ID tokens following the steps defined in the OpenID Connect spec. An OpenID Connect ID token is not meant to be consumed until it's verified. Args: signature_verifier (AsyncAsymmetricSignatureVerifier): The instance that knows how to verify the signature. issuer (str): The expected issuer claim value. audience (str): The expected audience claim value. leeway (int, optional): The clock skew to accept when verifying date related claims in seconds. Defaults to 60 seconds. """ def __init__( self, signature_verifier: AsyncAsymmetricSignatureVerifier, issuer: str, audience: str, leeway: int = 0, ) -> None: if not signature_verifier or not isinstance( signature_verifier, AsyncAsymmetricSignatureVerifier ): raise TypeError( "signature_verifier must be an instance of AsyncAsymmetricSignatureVerifier." ) self.iss = issuer self.aud = audience self.leeway = leeway self._sv = signature_verifier self._clock = None # legacy testing requirement def set_session(self, session: ClientSession) -> None: """Set Client Session to improve performance by reusing session. Args: session (aiohttp.ClientSession): The client session which should be closed manually or within context manager. """ self._sv.set_session(session) async def verify( self, token: str, nonce: str | None = None, max_age: int | None = None, organization: str | None = None, ) -> dict[str, Any]: """Attempts to verify the given ID token, following the steps defined in the OpenID Connect spec. Args: token (str): The JWT to verify. nonce (str, optional): The nonce value sent during authentication. max_age (int, optional): The max_age value sent during authentication. organization (str, optional): The expected organization ID (org_id) or organization name (org_name) claim value. This should be specified when logging in to an organization. Returns: the decoded payload from the token Raises: TokenValidationError: when the token cannot be decoded, the token signing algorithm is not the expected one, the token signature is invalid or the token has a claim missing or with unexpected value. """ # Verify token presence if not token or not isinstance(token, str): raise TokenValidationError("ID token is required but missing.") # Verify algorithm and signature payload = await self._sv.verify_signature(token) # Verify claims self._verify_payload(payload, nonce, max_age, organization) return payload auth0-auth0-python-0f6dbea/auth0/authentication/back_channel_login.py000066400000000000000000000050151506260514000260340ustar00rootroot00000000000000from typing import Any, Optional, Union, List, Dict from .base import AuthenticationBase import json class BackChannelLogin(AuthenticationBase): """Back-Channel Login endpoint""" def back_channel_login( self, binding_message: str, login_hint: str, scope: str, authorization_details: Optional[Union[str, List[Dict]]] = None, requested_expiry: Optional[int] = None, **kwargs ) -> Any: """Send a Back-Channel Login. Args: binding_message (str): Human-readable string displayed on both the device calling /bc-authorize and the user’s authentication device to ensure the user is approves the correct request. login_hint (str): JSON string containing user details for authentication in the iss_sub format.Ensure serialization before passing. scope(str): "openid" is a required scope.Multiple scopes are separated with whitespace. authorization_details (str, list of dict, optional): JSON string or a list of dictionaries representing Rich Authorization Requests (RAR) details to include in the CIBA request. requested_expiry (int, optional): Number of seconds the authentication request is valid for. Auth0 defaults to 300 seconds (5 mins) if not provided. **kwargs: Other fields to send along with the request. Returns: auth_req_id, expires_in, interval """ data = { "client_id": self.client_id, "binding_message": binding_message, "login_hint": login_hint, "scope": scope, **kwargs, } if authorization_details is not None: if isinstance(authorization_details, str): data["authorization_details"] = authorization_details elif isinstance(authorization_details, list): data["authorization_details"] = json.dumps(authorization_details) if requested_expiry is not None: if not isinstance(requested_expiry, int) or requested_expiry <= 0: raise ValueError("requested_expiry must be a positive integer") data["requested_expiry"] = str(requested_expiry) data.update(kwargs) return self.authenticated_post( f"{self.protocol}://{self.domain}/bc-authorize", data = data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) auth0-auth0-python-0f6dbea/auth0/authentication/base.py000066400000000000000000000056161506260514000231750ustar00rootroot00000000000000from __future__ import annotations from typing import Any from auth0.rest import RestClient, RestClientOptions from auth0.types import RequestData, TimeoutType from .client_authentication import add_client_authentication UNKNOWN_ERROR = "a0.sdk.internal.unknown" class AuthenticationBase: """Base authentication object providing simple REST methods. Args: domain (str): The domain of your Auth0 tenant client_id (str): Your application's client ID client_secret (str, optional): Your application's client secret client_assertion_signing_key (str, optional): Private key used to sign the client assertion JWT. client_assertion_signing_alg (str, optional): Algorithm used to sign the client assertion JWT (defaults to 'RS256'). telemetry (bool, optional): Enable or disable telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Useful for testing. (defaults to 'https') """ def __init__( self, domain: str, client_id: str, client_secret: str | None = None, client_assertion_signing_key: str | None = None, client_assertion_signing_alg: str | None = None, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", ) -> None: self.domain = domain self.client_id = client_id self.client_secret = client_secret self.client_assertion_signing_key = client_assertion_signing_key self.client_assertion_signing_alg = client_assertion_signing_alg self.protocol = protocol self.client = RestClient( None, options=RestClientOptions(telemetry=telemetry, timeout=timeout, retries=0), ) def _add_client_authentication(self, payload: dict[str, Any]) -> dict[str, Any]: return add_client_authentication( payload, self.domain, self.client_id, self.client_secret, self.client_assertion_signing_key, self.client_assertion_signing_alg, ) def post( self, url: str, data: RequestData | None = None, headers: dict[str, str] | None = None, ) -> Any: return self.client.post(url, data=data, headers=headers) def authenticated_post( self, url: str, data: dict[str, Any], headers: dict[str, str] | None = None, ) -> Any: return self.client.post( url, data=self._add_client_authentication(data), headers=headers ) def get( self, url: str, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, ) -> Any: return self.client.get(url, params, headers) auth0-auth0-python-0f6dbea/auth0/authentication/client_authentication.py000066400000000000000000000051741506260514000266370ustar00rootroot00000000000000from __future__ import annotations import datetime import uuid from typing import Any import jwt def create_client_assertion_jwt( domain: str, client_id: str, client_assertion_signing_key: str, client_assertion_signing_alg: str | None, ) -> str: """Creates a JWT for the client_assertion field. Args: domain (str): The domain of your Auth0 tenant client_id (str): Your application's client ID client_assertion_signing_key (str): Private key used to sign the client assertion JWT client_assertion_signing_alg (str, optional): Algorithm used to sign the client assertion JWT (defaults to 'RS256') Returns: A JWT signed with the `client_assertion_signing_key`. """ client_assertion_signing_alg = client_assertion_signing_alg or "RS256" now = datetime.datetime.utcnow() return jwt.encode( { "iss": client_id, "sub": client_id, "aud": f"https://{domain}/", "iat": now, "exp": now + datetime.timedelta(seconds=180), "jti": str(uuid.uuid4()), }, client_assertion_signing_key, client_assertion_signing_alg, ) def add_client_authentication( payload: dict[str, Any], domain: str, client_id: str, client_secret: str | None, client_assertion_signing_key: str | None, client_assertion_signing_alg: str | None, ) -> dict[str, Any]: """Adds the client_assertion or client_secret fields to authenticate a payload. Args: payload (dict): The POST payload that needs additional fields to be authenticated. domain (str): The domain of your Auth0 tenant client_id (str): Your application's client ID client_secret (str, optional): Your application's client secret client_assertion_signing_key (str, optional): Private key used to sign the client assertion JWT client_assertion_signing_alg (str, optional): Algorithm used to sign the client assertion JWT (defaults to 'RS256') Returns: A copy of the payload with client authentication fields added. """ authenticated_payload = payload.copy() if client_assertion_signing_key: authenticated_payload["client_assertion"] = create_client_assertion_jwt( domain, client_id, client_assertion_signing_key, client_assertion_signing_alg, ) authenticated_payload[ "client_assertion_type" ] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" elif client_secret: authenticated_payload["client_secret"] = client_secret return authenticated_payload auth0-auth0-python-0f6dbea/auth0/authentication/database.py000066400000000000000000000064041506260514000240230ustar00rootroot00000000000000from __future__ import annotations from typing import Any from .base import AuthenticationBase class Database(AuthenticationBase): """Database & Active Directory / LDAP Authentication. Args: domain (str): Your auth0 domain (e.g: username.auth0.com) """ def signup( self, email: str, password: str, connection: str, username: str | None = None, user_metadata: dict[str, Any] | None = None, given_name: str | None = None, family_name: str | None = None, name: str | None = None, nickname: str | None = None, picture: str | None = None, ) -> dict[str, Any]: """Signup using email and password. Args: email (str): The user's email address. password (str): The user's desired password. connection (str): The name of the database connection where this user should be created. username (str, optional): The user's username, if required by the database connection. user_metadata (dict, optional): Additional key-value information to store for the user. Some limitations apply, see: https://auth0.com/docs/metadata#metadata-restrictions given_name (str, optional): The user's given name(s). family_name (str, optional): The user's family name(s). name (str, optional): The user's full name. nickname (str, optional): The user's nickname. picture (str, optional): A URI pointing to the user's picture. See: https://auth0.com/docs/api/authentication#signup """ body: dict[str, Any] = { "client_id": self.client_id, "email": email, "password": password, "connection": connection, } if username: body.update({"username": username}) if user_metadata: body.update({"user_metadata": user_metadata}) if given_name: body.update({"given_name": given_name}) if family_name: body.update({"family_name": family_name}) if name: body.update({"name": name}) if nickname: body.update({"nickname": nickname}) if picture: body.update({"picture": picture}) data: dict[str, Any] = self.post( f"{self.protocol}://{self.domain}/dbconnections/signup", data=body ) return data def change_password( self, email: str, connection: str, password: str | None = None, organization: str | None = None, ) -> str: """Asks to change a password for a given user. email (str): The user's email address. connection (str): The name of the database connection where this user should be created. organization (str, optional): The id of the Organization associated with the user. """ body = { "client_id": self.client_id, "email": email, "connection": connection, } if organization: body["organization"] = organization data: str = self.post( f"{self.protocol}://{self.domain}/dbconnections/change_password", data=body, ) return data auth0-auth0-python-0f6dbea/auth0/authentication/delegated.py000066400000000000000000000022461506260514000241750ustar00rootroot00000000000000from __future__ import annotations from typing import Any from .base import AuthenticationBase class Delegated(AuthenticationBase): """Delegated authentication endpoints. Args: domain (str): Your auth0 domain (e.g: username.auth0.com) """ def get_token( self, target: str, api_type: str, grant_type: str, id_token: str | None = None, refresh_token: str | None = None, scope: str = "openid", ) -> Any: """Obtain a delegation token.""" if id_token and refresh_token: raise ValueError("Only one of id_token or refresh_token can be None") data = { "client_id": self.client_id, "grant_type": grant_type, "target": target, "scope": scope, "api_type": api_type, } if id_token: data.update({"id_token": id_token}) elif refresh_token: data.update({"refresh_token": refresh_token}) else: raise ValueError("Either id_token or refresh_token must have a value") return self.post(f"{self.protocol}://{self.domain}/delegation", data=data) auth0-auth0-python-0f6dbea/auth0/authentication/enterprise.py000066400000000000000000000012641506260514000244360ustar00rootroot00000000000000from typing import Any from .base import AuthenticationBase class Enterprise(AuthenticationBase): """Enterprise endpoints. Args: domain (str): Your auth0 domain (e.g: my-domain.us.auth0.com) """ def saml_metadata(self) -> Any: """Get SAML2.0 Metadata.""" return self.get( url="{}://{}/samlp/metadata/{}".format( self.protocol, self.domain, self.client_id ) ) def wsfed_metadata(self) -> Any: """Returns the WS-Federation Metadata.""" url = "{}://{}/wsfed/FederationMetadata/2007-06/FederationMetadata.xml" return self.get(url=url.format(self.protocol, self.domain)) auth0-auth0-python-0f6dbea/auth0/authentication/get_token.py000066400000000000000000000256711506260514000242450ustar00rootroot00000000000000from __future__ import annotations from typing import Any from .base import AuthenticationBase class GetToken(AuthenticationBase): """/oauth/token related endpoints Args: domain (str): Your auth0 domain (e.g: username.auth0.com) """ def authorization_code( self, code: str, redirect_uri: str | None, grant_type: str = "authorization_code", ) -> Any: """Authorization code grant This is the OAuth 2.0 grant that regular web apps utilize in order to access an API. Use this endpoint to exchange an Authorization Code for a Token. Args: code (str): The Authorization Code received from the /authorize Calls redirect_uri (str, optional): This is required only if it was set at the GET /authorize endpoint. The values must match grant_type (str): Denotes the flow you're using. For authorization code use authorization_code Returns: access_token, id_token """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "code": code, "grant_type": grant_type, "redirect_uri": redirect_uri, }, ) def authorization_code_pkce( self, code_verifier: str, code: str, redirect_uri: str | None, grant_type: str = "authorization_code", ) -> Any: """Authorization code pkce grant This is the OAuth 2.0 grant that mobile apps utilize in order to access an API. Use this endpoint to exchange an Authorization Code for a Token. Args: code_verifier (str): Cryptographically random key that was used to generate the code_challenge passed to /authorize. code (str): The Authorization Code received from the /authorize Calls redirect_uri (str, optional): This is required only if it was set at the GET /authorize endpoint. The values must match grant_type (str): Denotes the flow you're using. For authorization code pkce use authorization_code Returns: access_token, id_token """ return self.post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "code_verifier": code_verifier, "code": code, "grant_type": grant_type, "redirect_uri": redirect_uri, }, ) def client_credentials( self, audience: str, grant_type: str = "client_credentials", organization: str | None = None, ) -> Any: """Client credentials grant This is the OAuth 2.0 grant that server processes utilize in order to access an API. Use this endpoint to directly request an access_token by using the Application Credentials (a Client Id and a Client Secret). Args: audience (str): The unique identifier of the target API you want to access. grant_type (str, optional): Denotes the flow you're using. For client credentials use "client_credentials" organization (str, optional): Optional Organization name or ID. When included, the access token returned will include the org_id and org_name claims Returns: access_token """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "audience": audience, "grant_type": grant_type, "organization": organization, }, ) def login( self, username: str, password: str, scope: str | None = None, realm: str | None = None, audience: str | None = None, grant_type: str = "http://auth0.com/oauth/grant-type/password-realm", forwarded_for: str | None = None, ) -> Any: """Calls /oauth/token endpoint with password-realm grant type This is the OAuth 2.0 grant that highly trusted apps utilize in order to access an API. In this flow the end-user is asked to fill in credentials (username/password) typically using an interactive form in the user-agent (browser). This information is later on sent to the client and Auth0. It is therefore imperative that the client is absolutely trusted with this information. Args: username (str): Resource owner's identifier password (str): resource owner's Secret scope(str, optional): String value of the different scopes the client is asking for. Multiple scopes are separated with whitespace. realm (str, optional): String value of the realm the user belongs. Set this if you want to add realm support at this grant. audience (str, optional): The unique identifier of the target API you want to access. grant_type (str, optional): Denotes the flow you're using. For password realm use http://auth0.com/oauth/grant-type/password-realm forwarded_for (str, optional): End-user IP as a string value. Set this if you want brute-force protection to work in server-side scenarios. See https://auth0.com/docs/get-started/authentication-and-authorization-flow/avoid-common-issues-with-resource-owner-password-flow-and-attack-protection Returns: access_token, id_token """ headers = None if forwarded_for: headers = {"auth0-forwarded-for": forwarded_for} return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "username": username, "password": password, "realm": realm, "scope": scope, "audience": audience, "grant_type": grant_type, }, headers=headers, ) def refresh_token( self, refresh_token: str, scope: str = "", grant_type: str = "refresh_token", ) -> Any: """Calls /oauth/token endpoint with refresh token grant type Use this endpoint to refresh an access token, using the refresh token you got during authorization. Args: refresh_token (str): The refresh token returned from the initial token request. scope (str): Use this to limit the scopes of the new access token. Multiple scopes are separated with whitespace. grant_type (str): Denotes the flow you're using. For refresh token use refresh_token Returns: access_token, id_token """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "refresh_token": refresh_token, "scope": scope, "grant_type": grant_type, }, ) def passwordless_login( self, username: str, otp: str, realm: str, scope: str, audience: str ) -> Any: """Calls /oauth/token endpoint with http://auth0.com/oauth/grant-type/passwordless/otp grant type Once the verification code was received, login the user using this endpoint with their phone number/email and verification code. Args: username (str): The user's phone number or email address. otp (str): the user's verification code. realm (str): use 'sms' or 'email'. Should be the same as the one used to start the passwordless flow. scope(str): String value of the different scopes the client is asking for. Multiple scopes are separated with whitespace. audience (str): The unique identifier of the target API you want to access. Returns: access_token, id_token """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "username": username, "otp": otp, "realm": realm, "scope": scope, "audience": audience, "grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp", }, ) def backchannel_login( self, auth_req_id: str, grant_type: str = "urn:openid:params:grant-type:ciba", ) -> Any: """Calls /oauth/token endpoint with "urn:openid:params:grant-type:ciba" grant type Args: auth_req_id (str): The id received from /bc-authorize grant_type (str): Denotes the flow you're using.For Back Channel login use urn:openid:params:grant-type:ciba Returns: access_token, id_token, refresh_token, token_type, expires_in, scope and authorization_details """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data={ "client_id": self.client_id, "auth_req_id": auth_req_id, "grant_type": grant_type, }, ) def access_token_for_connection( self, subject_token_type: str, subject_token: str, requested_token_type: str, connection: str | None = None, grant_type: str = "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", login_hint: str = None ) -> Any: """Calls /oauth/token endpoint with federated-connection-access-token grant type Args: subject_token_type (str): String containing the type of token. subject_token (str): String containing the value of subject_token_type. requested_token_type (str): String containing the type of requested token. connection (str, optional): Denotes the name of a social identity provider configured to your application login_hint (str, optional): A hint to the OpenID Provider regarding the end-user for whom authentication is being requested Returns: access_token, scope, issued_token_type, token_type, expires_in """ data = { "client_id": self.client_id, "grant_type": grant_type, "subject_token_type": subject_token_type, "subject_token": subject_token, "requested_token_type": requested_token_type, "connection": connection, } if login_hint: data["login_hint"] = login_hint return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/token", data=data, )auth0-auth0-python-0f6dbea/auth0/authentication/passwordless.py000066400000000000000000000045351506260514000250130ustar00rootroot00000000000000from __future__ import annotations from typing import Any from .base import AuthenticationBase class Passwordless(AuthenticationBase): """Passwordless connections endpoints. Args: domain (str): Your auth0 domain (e.g: my-domain.us.auth0.com) """ def email( self, email: str, send: str = "link", auth_params: dict[str, str] | None = None ) -> Any: """Start flow sending an email. Given the user email address, it will send an email with: - A link (default, send:"link"). You can then authenticate with this user opening the link and he will be automatically logged in to the application. Optionally, you can append/override parameters to the link (like scope, redirect_uri, protocol, response_type, etc.) using auth_params dict. - A verification code (send:"code"). You can then authenticate with this user using email as username and code as password. Complete the authentication using the get_token.passwordless_login method. Args: email (str): Email address. send (str, optional): Can be: 'link' or 'code'. Defaults to 'link'. auth_params (dict, optional): Parameters to append or override. """ data: dict[str, Any] = { "client_id": self.client_id, "connection": "email", "email": email, "send": send, } if auth_params: data.update({"authParams": auth_params}) return self.authenticated_post( f"{self.protocol}://{self.domain}/passwordless/start", data=data ) def sms(self, phone_number: str) -> Any: """Start flow sending an SMS message. Given the user phone number, it will send an SMS with a verification code. You can then authenticate with this user using phone number as username and code as password. Complete the authentication using the get_token.passwordless_login method. Args: phone_number (str): Phone number. """ data = { "client_id": self.client_id, "connection": "sms", "phone_number": phone_number, } return self.authenticated_post( f"{self.protocol}://{self.domain}/passwordless/start", data=data ) auth0-auth0-python-0f6dbea/auth0/authentication/pushed_authorization_requests.py000066400000000000000000000027201506260514000304570ustar00rootroot00000000000000from typing import Any from .base import AuthenticationBase class PushedAuthorizationRequests(AuthenticationBase): """Pushed Authorization Request (PAR) endpoint""" def pushed_authorization_request( self, response_type: str, redirect_uri: str, **kwargs ) -> Any: """Send a Pushed Authorization Request (PAR). Args: response_type (str): Indicates to Auth0 which OAuth 2.0 flow you want to perform. redirect_uri (str): The URL to which Auth0 will redirect the browser after authorization has been granted by the user. **kwargs: Other fields to send along with the PAR. For RAR requests, authorization_details parameter should be added in a proper format. See:https://datatracker.ietf.org/doc/html/rfc9396 For JAR requests, requests parameter should be send with the JWT as the value. See: https://datatracker.ietf.org/doc/html/rfc9126#name-the-request-request-paramet See: https://www.rfc-editor.org/rfc/rfc9126.html """ return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/par", data={ "client_id":self.client_id, "client_secret":self.client_secret, "response_type": response_type, "redirect_uri": redirect_uri, **kwargs, }, headers={"Content-Type": "application/x-www-form-urlencoded"}, )auth0-auth0-python-0f6dbea/auth0/authentication/revoke_token.py000066400000000000000000000017421506260514000247520ustar00rootroot00000000000000from typing import Any from .base import AuthenticationBase class RevokeToken(AuthenticationBase): """Revoke Refresh Token endpoint Args: domain (str): Your auth0 domain (e.g: my-domain.us.auth0.com) """ def revoke_refresh_token(self, token: str) -> Any: """Revokes a Refresh Token if it has been compromised Each revocation request invalidates not only the specific token, but all other tokens based on the same authorization grant. This means that all Refresh Tokens that have been issued for the same user, application, and audience will be revoked. Args: token (str): The Refresh Token you want to revoke See: https://auth0.com/docs/api/authentication#refresh-token """ body = { "client_id": self.client_id, "token": token, } return self.authenticated_post( f"{self.protocol}://{self.domain}/oauth/revoke", data=body ) auth0-auth0-python-0f6dbea/auth0/authentication/social.py000066400000000000000000000022411506260514000235240ustar00rootroot00000000000000from typing import Any from .base import AuthenticationBase class Social(AuthenticationBase): """Social provider's endpoints. Args: domain (str): Your auth0 domain (e.g: my-domain.us.auth0.com) """ def login(self, access_token: str, connection: str, scope: str = "openid") -> Any: """Login using a social provider's access token Given the social provider's access_token and the connection specified, it will do the authentication on the provider and return a dict with the access_token and id_token. Currently, this endpoint only works for Facebook, Google, Twitter and Weibo. Args: access_token (str): social provider's access_token. connection (str): connection type (e.g: 'facebook') Returns: A dict with 'access_token' and 'id_token' keys. """ return self.post( f"{self.protocol}://{self.domain}/oauth/access_token", data={ "client_id": self.client_id, "access_token": access_token, "connection": connection, "scope": scope, }, ) auth0-auth0-python-0f6dbea/auth0/authentication/token_verifier.py000066400000000000000000000413721506260514000252750ustar00rootroot00000000000000"""Token Verifier module""" from __future__ import annotations import json import time from typing import TYPE_CHECKING, Any, ClassVar import jwt import requests from auth0.exceptions import TokenValidationError if TYPE_CHECKING: from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey class SignatureVerifier: """Abstract class that will verify a given JSON web token's signature using the key fetched internally given its key id. Args: algorithm (str): The expected signing algorithm (e.g. RS256). """ DISABLE_JWT_CHECKS: ClassVar[dict[str, bool]] = { "verify_signature": True, "verify_exp": False, "verify_nbf": False, "verify_iat": False, "verify_aud": False, "verify_iss": False, "require_exp": False, "require_iat": False, "require_nbf": False, } def __init__(self, algorithm: str) -> None: if not algorithm or type(algorithm) != str: raise ValueError("algorithm must be specified.") self._algorithm = algorithm def _fetch_key(self, key_id: str) -> str | RSAPublicKey: """Obtains the key associated to the given key id. Must be implemented by subclasses. Args: key_id (str): The id of the key to fetch. Returns: the key to use for verifying a cryptographic signature """ raise NotImplementedError def _get_kid(self, token: str) -> str | None: """Gets the key id from the kid claim of the header of the token Args: token (str): The JWT to get the header from. Raises: TokenValidationError: if the token cannot be decoded, the algorithm is invalid or the token's signature doesn't match the calculated one. Returns: the key id or None """ try: header = jwt.get_unverified_header(token) except jwt.exceptions.DecodeError: raise TokenValidationError("token could not be decoded.") alg = header.get("alg", None) if alg != self._algorithm: raise TokenValidationError( 'Signature algorithm of "{}" is not supported. Expected the token ' 'to be signed with "{}"'.format(alg, self._algorithm) ) return header.get("kid", None) def _decode_jwt(self, token: str, secret_or_certificate: str) -> dict[str, Any]: """Verifies and decodes the given JSON web token with the given public key or shared secret. Args: token (str): The JWT to get its signature verified. secret_or_certificate (str): The public key or shared secret. Raises: TokenValidationError: if the token cannot be decoded, the algorithm is invalid or the token's signature doesn't match the calculated one. """ try: decoded = jwt.decode( jwt=token, key=secret_or_certificate, algorithms=[self._algorithm], options=self.DISABLE_JWT_CHECKS, ) except jwt.exceptions.InvalidSignatureError: raise TokenValidationError("Invalid token signature.") return decoded def verify_signature(self, token: str) -> dict[str, Any]: """Verifies the signature of the given JSON web token. Args: token (str): The JWT to get its signature verified. Raises: TokenValidationError: if the token cannot be decoded, the algorithm is invalid or the token's signature doesn't match the calculated one. """ kid = self._get_kid(token) if kid is None: kid = "" secret_or_certificate = self._fetch_key(key_id=kid) return self._decode_jwt(token, secret_or_certificate) # type: ignore[arg-type] class SymmetricSignatureVerifier(SignatureVerifier): """Verifier for HMAC signatures, which rely on shared secrets. Args: shared_secret (str): The shared secret used to decode the token. algorithm (str, optional): The expected signing algorithm. Defaults to "HS256". """ def __init__(self, shared_secret: str, algorithm: str = "HS256") -> None: super().__init__(algorithm) self._shared_secret = shared_secret def _fetch_key(self, key_id: str = "") -> str: return self._shared_secret class JwksFetcher: """Class that fetches and holds a JSON web key set. This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it. Args: jwks_url (str): The url where the JWK set is located. cache_ttl (str, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds. """ CACHE_TTL: ClassVar[int] = 600 # 10 min cache lifetime def __init__(self, jwks_url: str, cache_ttl: int = CACHE_TTL) -> None: self._jwks_url = jwks_url self._init_cache(cache_ttl) def _init_cache(self, cache_ttl: int) -> None: self._cache_value: dict[str, RSAPublicKey] = {} self._cache_date = 0.0 self._cache_ttl = cache_ttl self._cache_is_fresh = False def _cache_expired(self) -> bool: """Checks if the cache is expired Returns: True if it should use the cache. """ return self._cache_date + self._cache_ttl < time.time() def _cache_jwks(self, jwks: dict[str, Any]) -> None: """Cache the response of the JWKS request Args: jwks (dict): The JWKS """ self._cache_value = self._parse_jwks(jwks) self._cache_is_fresh = True self._cache_date = time.time() def _fetch_jwks(self, force: bool = False) -> dict[str, RSAPublicKey]: """Attempts to obtain the JWK set from the cache, as long as it's still valid. When not, it will perform a network request to the jwks_url to obtain a fresh result and update the cache value with it. Args: force (bool, optional): whether to ignore the cache and force a network request or not. Defaults to False. """ if force or self._cache_expired(): self._cache_value = {} response = requests.get(self._jwks_url) if response.ok: jwks: dict[str, Any] = response.json() self._cache_jwks(jwks) return self._cache_value self._cache_is_fresh = False return self._cache_value @staticmethod def _parse_jwks(jwks: dict[str, Any]) -> dict[str, RSAPublicKey]: """ Converts a JWK string representation into a binary certificate in PEM format. """ keys: dict[str, RSAPublicKey] = {} for key in jwks["keys"]: # noinspection PyUnresolvedReferences # requirement already includes cryptography -> pyjwt[crypto] rsa_key: RSAPublicKey = jwt.algorithms.RSAAlgorithm.from_jwk( json.dumps(key) ) keys[key["kid"]] = rsa_key return keys def get_key(self, key_id: str) -> RSAPublicKey: """Obtains the JWK associated with the given key id. Args: key_id (str): The id of the key to fetch. Returns: the JWK associated with the given key id. Raises: TokenValidationError: when a key with that id cannot be found """ keys = self._fetch_jwks() if keys and key_id in keys: return keys[key_id] if not self._cache_is_fresh: keys = self._fetch_jwks(force=True) if keys and key_id in keys: return keys[key_id] raise TokenValidationError(f'RSA Public Key with ID "{key_id}" was not found.') class AsymmetricSignatureVerifier(SignatureVerifier): """Verifier for RSA signatures, which rely on public key certificates. Args: jwks_url (str): The url where the JWK set is located. algorithm (str, optional): The expected signing algorithm. Defaults to "RS256". cache_ttl (int, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds. """ def __init__( self, jwks_url: str, algorithm: str = "RS256", cache_ttl: int = JwksFetcher.CACHE_TTL, ) -> None: super().__init__(algorithm) self._fetcher = JwksFetcher(jwks_url, cache_ttl) def _fetch_key(self, key_id: str) -> RSAPublicKey: return self._fetcher.get_key(key_id) class TokenVerifier: """Class that verifies ID tokens following the steps defined in the OpenID Connect spec. An OpenID Connect ID token is not meant to be consumed until it's verified. Args: signature_verifier (SignatureVerifier): The instance that knows how to verify the signature. issuer (str): The expected issuer claim value. audience (str): The expected audience claim value. leeway (int, optional): The clock skew to accept when verifying date related claims in seconds. Defaults to 60 seconds. """ def __init__( self, signature_verifier: SignatureVerifier, issuer: str, audience: str, leeway: int = 0, ) -> None: if not signature_verifier or not isinstance( signature_verifier, SignatureVerifier ): raise TypeError( "signature_verifier must be an instance of SignatureVerifier." ) self.iss = issuer self.aud = audience self.leeway = leeway self._sv = signature_verifier self._clock = None # visible for testing def verify( self, token: str, nonce: str | None = None, max_age: int | None = None, organization: str | None = None, ) -> dict[str, Any]: """Attempts to verify the given ID token, following the steps defined in the OpenID Connect spec. Args: token (str): The JWT to verify. nonce (str, optional): The nonce value sent during authentication. max_age (int, optional): The max_age value sent during authentication. organization (str, optional): The expected organization ID (org_id) or organization name (org_name) claim value. This should be specified when logging in to an organization. Returns: the decoded payload from the token Raises: TokenValidationError: when the token cannot be decoded, the token signing algorithm is not the expected one, the token signature is invalid or the token has a claim missing or with unexpected value. """ # Verify token presence if not token or not isinstance(token, str): raise TokenValidationError("ID token is required but missing.") # Verify algorithm and signature payload = self._sv.verify_signature(token) # Verify claims self._verify_payload(payload, nonce, max_age, organization) return payload def _verify_payload( self, payload: dict[str, Any], nonce: str | None = None, max_age: int | None = None, organization: str | None = None, ) -> None: # Issuer if "iss" not in payload or not isinstance(payload["iss"], str): raise TokenValidationError( "Issuer (iss) claim must be a string present in the ID token" ) if payload["iss"] != self.iss: raise TokenValidationError( 'Issuer (iss) claim mismatch in the ID token; expected "{}", ' 'found "{}"'.format(self.iss, payload["iss"]) ) # Subject if "sub" not in payload or not isinstance(payload["sub"], str): raise TokenValidationError( "Subject (sub) claim must be a string present in the ID token" ) # Audience if "aud" not in payload or not isinstance(payload["aud"], (str, list)): raise TokenValidationError( "Audience (aud) claim must be a string or array of strings present in" " the ID token" ) if isinstance(payload["aud"], list) and self.aud not in payload["aud"]: payload_audiences = ", ".join(payload["aud"]) raise TokenValidationError( 'Audience (aud) claim mismatch in the ID token; expected "{}" but was ' 'not one of "{}"'.format(self.aud, payload_audiences) ) elif isinstance(payload["aud"], str) and payload["aud"] != self.aud: raise TokenValidationError( 'Audience (aud) claim mismatch in the ID token; expected "{}" ' 'but found "{}"'.format(self.aud, payload["aud"]) ) # --Time validation (epoch)-- now = self._clock or time.time() leeway = self.leeway # Expires at if "exp" not in payload or not isinstance(payload["exp"], int): raise TokenValidationError( "Expiration Time (exp) claim must be a number present in the ID token" ) exp_time = payload["exp"] + leeway if now > exp_time: raise TokenValidationError( "Expiration Time (exp) claim error in the ID token; current time ({})" " is after expiration time ({})".format(now, exp_time) ) # Issued at if "iat" not in payload or not isinstance(payload["iat"], int): raise TokenValidationError( "Issued At (iat) claim must be a number present in the ID token" ) # Nonce if nonce: if "nonce" not in payload or not isinstance(payload["nonce"], str): raise TokenValidationError( "Nonce (nonce) claim must be a string present in the ID token" ) if payload["nonce"] != nonce: raise TokenValidationError( 'Nonce (nonce) claim mismatch in the ID token; expected "{}", ' 'found "{}"'.format(nonce, payload["nonce"]) ) # Organization if organization: if organization.startswith("org_"): if "org_id" not in payload or not isinstance(payload["org_id"], str): raise TokenValidationError( "Organization (org_id) claim must be a string present in the ID" " token" ) if payload["org_id"] != organization: raise TokenValidationError( "Organization (org_id) claim mismatch in the ID token; expected" ' "{}", found "{}"'.format(organization, payload["org_id"]) ) else: if "org_name" not in payload or not isinstance( payload["org_name"], str ): raise TokenValidationError( "Organization (org_name) claim must be a string present in the ID" " token" ) if payload["org_name"] != organization.lower(): raise TokenValidationError( "Organization (org_name) claim mismatch in the ID token; expected" ' "{}", found "{}"'.format(organization, payload["org_name"]) ) # Authorized party if isinstance(payload["aud"], list) and len(payload["aud"]) > 1: if "azp" not in payload or not isinstance(payload["azp"], str): raise TokenValidationError( "Authorized Party (azp) claim must be a string present in the ID" " token when Audience (aud) claim has multiple values" ) if payload["azp"] != self.aud: raise TokenValidationError( "Authorized Party (azp) claim mismatch in the ID token; expected" ' "{}", found "{}"'.format(self.aud, payload["azp"]) ) # Authentication time if max_age: if "auth_time" not in payload or not isinstance(payload["auth_time"], int): raise TokenValidationError( "Authentication Time (auth_time) claim must be a number present in" " the ID token when Max Age (max_age) is specified" ) auth_valid_until = payload["auth_time"] + max_age + leeway if now > auth_valid_until: raise TokenValidationError( "Authentication Time (auth_time) claim in the ID token indicates" " that too much time has passed since the last end-user" " authentication. Current time ({}) is after last auth at ({})".format( now, auth_valid_until ) ) auth0-auth0-python-0f6dbea/auth0/authentication/users.py000066400000000000000000000032401506260514000234130ustar00rootroot00000000000000from __future__ import annotations from typing import Any from auth0.rest import RestClient, RestClientOptions from auth0.types import TimeoutType class Users: """Users client. Args: domain (str): The domain of your Auth0 tenant telemetry (bool, optional): Enable or disable telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Useful for testing. (defaults to 'https') """ def __init__( self, domain: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( None, options=RestClientOptions(telemetry=telemetry, timeout=timeout, retries=0), ) """Userinfo related endpoints. Args: domain (str): Your auth0 domain (e.g: username.auth0.com) """ def userinfo(self, access_token: str) -> dict[str, Any]: """Returns the user information based on the Auth0 access token. This endpoint will work only if openid was granted as a scope for the access_token. Args: access_token (str): Auth0 access token (obtained during login). Returns: The user profile. """ data: dict[str, Any] = self.client.get( url=f"{self.protocol}://{self.domain}/userinfo", headers={"Authorization": f"Bearer {access_token}"}, ) return data auth0-auth0-python-0f6dbea/auth0/exceptions.py000066400000000000000000000015331506260514000214170ustar00rootroot00000000000000from __future__ import annotations from typing import Any class Auth0Error(Exception): def __init__( self, status_code: int, error_code: str, message: str, content: Any | None = None, headers: Any | None = None, ) -> None: self.status_code = status_code self.error_code = error_code self.message = message self.content = content self.headers = headers def __str__(self) -> str: return f"{self.status_code}: {self.message}" class RateLimitError(Auth0Error): def __init__(self, error_code: str, message: str, reset_at: int, headers: Any | None = None) -> None: super().__init__(status_code=429, error_code=error_code, message=message, headers=headers) self.reset_at = reset_at class TokenValidationError(Exception): pass auth0-auth0-python-0f6dbea/auth0/management/000077500000000000000000000000001506260514000207765ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/management/__init__.py000066400000000000000000000034771506260514000231220ustar00rootroot00000000000000from ..utils import is_async_available from .actions import Actions from .attack_protection import AttackProtection from .blacklists import Blacklists from .branding import Branding from .client_credentials import ClientCredentials from .client_grants import ClientGrants from .clients import Clients from .connections import Connections from .custom_domains import CustomDomains from .device_credentials import DeviceCredentials from .email_templates import EmailTemplates from .emails import Emails from .grants import Grants from .guardian import Guardian from .hooks import Hooks from .jobs import Jobs from .log_streams import LogStreams from .logs import Logs from .network_acls import NetworkAcls from .organizations import Organizations from .resource_servers import ResourceServers from .roles import Roles from .rules import Rules from .rules_configs import RulesConfigs from .self_service_profiles import SelfServiceProfiles from .stats import Stats from .tenants import Tenants from .tickets import Tickets from .user_blocks import UserBlocks from .users import Users from .users_by_email import UsersByEmail if is_async_available(): from .async_auth0 import AsyncAuth0 as Auth0 else: # pragma: no cover from .auth0 import Auth0 # type: ignore[assignment] __all__ = ( "Auth0", "Actions", "AttackProtection", "Blacklists", "Branding", "ClientCredentials", "ClientGrants", "Clients", "Connections", "CustomDomains", "DeviceCredentials", "EmailTemplates", "Emails", "Grants", "Guardian", "Hooks", "Jobs", "LogStreams", "Logs", "NetworkAcls" "Organizations", "ResourceServers", "Roles", "RulesConfigs", "Rules", "SelfServiceProfiles", "Stats", "Tenants", "Tickets", "UserBlocks", "UsersByEmail", "Users", ) auth0-auth0-python-0f6dbea/auth0/management/actions.py000066400000000000000000000210451506260514000230120ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Actions: """Auth0 Actions endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions, optional): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, *args: str | None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/actions" for p in args: if p is not None: url = f"{url}/{p}" return url def get_actions( self, trigger_id: str | None = None, action_name: str | None = None, deployed: bool | None = None, installed: bool = False, page: int | None = None, per_page: int | None = None, ) -> Any: """Get all actions. Args: trigger_id (str, optional): Filter the results to only actions associated with this trigger ID. action_name (str, optional): Filter the results to only actions with this name. deployed (bool, optional): True to filter the results to only deployed actions. Defaults to False. installed (bool, optional): True to filter the results to only installed actions. Defaults to False. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Actions/get_actions """ deployed_str = str(deployed).lower() if deployed is not None else None params = { "triggerId": trigger_id, "actionName": action_name, "deployed": deployed_str, "installed": str(installed).lower(), "page": page, "per_page": per_page, } return self.client.get(self._url("actions"), params=params) def create_action(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new action. Args: body (dict): Attributes for the new action. See: https://auth0.com/docs/api/management/v2#!/Actions/post_action """ return self.client.post(self._url("actions"), data=body) def update_action(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Updates an action. Args: id (str): the ID of the action. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Actions/patch_action """ return self.client.patch(self._url("actions", id), data=body) def get_action(self, id: str) -> dict[str, Any]: """Retrieves an action by its ID. Args: id (str): Id of action to retrieve. See: https://auth0.com/docs/api/management/v2#!/Actions/get_action """ params = {} return self.client.get(self._url("actions", id), params=params) def delete_action(self, id: str, force: bool = False) -> Any: """Deletes an action and all of its associated versions. Args: id (str): ID of the action to delete. force (bool, optional): True to force action deletion detaching bindings, False otherwise. Defaults to False. See: https://auth0.com/docs/api/management/v2#!/Actions/delete_action """ params = {"force": str(force).lower()} return self.client.delete(self._url("actions", id), params=params) def get_triggers(self) -> dict[str, Any]: """Retrieve the set of triggers currently available within actions. See: https://auth0.com/docs/api/management/v2#!/Actions/get_triggers """ params = {} return self.client.get(self._url("triggers"), params=params) def get_execution(self, id: str) -> dict[str, Any]: """Get information about a specific execution of a trigger. Args: id (str): The ID of the execution to retrieve. See: https://auth0.com/docs/api/management/v2#!/Actions/get_execution """ params = {} return self.client.get(self._url("executions", id), params=params) def get_action_versions( self, id: str, page: int | None = None, per_page: int | None = None ) -> dict[str, Any]: """Get all of an action's versions. Args: id (str): The ID of the action. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Actions/get_action_versions """ params = {"page": page, "per_page": per_page} return self.client.get(self._url("actions", id, "versions"), params=params) def get_trigger_bindings( self, id: str, page: int | None = None, per_page: int | None = None ) -> dict[str, Any]: """Get the actions that are bound to a trigger. Args: id (str): The trigger ID. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Actions/get_bindings """ params = {"page": page, "per_page": per_page} return self.client.get(self._url("triggers", id, "bindings"), params=params) def get_action_version(self, action_id: str, version_id: str) -> dict[str, Any]: """Retrieve a specific version of an action. Args: action_id (str): The ID of the action. version_id (str): The ID of the version to retrieve. See: https://auth0.com/docs/api/management/v2#!/Actions/get_action_version """ params = {} return self.client.get( self._url("actions", action_id, "versions", version_id), params=params ) def deploy_action(self, id: str) -> dict[str, Any]: """Deploy an action. Args: id (str): The ID of the action to deploy. See: https://auth0.com/docs/api/management/v2#!/Actions/post_deploy_action """ return self.client.post(self._url("actions", id, "deploy")) def rollback_action_version( self, action_id: str, version_id: str ) -> dict[str, Any]: """Roll back to a previous version of an action. Args: action_id (str): The ID of the action. version_id (str): The ID of the version. See: https://auth0.com/docs/api/management/v2#!/Actions/post_deploy_draft_version """ return self.client.post( self._url("actions", action_id, "versions", version_id, "deploy"), data={} ) def update_trigger_bindings(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a trigger's bindings. Args: id (str): The ID of the trigger to update. body (dict): Attributes for the updated trigger binding. See: https://auth0.com/docs/api/management/v2#!/Actions/patch_bindings """ return self.client.patch(self._url("triggers", id, "bindings"), data=body) auth0-auth0-python-0f6dbea/auth0/management/async_auth0.py000066400000000000000000000040231506260514000235650ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING import aiohttp from ..asyncify import asyncify from .auth0 import Auth0 if TYPE_CHECKING: from types import TracebackType from auth0.rest import RestClientOptions class AsyncAuth0: """Provides easy access to all endpoint classes Args: domain (str): Your Auth0 domain, for example 'username.auth0.com' token (str): Management API v2 Token rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, rest_options: RestClientOptions | None = None ) -> None: self._services = [] for name, attr in vars(Auth0(domain, token, rest_options=rest_options)).items(): cls = asyncify(attr.__class__) service = cls(domain=domain, token=token, rest_options=rest_options) self._services.append(service) setattr( self, name, service, ) def set_session(self, session: aiohttp.ClientSession) -> None: """Set Client Session to improve performance by reusing session. Args: session (aiohttp.ClientSession): The client session which should be closed manually or within context manager. """ self._session = session for service in self._services: service.set_session(self._session) async def __aenter__(self) -> AsyncAuth0: """Automatically create and set session within context manager.""" self.set_session(aiohttp.ClientSession()) return self async def __aexit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Automatically close session within context manager.""" await self._session.close() auth0-auth0-python-0f6dbea/auth0/management/attack_protection.py000066400000000000000000000102161506260514000250650ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class AttackProtection: """Auth0 attack protection endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, component: str) -> str: return "{}://{}/api/v2/attack-protection/{}".format( self.protocol, self.domain, component ) def get_breached_password_detection(self) -> dict[str, Any]: """Get breached password detection settings. Returns the breached password detection settings. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_breached_password_detection """ url = self._url("breached-password-detection") return self.client.get(url) def update_breached_password_detection( self, body: dict[str, Any] ) -> dict[str, Any]: """Update breached password detection settings. Returns the breached password detection settings. Args: body (dict): breached password detection settings. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_breached_password_detection """ url = self._url("breached-password-detection") return self.client.patch(url, data=body) def get_brute_force_protection(self) -> dict[str, Any]: """Get the brute force configuration. Returns the brute force configuration. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_brute_force_protection """ url = self._url("brute-force-protection") return self.client.get(url) def update_brute_force_protection(self, body: dict[str, Any]) -> dict[str, Any]: """Update the brute force configuration. Returns the brute force configuration. Args: body (dict): updates of the brute force configuration. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_brute_force_protection """ url = self._url("brute-force-protection") return self.client.patch(url, data=body) def get_suspicious_ip_throttling(self) -> dict[str, Any]: """Get the suspicious IP throttling configuration. Returns the suspicious IP throttling configuration. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_suspicious_ip_throttling """ url = self._url("suspicious-ip-throttling") return self.client.get(url) def update_suspicious_ip_throttling(self, body: dict[str, Any]) -> dict[str, Any]: """Update the suspicious IP throttling configuration. Returns the suspicious IP throttling configuration. Args: body (dict): updates of the suspicious IP throttling configuration. See: https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_suspicious_ip_throttling """ url = self._url("suspicious-ip-throttling") return self.client.patch(url, data=body) auth0-auth0-python-0f6dbea/auth0/management/auth0.py000066400000000000000000000104501506260514000223710ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING from .actions import Actions from .attack_protection import AttackProtection from .blacklists import Blacklists from .branding import Branding from .client_credentials import ClientCredentials from .client_grants import ClientGrants from .clients import Clients from .connections import Connections from .custom_domains import CustomDomains from .device_credentials import DeviceCredentials from .email_templates import EmailTemplates from .emails import Emails from .grants import Grants from .guardian import Guardian from .hooks import Hooks from .jobs import Jobs from .log_streams import LogStreams from .logs import Logs from .network_acls import NetworkAcls from .organizations import Organizations from .prompts import Prompts from .resource_servers import ResourceServers from .roles import Roles from .rules import Rules from .rules_configs import RulesConfigs from .self_service_profiles import SelfServiceProfiles from .stats import Stats from .tenants import Tenants from .tickets import Tickets from .user_blocks import UserBlocks from .users import Users from .users_by_email import UsersByEmail if TYPE_CHECKING: from auth0.rest import RestClientOptions class Auth0: """Provides easy access to all endpoint classes Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, rest_options: RestClientOptions | None = None ): self.actions = Actions(domain, token, rest_options=rest_options) self.attack_protection = AttackProtection( domain, token, rest_options=rest_options ) self.blacklists = Blacklists(domain, token, rest_options=rest_options) self.branding = Branding(domain, token, rest_options=rest_options) self.client_credentials = ClientCredentials( domain, token, rest_options=rest_options ) self.client_grants = ClientGrants(domain, token, rest_options=rest_options) self.clients = Clients(domain, token, rest_options=rest_options) self.connections = Connections(domain, token, rest_options=rest_options) self.custom_domains = CustomDomains(domain, token, rest_options=rest_options) self.device_credentials = DeviceCredentials( domain, token, rest_options=rest_options ) self.email_templates = EmailTemplates(domain, token, rest_options=rest_options) self.emails = Emails(domain, token, rest_options=rest_options) self.grants = Grants(domain, token, rest_options=rest_options) self.guardian = Guardian(domain, token, rest_options=rest_options) self.hooks = Hooks(domain, token, rest_options=rest_options) self.jobs = Jobs(domain, token, rest_options=rest_options) self.log_streams = LogStreams(domain, token, rest_options=rest_options) self.logs = Logs(domain, token, rest_options=rest_options) self.network_acls = NetworkAcls(domain, token, rest_options=rest_options) self.organizations = Organizations(domain, token, rest_options=rest_options) self.prompts = Prompts(domain, token, rest_options=rest_options) self.resource_servers = ResourceServers( domain, token, rest_options=rest_options ) self.roles = Roles(domain, token, rest_options=rest_options) self.rules_configs = RulesConfigs(domain, token, rest_options=rest_options) self.rules = Rules(domain, token, rest_options=rest_options) self.self_service_profiles = SelfServiceProfiles( domain, token, rest_options=rest_options ) self.stats = Stats(domain, token, rest_options=rest_options) self.tenants = Tenants(domain, token, rest_options=rest_options) self.tickets = Tickets(domain, token, rest_options=rest_options) self.user_blocks = UserBlocks(domain, token, rest_options=rest_options) self.users_by_email = UsersByEmail(domain, token, rest_options=rest_options) self.users = Users(domain, token, rest_options=rest_options) auth0-auth0-python-0f6dbea/auth0/management/blacklists.py000066400000000000000000000045561506260514000235150ustar00rootroot00000000000000from __future__ import annotations from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Blacklists: """Auth0 blacklists endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.url = f"{protocol}://{domain}/api/v2/blacklists/tokens" self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def get(self, aud: str | None = None) -> list[dict[str, str]]: """Retrieves the jti and aud of all tokens in the blacklist. Args: aud (str, optional): The JWT's aud claim. The client_id of the application for which it was issued. See: https://auth0.com/docs/api/management/v2#!/Blacklists/get_tokens """ params = {"aud": aud} return self.client.get(self.url, params=params) def create(self, jti: str, aud: str | None = None) -> dict[str, str]: """Adds a token to the blacklist. Args: jti (str): the jti of the JWT to blacklist. aud (str, optional): The JWT's aud claim. The client_id of the application for which it was issued. See: https://auth0.com/docs/api/management/v2#!/Blacklists/post_tokens """ body = { "jti": jti, } if aud: body.update({"aud": aud}) return self.client.post(self.url, data=body) auth0-auth0-python-0f6dbea/auth0/management/branding.py000066400000000000000000000121411506260514000231330ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Branding: """Auth0 Branding endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, *args: str) -> str: url = f"{self.protocol}://{self.domain}/api/v2/branding" for p in args: if p is not None: url = f"{url}/{p}" return url def get(self) -> dict[str, Any]: """Retrieve branding settings. Requires "read:branding" scope. See: https://auth0.com/docs/api/management/v2#!/Branding/get_branding """ return self.client.get(self._url()) def update(self, body: dict[str, Any]) -> dict[str, Any]: """Update branding settings. Requires "update:branding" scope. Args: body (dict): Attributes for the updated trigger binding. See: https://auth0.com/docs/api/management/v2#!/Branding/patch_branding """ return self.client.patch(self._url(), data=body) def get_template_universal_login(self) -> dict[str, Any]: """Get template for New Universal Login Experience. Requires "read:branding" scope. See: https://auth0.com/docs/api/management/v2#!/Branding/get_universal_login """ return self.client.get(self._url("templates", "universal-login")) def delete_template_universal_login(self) -> Any: """Delete template for New Universal Login Experience. Requires "delete:branding" scope. See: https://auth0.com/docs/api/management/v2#!/Branding/delete_universal_login """ return self.client.delete(self._url("templates", "universal-login")) def update_template_universal_login(self, body: dict[str, Any]) -> dict[str, Any]: """Update template for New Universal Login Experience. Requires "update:branding" scope. Args: body (str): Complete HTML content to assign to the template. See linked API documentation for example. See: https://auth0.com/docs/api/management/v2#!/Branding/put_universal_login """ return self.client.put( self._url("templates", "universal-login"), data={"template": body}, ) def get_default_branding_theme(self) -> dict[str, Any]: """Retrieve default branding theme. See: https://auth0.com/docs/api/management/v2#!/Branding/get_default_branding_theme """ return self.client.get(self._url("themes", "default")) def get_branding_theme(self, theme_id: str) -> dict[str, Any]: """Retrieve branding theme. Args: theme_id (str): The theme_id to retrieve branding theme for. See: https://auth0.com/docs/api/management/v2#!/Branding/get_branding_theme """ return self.client.get(self._url("themes", theme_id)) def delete_branding_theme(self, theme_id: str) -> Any: """Delete branding theme. Args: theme_id (str): The theme_id to delete branding theme for. See: https://auth0.com/docs/api/management/v2#!/Branding/delete_branding_theme """ return self.client.delete(self._url("themes", theme_id)) def update_branding_theme( self, theme_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Update branding theme. Args: theme_id (str): The theme_id to update branding theme for. body (dict): The attributes to set on the theme. See: https://auth0.com/docs/api/management/v2#!/Branding/patch_branding_theme """ return self.client.patch(self._url("themes", theme_id), data=body) def create_branding_theme(self, body: dict[str, Any]) -> dict[str, Any]: """Create branding theme. Args: body (dict): The attributes to set on the theme. See: https://auth0.com/docs/api/management/v2#!/Branding/post_branding_theme """ return self.client.post(self._url("themes"), data=body) auth0-auth0-python-0f6dbea/auth0/management/client_credentials.py000066400000000000000000000062621506260514000252110ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class ClientCredentials: """Auth0 client credentials endpoints. Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, client_id: str, id: str | None = None) -> str: url = "{}://{}/api/v2/clients/{}/credentials".format( self.protocol, self.domain, client_id ) if id is not None: return f"{url}/{id}" return url def all(self, client_id: str) -> list[dict[str, Any]]: """Get a list of credentials associated with a client. Args: client_id (string): The id of a client that owns the credentials. See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/get_client_credentials """ return self.client.get(self._url(client_id)) def get(self, client_id: str, id: str) -> dict[str, Any]: """Retrieve a specified client credential. Args: client_id (string): The id of a client that owns the credential. id (string): The id of the credential. See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/get_client_credentials_by_id """ return self.client.get(self._url(client_id, id)) def create(self, client_id: str, body: dict[str, Any]) -> dict[str, Any]: """Create a credential on a client. Args: client_id (string): The id of a client to create the credential for. See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/post_client_credentials """ return self.client.post(self._url(client_id), data=body) def delete(self, client_id: str, id: str) -> dict[str, Any]: """Delete a client's credential. Args: id (str): The id of credential to delete. See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/delete_client_credentials_by_id """ return self.client.delete(self._url(client_id, id)) auth0-auth0-python-0f6dbea/auth0/management/client_grants.py000066400000000000000000000126621506260514000242130ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class ClientGrants: """Auth0 client grants endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/client-grants" if id is not None: return f"{url}/{id}" return url def all( self, audience: str | None = None, page: int | None = None, per_page: int | None = None, include_totals: bool = False, client_id: str | None = None, allow_any_organization: bool | None = None, ): """Retrieves all client grants. Args: audience (str, optional): URL encoded audience of a Resource Server to filter. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. client_id (string, optional): The id of a client to filter. allow_any_organization (bool, optional): Optional filter on allow_any_organization. See: https://auth0.com/docs/api/management/v2#!/Client_Grants/get_client_grants """ params = { "audience": audience, "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), "client_id": client_id, "allow_any_organization": allow_any_organization, } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a client grant. Args: body (dict): Attributes for the new client grant. See: https://auth0.com/docs/api/management/v2#!/Client_Grants/post_client_grants """ return self.client.post(self._url(), data=body) def delete(self, id: str) -> Any: """Deletes a client grant. Args: id (str): Id of client grant to delete. See: https://auth0.com/docs/api/management/v2#!/Client_Grants/delete_client_grants_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Modifies a client grant. Args: id (str): The id of the client grant to modify. body (dict): Attributes to update. See: https://auth0.com/docs/api/management/v2#!/Client_Grants/patch_client_grants_by_id """ return self.client.patch(self._url(id), data=body) def get_organizations( self, id: str, page: int | None = None, per_page: int | None = None, include_totals: bool = False, from_param: str | None = None, take: int | None = None, ): """Get the organizations associated to a client grant. Args: id (str): Id of client grant. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. from_param (str, optional): Id to start retrieving entries. You can limit the amount of entries using the take parameter. take (int, optional): The total amount of entries to retrieve when using the from parameter. When not set, the default value is up to the server. """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "from": from_param, "take": take, } return self.client.get(self._url(f"{id}/organizations"), params=params) auth0-auth0-python-0f6dbea/auth0/management/clients.py000066400000000000000000000134231506260514000230140ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Clients: """Auth0 applications endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/clients" if id is not None: return f"{url}/{id}" return url def all( self, fields: list[str] | None = None, include_fields: bool = True, page: int | None = None, per_page: int | None = None, extra_params: dict[str, Any] | None = None, ) -> list[dict[str, Any]]: """Retrieves a list of all the applications. Important: The client_secret and encryption_key attributes can only be retrieved with the read:client_keys scope. Args: fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. extra_params (dictionary, optional): The extra parameters to add to the request. The fields, include_fields, page and per_page values specified as parameters take precedence over the ones defined here. See: https://auth0.com/docs/api/management/v2#!/Clients/get_clients """ params = extra_params or {} params["fields"] = fields and ",".join(fields) or None params["include_fields"] = str(include_fields).lower() params["page"] = page params["per_page"] = per_page return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new application. Args: body (dict): Attributes for the new application. See: https://auth0.com/docs/api/v2#!/Clients/post_clients """ return self.client.post(self._url(), data=body) def get( self, id: str, fields: list[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Retrieves an application by its id. Important: The client_secret, encryption_key and signing_keys attributes can only be retrieved with the read:client_keys scope. Args: id (str): Id of the application to get. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Clients/get_clients_by_id """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(id), params=params) def delete(self, id: str) -> Any: """Deletes an application and all its related assets. Args: id (str): Id of application to delete. See: https://auth0.com/docs/api/management/v2#!/Clients/delete_clients_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Modifies an application. Important: The client_secret, encryption_key and signing_keys attributes can only be updated with the update:client_keys scope. Args: id (str): Client ID of the application. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Clients/patch_clients_by_id """ return self.client.patch(self._url(id), data=body) def rotate_secret(self, id: str) -> dict[str, Any]: """Rotate a client secret. The generated secret is NOT base64 encoded. Args: id (str): Client ID of the application. See: https://auth0.com/docs/api/management/v2#!/Clients/post_rotate_secret """ data = {"id": id} url = self._url("%s/rotate-secret" % id) return self.client.post(url, data=data) auth0-auth0-python-0f6dbea/auth0/management/connections.py000066400000000000000000000142731506260514000237010ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Connections: """Auth0 connection endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/connections" if id is not None: return f"{url}/{id}" return url def all( self, strategy: str | None = None, fields: list[str] | None = None, include_fields: bool = True, page: int | None = None, per_page: int | None = None, extra_params: dict[str, Any] | None = None, name: str | None = None, ) -> list[dict[str, Any]]: """Retrieves all connections. Args: strategy (str, optional): Only retrieve connections of this strategy type. (e.g: strategy='amazon') fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. By default, all the fields will be retrieved. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. extra_params (dictionary, optional): The extra parameters to add to the request. The fields, include_fields, page and per_page values specified as parameters take precedence over the ones defined here. name (str): Provide the name of the connection to retrieve. See: https://auth0.com/docs/api/management/v2#!/Connections/get_connections Returns: A list of connection objects. """ params = extra_params or {} params["strategy"] = strategy or None params["fields"] = fields and ",".join(fields) or None params["include_fields"] = str(include_fields).lower() params["page"] = page params["per_page"] = per_page params["name"] = name return self.client.get(self._url(), params=params) def get( self, id: str, fields: list[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Retrieve connection by id. Args: id (str): Id of the connection to get. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. By default, all the fields will be retrieved. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Connections/get_connections_by_id Returns: A connection object. """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(id), params=params) def delete(self, id: str) -> Any: """Deletes a connection and all its users. Args: id: Id of the connection to delete. See: https://auth0.com/docs/api/management/v2#!/Connections/delete_connections_by_id Returns: An empty dict. """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Modifies a connection. Args: id: Id of the connection. body (dict): Specifies which fields are to be modified, and to what values. See: https://auth0.com/docs/api/management/v2#!/Connections/patch_connections_by_id Returns: The modified connection object. """ return self.client.patch(self._url(id), data=body) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new connection. Args: body (dict): Attributes used to create the connection. Mandatory attributes are: 'name' and 'strategy'. See: https://auth0.com/docs/api/management/v2#!/Connections/post_connections """ return self.client.post(self._url(), data=body) def delete_user_by_email(self, id: str, email: str) -> Any: """Deletes a specified connection user by its email. Args: id (str): The id of the connection (must be a database connection). email (str): The email of the user to delete. See: https://auth0.com/docs/api/management/v2#!/Connections/delete_users_by_email Returns: An empty dict. """ return self.client.delete(self._url(id) + "/users", params={"email": email}) auth0-auth0-python-0f6dbea/auth0/management/custom_domains.py000066400000000000000000000060731506260514000244020ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class CustomDomains: """Auth0 custom domains endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/custom-domains" if id is not None: return url + "/" + id return url def all(self) -> list[dict[str, Any]]: """Retrieves all custom domains. See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/get_custom_domains """ return self.client.get(self._url()) def get(self, id: str) -> dict[str, Any]: """Retrieves custom domain. See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/get_custom_domains_by_id """ url = self._url("%s" % (id)) return self.client.get(url) def delete(self, id: str) -> Any: """Deletes a grant. Args: id (str): The id of the custom domain to delete. See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/delete_custom_domains_by_id """ url = self._url("%s" % (id)) return self.client.delete(url) def create_new(self, body: dict[str, Any]) -> dict[str, Any]: """Configure a new custom domain. Args: body (str): The domain, tye and verification method in json. See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/post_custom_domains """ return self.client.post(self._url(), data=body) def verify(self, id: str) -> dict[str, Any]: """Verify a custom domain. Args: id (str): The id of the custom domain to delete. See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/post_verify """ url = self._url("%s/verify" % (id)) return self.client.post(url) auth0-auth0-python-0f6dbea/auth0/management/device_credentials.py000066400000000000000000000100371506260514000251650ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class DeviceCredentials: """Auth0 connection endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/device-credentials" if id is not None: return f"{url}/{id}" return url def get( self, user_id: str, client_id: str, type: str, fields: list[str] | None = None, include_fields: bool = True, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """List device credentials. Args: user_id (str): The user_id of the devices to retrieve. client_id (str): The client_id of the devices to retrieve. type (str): The type of credentials (public_key, refresh_token). fields (list, optional): A list of fields to include or exclude (depending on include_fields) from the result. Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. page (int, optional): Page index of the results to return. First page is 0. per_page (int, optional): Number of results per page. include_totals (bool, optional): True to return results inside an object that contains the total result count (True) or as a direct array of results (False, default). See: https://auth0.com/docs/api/management/v2#!/Device_Credentials/get_device_credentials """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), "user_id": user_id, "client_id": client_id, "type": type, "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a device public key. Args: body (dict): parameters for creating the public key (e.g: type, device_name, client_id, etc). See: https://auth0.com/docs/api/v2#!/Device_Credentials/post_device_credentials """ return self.client.post(self._url(), data=body) def delete(self, id: str) -> Any: """Delete credential. Args: id (str): The id of the credential to delete. See: https://auth0.com/docs/api/management/v2#!/Device_Credentials/delete_device_credentials_by_id """ return self.client.delete(self._url(id)) auth0-auth0-python-0f6dbea/auth0/management/email_templates.py000066400000000000000000000062551506260514000245250ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class EmailTemplates: """Auth0 email templates endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/email-templates" if id is not None: return f"{url}/{id}" return url def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new email template. Args: body (dict): Attributes for the new email template. See: https://auth0.com/docs/api/management/v2#!/Email_Templates/post_email_templates """ return self.client.post(self._url(), data=body) def get(self, template_name: str) -> dict[str, Any]: """Retrieves an email template by its name. Args: template_name (str): Name of the email template to get. Must be one of: 'verify_email', 'reset_email', 'welcome_email', 'blocked_account', 'stolen_credentials', 'enrollment_email', 'change_password', 'password_reset', 'mfa_oob_code'. See: https://auth0.com/docs/api/management/v2#!/Email_Templates/get_email_templates_by_templateName """ return self.client.get(self._url(template_name)) def update(self, template_name: str, body: dict[str, Any]) -> dict[str, Any]: """Update an existing email template. Args: template_name (str): Name of the email template to update. Must be one of: 'verify_email', 'reset_email', 'welcome_email', 'blocked_account', 'stolen_credentials', 'enrollment_email', 'change_password', 'password_reset', 'mfa_oob_code'. body (dict): Attributes to update on the email template. See: https://auth0.com/docs/api/management/v2#!/Email_Templates/patch_email_templates_by_templateName """ return self.client.patch(self._url(template_name), data=body) auth0-auth0-python-0f6dbea/auth0/management/emails.py000066400000000000000000000063211506260514000226240ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Emails: """Auth0 email endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/emails/provider" if id is not None: return f"{url}/{id}" return url def get( self, fields: list[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Get the email provider. Args: fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Emails/get_provider """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(), params=params) def config(self, body: dict[str, Any]) -> dict[str, Any]: """Configure the email provider. Args: body (dict): attributes of the created email provider. See: https://auth0.com/docs/api/v2#!/Emails/post_provider """ return self.client.post(self._url(), data=body) def delete(self) -> Any: """Delete the email provider. (USE WITH CAUTION) See: https://auth0.com/docs/api/management/v2#!/Emails/delete_provider """ return self.client.delete(self._url()) def update(self, body: dict[str, Any]) -> dict[str, Any]: """Update the email provider. Args: body (dict): attributes to update on the email provider See: https://auth0.com/docs/api/v2#!/Emails/patch_provider """ return self.client.patch(self._url(), data=body) auth0-auth0-python-0f6dbea/auth0/management/grants.py000066400000000000000000000061611506260514000226520ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Grants: """Auth0 grants endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/grants" if id is not None: return url + "/" + id return url def all( self, page: int | None = None, per_page: int | None = None, include_totals: bool = False, extra_params: dict[str, Any] | None = None, ): """Retrieves all grants. Args: page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. extra_params (dictionary, optional): The extra parameters to add to the request. The page, per_page, and include_totals values specified as parameters take precedence over the ones defined here. See: https://auth0.com/docs/api/management/v2#!/Grants/get_grants """ params = extra_params or {} params.update( { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } ) return self.client.get(self._url(), params=params) def delete(self, id: str) -> Any: """Deletes a grant. Args: id (str): The id of the grant to delete. See: https://auth0.com/docs/api/management/v2#!/Grants/delete_grants_by_id """ url = self._url("%s" % (id)) return self.client.delete(url) auth0-auth0-python-0f6dbea/auth0/management/guardian.py000066400000000000000000000127731506260514000231540ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Guardian: """Auth0 guardian endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/guardian" if id is not None: return f"{url}/{id}" return url def all_factors(self) -> list[dict[str, Any]]: """Retrieves all factors. Useful to check factor enablement and trial status. See: https://auth0.com/docs/api/management/v2#!/Guardian/get_factors """ return self.client.get(self._url("factors")) def update_factor(self, name: str, body: dict[str, Any]) -> dict[str, Any]: """Update Guardian factor. Useful to enable / disable factor. Args: name (str): Either push-notification or sms. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Guardian/put_factors_by_name """ url = self._url(f"factors/{name}") return self.client.put(url, data=body) def update_templates(self, body: dict[str, Any]) -> dict[str, Any]: """Update enrollment and verification SMS templates. Useful to send custom messages on sms enrollment and verification. Args: body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Guardian/put_templates """ return self.client.put(self._url("factors/sms/templates"), data=body) def get_templates(self) -> dict[str, Any]: """Get enrollment and verification templates. Retrieve both templates. Useful to check if a different template than default was set. See: https://auth0.com/docs/api/management/v2#!/Guardian/get_templates """ return self.client.get(self._url("factors/sms/templates")) def get_enrollment(self, id: str) -> dict[str, Any]: """Retrieves an enrollment. Useful to check its type and related metadata. Args: id (str): The id of the device account to update. See: https://auth0.com/docs/api/management/v2#!/Guardian/get_enrollments_by_id """ url = self._url(f"enrollments/{id}") return self.client.get(url) def delete_enrollment(self, id: str) -> Any: """Deletes an enrollment. Useful when you want to force re-enroll. Args: id (str): The id of the device account to update. See: https://auth0.com/docs/api/management/v2#!/Guardian/delete_enrollments_by_id """ url = self._url(f"enrollments/{id}") return self.client.delete(url) def create_enrollment_ticket(self, body: dict[str, Any]) -> dict[str, Any]: """Creates an enrollment ticket for user_id A useful way to send an email to a user, with a link that lead to start the enrollment process. Args: body (dict): Details of the user to send the ticket to. See: https://auth0.com/docs/api/management/v2#!/Guardian/post_ticket """ return self.client.post(self._url("enrollments/ticket"), data=body) def get_factor_providers(self, factor_name: str, name: str) -> dict[str, Any]: """Get Guardian SNS or SMS factor providers. Returns provider configuration. Args: factor_name (str): Either push-notification or sms. name (str): Name of the provider. See: https://auth0.com/docs/api/management/v2#!/Guardian/get_sns https://auth0.com/docs/api/management/v2#!/Guardian/get_twilio """ url = self._url(f"factors/{factor_name}/providers/{name}") return self.client.get(url) def update_factor_providers( self, factor_name: str, name: str, body: dict[str, Any] ) -> dict[str, Any]: """Get Guardian factor providers. Returns provider configuration. Args: factor_name (str): Either push-notification or sms. name (str): Name of the provider. body (dict): Details of the factor provider. See: https://auth0.com/docs/api/management/v2#!/Guardian/put_twilio """ url = self._url(f"factors/{factor_name}/providers/{name}") return self.client.put(url, data=body) auth0-auth0-python-0f6dbea/auth0/management/hooks.py000066400000000000000000000145511506260514000225010ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Hooks: """Hooks endpoint implementation. Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/hooks" if id is not None: return f"{url}/{id}" return url def all( self, enabled: bool = True, fields: list[str] | None = None, include_fields: bool = True, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """Retrieves a list of all hooks. Args: enabled (bool, optional): If provided, retrieves hooks that match the value, otherwise all hooks are retrieved. fields (list, optional): A list of fields to include or exclude (depending on include_fields) from the result, empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise (defaults to true). page (int, optional): The result's page number (zero based). per_page (int, optional): The amount of entries per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. See: https://auth0.com/docs/api/management/v2#!/Hooks/get_hooks """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } # since the default is True, this is here to disable the filter if enabled is not None: params["enabled"] = str(enabled).lower() return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new Hook. Args: body (dict): Attributes for the newly created hook, See: https://auth0.com/docs/api/v2#!/Hooks/post_hooks """ return self.client.post(self._url(), data=body) def get(self, id: str, fields: list[str] | None = None) -> dict[str, Any]: """Retrieves a hook by its ID. Args: id (str): The id of the hook to retrieve. fields (list, optional): A list of fields to include or exclude (depending on include_fields) from the result, empty to retrieve all fields. See: https://auth0.com/docs/api/management/v2#!/Hooks/get_hooks_by_id """ params = { "fields": fields and ",".join(fields) or None, } return self.client.get(self._url(id), params=params) def delete(self, id: str) -> Any: """Deletes a hook. Args: id (str): The id of the hook to delete. See: https://auth0.com/docs/api/management/v2#!/Hooks/delete_hooks_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Updates an existing hook. Args: id (str): The id of the hook to modify. body (dict): Attributes to modify. See: https://auth0.com/docs/api/v2#!/Hooks/patch_hooks_by_id """ return self.client.patch(self._url(id), data=body) def get_secrets(self, id: str) -> dict[str, Any]: """Retrieves a hook's secrets. Args: id (str): The id of the hook to retrieve secrets from. See: https://auth0.com/docs/api/management/v2#!/Hooks/get_secrets """ return self.client.get(self._url("%s/secrets" % id)) def add_secrets(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Add one or more secrets for an existing hook. Args: id (str): The id of the hook to add secrets to. body (dict): Dict of key-value pairs where the value must be a string. See: https://auth0.com/docs/api/management/v2#!/Hooks/post_secrets """ return self.client.post(self._url("%s/secrets" % id), data=body) def delete_secrets(self, id: str, body: list[str]) -> Any: """Delete one or more existing secrets for an existing hook. Args: id (str): The id of the hook to add secrets to. body (list): List of secret names to delete. See: https://auth0.com/docs/api/management/v2#!/Hooks/delete_secrets """ return self.client.delete(self._url("%s/secrets" % id), data=body) def update_secrets(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update one or more existing secrets for an existing hook. Args: id (str): The id of the hook to add secrets to. body (dict): Dict of key-value pairs where the value must be a string. See: https://auth0.com/docs/api/management/v2#!/Hooks/patch_secrets """ return self.client.patch(self._url("%s/secrets" % id), data=body) auth0-auth0-python-0f6dbea/auth0/management/jobs.py000066400000000000000000000116131506260514000223070ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Jobs: """Auth0 jobs endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, path: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/jobs" if path is not None: return f"{url}/{path}" return url def get(self, id: str) -> dict[str, Any]: """Retrieves a job. Useful to check its status. Args: id (str): The id of the job. See: https://auth0.com/docs/api/management/v2#!/Jobs/get_jobs_by_id """ return self.client.get(self._url(id)) def get_failed_job(self, id: str) -> dict[str, Any]: """Get failed job error details. Args: id (str): The id of the job. See: https://auth0.com/docs/api/management/v2#!/Jobs/get_errors """ url = self._url(f"{id}/errors") return self.client.get(url) def export_users(self, body: dict[str, Any]): """Export all users to a file using a long running job. Check job status with get(). URL pointing to the export file will be included in the status once the job is complete. Args: body (dict): The details of the export users request. See: https://auth0.com/docs/api/management/v2#!/Jobs/post_users_exports """ return self.client.post(self._url("users-exports"), data=body) def import_users( self, connection_id: str, file_obj: Any, upsert: bool = False, send_completion_email: bool = True, external_id: str | None = None, ) -> dict[str, Any]: """Imports users to a connection from a file. Args: connection_id (str): The connection id of the connection to which users will be inserted. file_obj (file): A file-like object to upload. The format for this file is explained in: https://auth0.com/docs/bulk-import. upsert (bool, optional): When set to False, pre-existing users that match on email address, user ID, or username will fail. When set to True, pre-existing users that match on any of these fields will be updated, but only with upsertable attributes. Defaults to False. For a list of user profile fields that can be upserted during import, see the following article https://auth0.com/docs/users/references/user-profile-structure#user-profile-attributes. send_completion_email (bool, optional): When set to True, an email will be sent to notify the completion of this job. When set to False, no email will be sent. Defaults to True. external_id (str, optional): Customer-defined ID. See: https://auth0.com/docs/api/management/v2#!/Jobs/post_users_imports """ return self.client.file_post( self._url("users-imports"), data={ "connection_id": connection_id, "upsert": str(upsert).lower(), "send_completion_email": str(send_completion_email).lower(), "external_id": external_id, }, files={"users": file_obj}, ) def send_verification_email(self, body: dict[str, Any]) -> dict[str, Any]: """Send verification email. Send an email to the specified user that asks them to click a link to verify their email address. Args: body (dict): Details of verification email request. See: https://auth0.com/docs/api/v2#!/Jobs/post_verification_email """ return self.client.post(self._url("verification-email"), data=body) auth0-auth0-python-0f6dbea/auth0/management/log_streams.py000066400000000000000000000063141506260514000236730ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class LogStreams: """Auth0 log streams endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/log-streams" if id is not None: return f"{url}/{id}" return url def list(self) -> list[dict[str, Any]]: """Search log events. Args: See: https://auth0.com/docs/api/management/v2/#!/Log_Streams/get_log_streams """ return self.client.get(self._url()) def get(self, id: str) -> dict[str, Any]: """Retrieves the data related to the log stream entry identified by id. Args: id (str): The id of the log stream to retrieve. See: https://auth0.com/docs/api/management/v2/#!/Log_Streams/get_log_streams_by_id """ return self.client.get(self._url(id)) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new log stream. Args: body (dict): the attributes for the role to create. See: https://auth0.com/docs/api/management/v2/#!/Log_Streams/post_log_streams """ return self.client.post(self._url(), data=body) def delete(self, id: str) -> dict[str, Any]: """Delete a log stream. Args: id (str): The id of the log ste to delete. See: https://auth0.com/docs/api/management/v2/#!/Log_Streams/delete_log_streams_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a log stream with the attributes passed in 'body' Args: id (str): The id of the log stream to update. body (dict): the attributes to update on the log stream. See: https://auth0.com/docs/api/management/v2/#!/Log_Streams/patch_log_streams_by_id """ return self.client.patch(self._url(id), data=body) auth0-auth0-python-0f6dbea/auth0/management/logs.py000066400000000000000000000101321506260514000223110ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Logs: """Auth0 logs endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/logs" if id is not None: return f"{url}/{id}" return url def search( self, page: int = 0, per_page: int = 50, sort: str | None = None, q: str | None = None, include_totals: bool = True, fields: list[str] | None = None, from_param: str | None = None, take: int | None = None, include_fields: bool = True, ): """Search log events. Args: page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 50 results per page. sort (str, optional): The field to use for sorting. 1 == ascending and -1 == descending. (e.g: date:1) When not set, the default value is up to the server. q (str, optional): Query in Lucene query string syntax. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. from_param (str, optional): Log Event Id to start retrieving logs. You can limit the amount of logs using the take parameter. take (int, optional): The total amount of entries to retrieve when using the from parameter. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Logs/get_logs """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "sort": sort, "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), "q": q, "from": from_param, "take": take, } return self.client.get(self._url(), params=params) def get(self, id: str) -> dict[str, Any]: """Retrieves the data related to the log entry identified by id. Args: id (str): The log_id of the log to retrieve. See: https://auth0.com/docs/api/management/v2#!/Logs/get_logs_by_id """ return self.client.get(self._url(id)) auth0-auth0-python-0f6dbea/auth0/management/network_acls.py000066400000000000000000000103271506260514000240460ustar00rootroot00000000000000from __future__ import annotations from typing import Any, List # List is being used as list is already a method. from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class NetworkAcls: """Auth0 Netwrok Acls endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/network-acls" if id is not None: return f"{url}/{id}" return url def all( self, page: int = 0, per_page: int = 25, include_totals: bool = True, ) -> List[dict[str, Any]]: """List self-service profiles. Args: page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2/network-acls/get-network-acls """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new self-service profile. Args: body (dict): Attributes for the new access control list. See: https://auth0.com/docs/api/management/v2/network-acls/post-network-acls """ return self.client.post(self._url(), data=body) def get(self, id: str) -> dict[str, Any]: """Get a self-service profile. Args: id (str): The id of the access control list to retrieve. See: https://auth0.com/docs/api/management/v2/network-acls/get-network-acls-by-id """ return self.client.get(self._url(id)) def delete(self, id: str) -> None: """Delete a self-service profile. Args: id (str): The id of the access control list to delete. See: https://auth0.com/docs/api/management/v2/network-acls/delete-network-acls-by-id """ self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a access control list. Args: id (str): The id of the access control list to update. body (dict): Attributes of the access control list to modify. See: https://auth0.com/docs/api/management/v2/network-acls/put-network-acls-by-id """ return self.client.put(self._url(id), data=body) def update_partial(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update partially the access control list. See: https://auth0.com/docs/api/management/v2/network-acls/patch-network-acls-by-id """ return self.client.patch(self._url(id), data=body) auth0-auth0-python-0f6dbea/auth0/management/organizations.py000066400000000000000000000434601506260514000242460ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Organizations: """Auth0 organizations endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, *args: str | None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/organizations" for p in args: if p is not None: url = f"{url}/{p}" return url # Organizations def all_organizations( self, page: int | None = None, per_page: int | None = None, include_totals: bool = True, from_param: str | None = None, take: int | None = None, ): """Retrieves a list of all the organizations. Args: page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. from_param (str, optional): Checkpoint Id from which to begin retrieving results. You can limit the number of entries using the take parameter. take (int, optional): The total amount of entries to retrieve when using the from parameter. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_organizations """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), "from": from_param, "take": take, } return self.client.get(self._url(), params=params) def get_organization_by_name(self, name: str | None = None) -> dict[str, Any]: """Retrieves an organization given its name. Args: name (str): The name of the organization to retrieve. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_name_by_name """ params = {} return self.client.get(self._url("name", name), params=params) def get_organization(self, id: str) -> dict[str, Any]: """Retrieves an organization by its ID. Args: id (str): Id of organization to retrieve. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_organizations_by_id """ params = {} return self.client.get(self._url(id), params=params) def create_organization(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new organization. Args: body (dict): Attributes for the new organization. See: https://auth0.com/docs/api/management/v2#!/Organizations/post_organizations """ return self.client.post(self._url(), data=body) def update_organization(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Modifies an organization. Args: id (str): the ID of the organization. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Organizations/patch_organizations_by_id """ return self.client.patch(self._url(id), data=body) def delete_organization(self, id: str) -> Any: """Deletes an organization and all its related assets. Args: id (str): Id of organization to delete. See: https://auth0.com/docs/api/management/v2#!/Organizations/delete_organizations_by_id """ return self.client.delete(self._url(id)) # Organization Connections def all_organization_connections( self, id: str, page: int | None = None, per_page: int | None = None ) -> list[dict[str, Any]]: """Retrieves a list of all the organization connections. Args: id (str): the ID of the organization. page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_enabled_connections """ params = {"page": page, "per_page": per_page} return self.client.get(self._url(id, "enabled_connections"), params=params) def get_organization_connection( self, id: str, connection_id: str ) -> dict[str, Any]: """Retrieves an organization connection by its ID. Args: id (str): the ID of the organization. connection_id (str): the ID of the connection. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_enabled_connections_by_connectionId """ params = {} return self.client.get( self._url(id, "enabled_connections", connection_id), params=params ) def create_organization_connection( self, id: str, body: dict[str, Any] ) -> dict[str, Any]: """Adds a connection to an organization. Args: id (str): the ID of the organization. body (dict): Attributes for the connection to add. See: https://auth0.com/docs/api/management/v2#!/Organizations/post_enabled_connections """ return self.client.post(self._url(id, "enabled_connections"), data=body) def update_organization_connection( self, id: str, connection_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Modifies an organization. Args: id (str): the ID of the organization. connection_id (str): the ID of the connection to update. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Organizations/patch_enabled_connections_by_connectionId """ return self.client.patch( self._url(id, "enabled_connections", connection_id), data=body ) def delete_organization_connection(self, id: str, connection_id: str) -> Any: """Deletes a connection from the given organization. Args: id (str): Id of organization. connection_id (str): the ID of the connection to delete. See: https://auth0.com/docs/api/management/v2#!/Organizations/delete_enabled_connections_by_connectionId """ return self.client.delete(self._url(id, "enabled_connections", connection_id)) # Organization Members def all_organization_members( self, id: str, page: int | None = None, per_page: int | None = None, include_totals: bool = True, from_param: str | None = None, take: int | None = None, fields: list[str] | None = None, include_fields: bool = True, ): """Retrieves a list of all the organization members. Member roles are not sent by default. Use `fields=roles` to retrieve the roles assigned to each listed member. To use this parameter, you must include the `read:organization_member_roles scope` in the token. Args: id (str): the ID of the organization. page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. from_param (str, optional): Checkpoint Id from which to begin retrieving results. You can limit the number of entries using the take parameter. take (int, optional): The total amount of entries to retrieve when using the from parameter. When not set, the default value is up to the server. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). If fields is left blank, all fields (except roles) are returned. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2/organizations/get-members """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), "from": from_param, "take": take, "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(id, "members"), params=params) def create_organization_members( self, id: str, body: dict[str, Any] ) -> dict[str, Any]: """Adds members to an organization. Args: id (str): the ID of the organization. body (dict): Attributes from the members to add. See: https://auth0.com/docs/api/management/v2#!/Organizations/post_members """ return self.client.post(self._url(id, "members"), data=body) def delete_organization_members(self, id: str, body: dict[str, Any]) -> Any: """Deletes members from the given organization. Args: id (str): Id of organization. body (dict): Attributes from the members to delete See: https://auth0.com/docs/api/management/v2#!/Organizations/delete_members """ return self.client.delete(self._url(id, "members"), data=body) # Organization Member Roles def all_organization_member_roles( self, id: str, user_id: str, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ) -> list[dict[str, Any]]: """Retrieves a list of all the roles from the given organization member. Args: id (str): the ID of the organization. user_id (str): the ID of the user member of the organization. page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_organization_member_roles """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower() } return self.client.get( self._url(id, "members", user_id, "roles"), params=params ) def create_organization_member_roles( self, id: str, user_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Adds roles to a member of an organization. Args: id (str): the ID of the organization. user_id (str): the ID of the user member of the organization. body (dict): Attributes from the members to add. See: https://auth0.com/docs/api/management/v2#!/Organizations/post_organization_member_roles """ return self.client.post(self._url(id, "members", user_id, "roles"), data=body) def delete_organization_member_roles( self, id: str, user_id: str, body: dict[str, Any] ) -> Any: """Deletes roles from a member of an organization. Args: id (str): Id of organization. user_id (str): the ID of the user member of the organization. body (dict): Attributes from the members to delete See: https://auth0.com/docs/api/management/v2#!/Organizations/delete_organization_member_roles """ return self.client.delete(self._url(id, "members", user_id, "roles"), data=body) # Organization Invitations def all_organization_invitations( self, id: str, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """Retrieves a list of all the organization invitations. Args: id (str): the ID of the organization. page (int): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. NOTE: returns start and limit, total count is not yet supported See: https://auth0.com/docs/api/management/v2#!/Organizations/get_invitations """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(id, "invitations"), params=params) def get_organization_invitation(self, id: str, invitaton_id: str) -> dict[str, Any]: """Retrieves an organization invitation by its ID. Args: id (str): the ID of the organization. invitaton_id (str): the ID of the invitation. See: https://auth0.com/docs/api/management/v2#!/Organizations/get_invitations_by_invitation_id """ params = {} return self.client.get( self._url(id, "invitations", invitaton_id), params=params ) def create_organization_invitation( self, id: str, body: dict[str, Any] ) -> dict[str, Any]: """Create an invitation to an organization. Args: id (str): the ID of the organization. body (dict): Attributes for the invitation to create. See: https://auth0.com/docs/api/management/v2#!/Organizations/post_invitations """ return self.client.post(self._url(id, "invitations"), data=body) def delete_organization_invitation(self, id: str, invitation_id: str) -> Any: """Deletes an invitation from the given organization. Args: id (str): Id of organization. invitation_id (str): the ID of the invitation to delete. See: https://auth0.com/docs/api/management/v2#!/Organizations/delete_invitations_by_invitation_id """ return self.client.delete(self._url(id, "invitations", invitation_id)) def get_client_grants( self, id: str, audience: str | None = None, client_id: str | None = None, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """Get client grants associated to an organization. Args: id (str): Id of organization. audience (str, optional): URL encoded audience of a Resource Server to filter. client_id (string, optional): The id of a client to filter. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. """ params = { "audience": audience, "client_id": client_id, "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(id, "client-grants"), params=params) def add_client_grant(self, id: str, grant_id: str) -> dict[str, Any]: """Associate a client grant with an organization. Args: id (str): the ID of the organization. grant_id (string) A Client Grant ID to add to the organization. """ return self.client.post( self._url(id, "client-grants"), data={"grant_id": grant_id} ) def delete_client_grant(self, id: str, grant_id: str) -> dict[str, Any]: """Remove a client grant from an organization. Args: id (str): the ID of the organization. grant_id (string) A Client Grant ID to remove from the organization. """ return self.client.delete(self._url(id, "client-grants", grant_id)) auth0-auth0-python-0f6dbea/auth0/management/prompts.py000066400000000000000000000060441506260514000230600ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Prompts: """Auth0 prompts endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, prompt: str | None = None, language: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/prompts" if prompt is not None and language is not None: return f"{url}/{prompt}/custom-text/{language}" return url def get(self) -> dict[str, Any]: """Retrieves prompts settings. See: https://auth0.com/docs/api/management/v2#!/Prompts/get_prompts """ return self.client.get(self._url()) def update(self, body: dict[str, Any]) -> dict[str, Any]: """Updates prompts settings. See: https://auth0.com/docs/api/management/v2#!/Prompts/patch_prompts """ return self.client.patch(self._url(), data=body) def get_custom_text(self, prompt: str, language: str): """Retrieves custom text for a prompt in a specific language. Args: prompt (str): Name of the prompt. language (str): Language to update. See: https://auth0.com/docs/api/management/v2#!/Prompts/get_custom_text_by_language """ return self.client.get(self._url(prompt, language)) def update_custom_text( self, prompt: str, language: str, body: dict[str, Any] ) -> dict[str, Any]: """Updates custom text for a prompt in a specific language. Args: prompt (str): Name of the prompt. language (str): Language to update. body (dict): An object containing custom dictionaries for a group of screens. See: https://auth0.com/docs/api/management/v2#!/Prompts/put_custom_text_by_language """ return self.client.put(self._url(prompt, language), data=body) auth0-auth0-python-0f6dbea/auth0/management/resource_servers.py000066400000000000000000000076031506260514000247560ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class ResourceServers: """Auth0 resource servers endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/resource-servers" if id is not None: return f"{url}/{id}" return url def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new resource server. Args: body (dict): Attributes for the new resource Server. See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/post_resource_servers """ return self.client.post(self._url(), data=body) def get_all( self, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """Retrieves all resource servers Args: page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/get_resource_servers """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(), params=params) def get(self, id: str) -> dict[str, Any]: """Retrieves a resource server by its id. Args: id (str): id of the resource server to get. See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/get_resource_servers_by_id """ return self.client.get(self._url(id)) def delete(self, id: str) -> Any: """Deletes a resource server. Args: id (str): Id of resource server to delete. See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/delete_resource_servers_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Modifies a resource server. Args: id (str): The id of the resource server to update. body (dict): Attributes to modify. See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/patch_resource_servers_by_id """ return self.client.patch(self._url(id), data=body) auth0-auth0-python-0f6dbea/auth0/management/roles.py000066400000000000000000000174621506260514000225060ustar00rootroot00000000000000from __future__ import annotations from typing import Any, List # List is being used as list is already a method. from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Roles: """Auth0 roles endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/roles" if id is not None: return f"{url}/{id}" return url def list( self, page: int = 0, per_page: int = 25, include_totals: bool = True, name_filter: str | None = None, ): """List or search roles. Args: page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. name_filter (str, optional): A case-insensitive filter to apply to search for roles by name. See: https://auth0.com/docs/api/management/v2#!/Roles/get_roles """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "name_filter": name_filter, } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new role. Args: body (dict): the attributes for the role to create. See: https://auth0.com/docs/api/v2#!/Roles/post_roles """ return self.client.post(self._url(), data=body) def get(self, id: str) -> dict[str, Any]: """Get a role. Args: id (str): The id of the role to retrieve. See: https://auth0.com/docs/api/management/v2#!/Roles/get_roles_by_id """ return self.client.get(self._url(id)) def delete(self, id: str) -> Any: """Delete a role. Args: id (str): The id of the role to delete. See: https://auth0.com/docs/api/management/v2#!/Roles/delete_roles_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a role with the attributes passed in 'body' Args: id (str): The id of the role to update. body (dict): the attributes to update on the role. See: https://auth0.com/docs/api/management/v2#!/Roles/patch_roles_by_id """ return self.client.patch(self._url(id), data=body) def list_users( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True, from_param: str | None = None, take: int | None = None, ): """List the users that have been associated with a given role. Args: id (str): The role's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. from_param (str, optional): Checkpoint Id from which to begin retrieving results. You can limit the number of entries using the take parameter. take (int, optional): The total amount of entries to retrieve when using the from parameter. When not set, the default value is up to the server. See https://auth0.com/docs/api/management/v2#!/Roles/get_role_user """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "from": from_param, "take": take, } url = self._url(f"{id}/users") return self.client.get(url, params=params) def add_users(self, id: str, users: List[str]) -> dict[str, Any]: """Assign users to a role. Args: id (str): The role's id. users (list of str): A list of users ids to add to this role. See https://auth0.com/docs/api/management/v2#!/Roles/post_role_users """ url = self._url(f"{id}/users") body = {"users": users} return self.client.post(url, data=body) def list_permissions( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True ): """List the permissions associated to a role. Args: id (str): The role's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See https://auth0.com/docs/api/management/v2#!/Roles/get_role_permission """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), } url = self._url(f"{id}/permissions") return self.client.get(url, params=params) def remove_permissions(self, id: str, permissions: List[dict[str, str]]) -> Any: """Unassociates permissions from a role. Args: id (str): The role's id. permissions (list of str): A list of permission ids to unassociate from the role. See https://auth0.com/docs/api/management/v2#!/Roles/delete_role_permission_assignment """ url = self._url(f"{id}/permissions") body = {"permissions": permissions} return self.client.delete(url, data=body) def add_permissions(self, id: str, permissions: List[dict[str, str]]) -> dict[str, Any]: """Associates permissions with a role. Args: id (str): The role's id. permissions (list of str): A list of permission ids to associate to the role. See https://auth0.com/docs/api/management/v2#!/Roles/post_role_permission_assignment """ url = self._url(f"{id}/permissions") body = {"permissions": permissions} return self.client.post(url, data=body) auth0-auth0-python-0f6dbea/auth0/management/rules.py000066400000000000000000000124551506260514000225110ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Rules: """Rules endpoint implementation. Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/rules" if id is not None: return f"{url}/{id}" return url def all( self, stage: str = "login_success", enabled: bool = True, fields: list[str] | None = None, include_fields: bool = True, page: int | None = None, per_page: int | None = None, include_totals: bool = False, ): """Retrieves a list of all rules. Args: stage (str, optional): Retrieves rules that match the execution stage. Defaults to login_success. enabled (bool, optional): If provided, retrieves rules that match the value, otherwise all rules are retrieved. fields (list, optional): A list of fields to include or exclude (depending on include_fields) from the result. Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. page (int, optional): The result's page number (zero based). When not set, the default value is up to the server. per_page (int, optional): The amount of entries per page. When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. See: https://auth0.com/docs/api/management/v2#!/Rules/get_rules """ params = { "stage": stage, "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } # since the default is True, this is here to disable the filter if enabled is not None: params["enabled"] = str(enabled).lower() return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new rule. Args: body (dict): Attributes for the newly created rule. See: https://auth0.com/docs/api/v2#!/Rules/post_rules """ return self.client.post(self._url(), data=body) def get( self, id: str, fields: list[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Retrieves a rule by its ID. Args: id (str): The id of the rule to retrieve. fields (list, optional): A list of fields to include or exclude (depending on include_fields) from the result. Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Rules/get_rules_by_id """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(id), params=params) def delete(self, id: str) -> Any: """Delete a rule. Args: id (str): The id of the rule to delete. See: https://auth0.com/docs/api/management/v2#!/Rules/delete_rules_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update an existing rule Args: id (str): The id of the rule to modify. body (dict): Attributes to modify. See: https://auth0.com/docs/api/v2#!/Rules/patch_rules_by_id """ return self.client.patch(self._url(id), data=body) auth0-auth0-python-0f6dbea/auth0/management/rules_configs.py000066400000000000000000000051131506260514000242120ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class RulesConfigs: """RulesConfig endpoint implementation. Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/rules-configs" if id is not None: return url + "/" + id return url def all(self) -> list[dict[str, Any]]: """Lists the config variable keys for rules. See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/get_rules_configs """ return self.client.get(self._url()) def unset(self, key: str) -> Any: """Removes the rules config for a given key. Args: key (str): rules config key to remove. See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/delete_rules_configs_by_key """ return self.client.delete(self._url(key)) def set(self, key: str, value: str) -> dict[str, Any]: """Sets the rules config for a given key. Args: key (str): rules config key to set. value (str): value to set for the rules config key. See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/put_rules_configs_by_key """ url = self._url(f"{key}") body = {"value": value} return self.client.put(url, data=body) auth0-auth0-python-0f6dbea/auth0/management/self_service_profiles.py000066400000000000000000000137031506260514000257300ustar00rootroot00000000000000from __future__ import annotations from typing import Any, List # List is being used as list is already a method. from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class SelfServiceProfiles: """Auth0 Self Service Profiles endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, profile_id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/self-service-profiles" if profile_id is not None: return f"{url}/{profile_id}" return url def all( self, page: int = 0, per_page: int = 25, include_totals: bool = True, ) -> List[dict[str, Any]]: """List self-service profiles. Args: page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2/self-service-profiles/get-self-service-profiles """ params = { "page": page, "per_page": per_page, "include_totals": str(include_totals).lower(), } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Create a new self-service profile. Args: body (dict): Attributes for the new self-service profile. See: https://auth0.com/docs/api/management/v2/self-service-profiles/post-self-service-profiles """ return self.client.post(self._url(), data=body) def get(self, profile_id: str) -> dict[str, Any]: """Get a self-service profile. Args: id (str): The id of the self-service profile to retrieve. See: https://auth0.com/docs/api/management/v2/self-service-profiles/get-self-service-profiles-by-id """ return self.client.get(self._url(profile_id)) def delete(self, profile_id: str) -> None: """Delete a self-service profile. Args: id (str): The id of the self-service profile to delete. See: https://auth0.com/docs/api/management/v2/self-service-profiles/delete-self-service-profiles-by-id """ self.client.delete(self._url(profile_id)) def update(self, profile_id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a self-service profile. Args: id (str): The id of the self-service profile to update. body (dict): Attributes of the self-service profile to modify. See: https://auth0.com/docs/api/management/v2/self-service-profiles/patch-self-service-profiles-by-id """ return self.client.patch(self._url(profile_id), data=body) def get_custom_text( self, profile_id: str, language: str, page: str ) -> dict[str, Any]: """Get the custom text for a self-service profile. See: https://auth0.com/docs/api/management/v2/self-service-profiles/get-self-service-profile-custom-text """ url = self._url(f"{profile_id}/custom-text/{language}/{page}") return self.client.get(url) def update_custom_text( self, profile_id: str, language: str, page: str, body: dict[str, Any] ) -> dict[str, Any]: """Update the custom text for a self-service profile. See: https://auth0.com/docs/api/management/v2/self-service-profiles/put-self-service-profile-custom-text """ url = self._url(f"{profile_id}/custom-text/{language}/{page}") return self.client.put(url, data=body) def create_sso_ticket( self, profile_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Create a single sign-on ticket for a self-service profile. Args: id (str): The id of the self-service profile to create the ticket for. body (dict): Attributes for the single sign-on ticket. See: https://auth0.com/docs/api/management/v2/self-service-profiles/post-sso-ticket """ url = self._url(f"{profile_id}/sso-ticket") return self.client.post(url, data=body) def revoke_sso_ticket(self, profile_id: str, ticket_id: str) -> None: """Revoke a single sign-on ticket for a self-service profile. Args: id (str): The id of the self-service profile to revoke the ticket from. ticket (str): The ticket to revoke. See: https://auth0.com/docs/api/management/v2/self-service-profiles/post-revoke """ url = self._url(f"{profile_id}/sso-ticket/{ticket_id}/revoke") self.client.post(url)auth0-auth0-python-0f6dbea/auth0/management/stats.py000066400000000000000000000045621506260514000225150ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Stats: """Auth0 stats endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, action: str) -> str: return f"{self.protocol}://{self.domain}/api/v2/stats/{action}" def active_users(self) -> int: """Gets the active users count (logged in during the last 30 days). Returns: An integer. See: https://auth0.com/docs/api/management/v2#!/Stats/get_active_users """ return self.client.get(self._url("active-users")) def daily_stats( self, from_date: str | None = None, to_date: str | None = None ) -> list[dict[str, Any]]: """Gets the daily stats for a particular period. Args: from_date (str, optional): The first day of the period (inclusive) in YYYYMMDD format. to_date (str, optional): The last day of the period (inclusive) in YYYYMMDD format. See: https://auth0.com/docs/api/management/v2#!/Stats/get_daily """ return self.client.get( self._url("daily"), params={"from": from_date, "to": to_date} ) auth0-auth0-python-0f6dbea/auth0/management/tenants.py000066400000000000000000000050771506260514000230350ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Tenants: """Auth0 tenants endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self) -> str: return f"{self.protocol}://{self.domain}/api/v2/tenants/settings" def get( self, fields: list[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Get tenant settings. Args: fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Tenants/get_settings """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(), params=params) def update(self, body: dict[str, Any]) -> dict[str, Any]: """Update tenant settings. Args: body (dict): the attributes to update in the tenant. See: https://auth0.com/docs/api/v2#!/Tenants/patch_settings """ return self.client.patch(self._url(), data=body) auth0-auth0-python-0f6dbea/auth0/management/tickets.py000066400000000000000000000043331506260514000230210ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Tickets: """Auth0 tickets endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, action: str) -> str: return f"{self.protocol}://{self.domain}/api/v2/tickets/{action}" def create_email_verification(self, body: dict[str, Any]) -> dict[str, Any]: """Create an email verification ticket. Args: body (dict): attributes to set on the email verification request. See: https://auth0.com/docs/api/v2#!/Tickets/post_email_verification """ return self.client.post(self._url("email-verification"), data=body) def create_pswd_change(self, body: dict[str, Any]) -> dict[str, Any]: """Create password change ticket. Args: body (dict): attributes to set on the password change request. See: https://auth0.com/docs/api/v2#!/Tickets/post_password_change """ return self.client.post(self._url("password-change"), data=body) auth0-auth0-python-0f6dbea/auth0/management/user_blocks.py000066400000000000000000000056741506260514000236770ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class UserBlocks: """Auth0 user blocks endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/user-blocks" if id is not None: return f"{url}/{id}" return url def get_by_identifier(self, identifier: str) -> dict[str, Any]: """Gets blocks by identifier Args: identifier (str): Should be any of: username, phone_number, email. See: https://auth0.com/docs/api/management/v2#!/User_Blocks/get_user_blocks """ params = {"identifier": identifier} return self.client.get(self._url(), params=params) def unblock_by_identifier(self, identifier: dict[str, Any]) -> Any: """Unblocks by identifier Args: identifier (str): Should be any of: username, phone_number, email. See: https://auth0.com/docs/api/management/v2#!/User_Blocks/delete_user_blocks """ params = {"identifier": identifier} return self.client.delete(self._url(), params=params) def get(self, id: str) -> dict[str, Any]: """Get a user's blocks Args: id (str): The user_id of the user to retrieve. See: https://auth0.com/docs/api/management/v2#!/User_Blocks/get_user_blocks_by_id """ return self.client.get(self._url(id)) def unblock(self, id: str) -> Any: """Unblock a user Args: id (str): The user_id of the user to update. See: https://auth0.com/docs/api/management/v2#!/User_Blocks/delete_user_blocks_by_id """ return self.client.delete(self._url(id)) auth0-auth0-python-0f6dbea/auth0/management/users.py000066400000000000000000000513551506260514000225220ustar00rootroot00000000000000from __future__ import annotations from typing import Any, List # List is being used as list is already a method. from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class Users: """Auth0 users endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self, id: str | None = None) -> str: url = f"{self.protocol}://{self.domain}/api/v2/users" if id is not None: return f"{url}/{id}" return url def list( self, page: int = 0, per_page: int = 25, sort: str | None = None, connection: str | None = None, q: str | None = None, search_engine: str | None = None, include_totals: bool = True, fields: List[str] | None = None, include_fields: bool = True, ): """List or search users. Args: page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. sort (str, optional): The field to use for sorting. 1 == ascending and -1 == descending. (e.g: email:1) When not set, the default value is up to the server. connection (str, optional): Connection filter. q (str, optional): Query in Lucene query string syntax. Only fields in app_metadata, user_metadata or the normalized user profile are searchable. search_engine (str, optional): The version of the search_engine to use when querying for users. Will default to the latest version available. See: https://auth0.com/docs/users/search. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be include in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Users/get_users """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "sort": sort, "connection": connection, "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), "q": q, "search_engine": search_engine, } return self.client.get(self._url(), params=params) def create(self, body: dict[str, Any]) -> dict[str, Any]: """Creates a new user. Args: body (dict): the attributes to set on the user to create. See: https://auth0.com/docs/api/v2#!/Users/post_users """ return self.client.post(self._url(), data=body) def get( self, id: str, fields: List[str] | None = None, include_fields: bool = True ) -> dict[str, Any]: """Get a user. Args: id (str): The user_id of the user to retrieve. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be included in the result, False otherwise. Defaults to True. See: https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id """ params = { "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(id), params=params) def delete(self, id: str) -> Any: """Delete a user. Args: id (str): The user_id of the user to delete. See: https://auth0.com/docs/api/management/v2#!/Users/delete_users_by_id """ return self.client.delete(self._url(id)) def update(self, id: str, body: dict[str, Any]) -> dict[str, Any]: """Update a user with the attributes passed in 'body' Args: id (str): The user_id of the user to update. body (dict): The attributes of the user to update. See: https://auth0.com/docs/api/v2#!/Users/patch_users_by_id """ return self.client.patch(self._url(id), data=body) def list_organizations( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True ): """List the organizations that the user is member of. Args: id (str): The user's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See https://auth0.com/docs/api/management/v2#!/Users/get_organizations """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), } url = self._url(f"{id}/organizations") return self.client.get(url, params=params) def list_roles( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True ): """List the roles associated with a user. Args: id (str): The user's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See https://auth0.com/docs/api/management/v2#!/Users/get_user_roles """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), } url = self._url(f"{id}/roles") return self.client.get(url, params=params) def remove_roles(self, id: str, roles: List[str]) -> Any: """Removes an array of roles from a user. Args: id (str): The user's id. roles (list of str): A list of roles ids to unassociate from the user. See https://auth0.com/docs/api/management/v2#!/Users/delete_user_roles """ url = self._url(f"{id}/roles") body = {"roles": roles} return self.client.delete(url, data=body) def add_roles(self, id: str, roles: List[str]) -> dict[str, Any]: """Associate an array of roles with a user. Args: id (str): The user's id. roles (list of str): A list of roles ids to associated with the user. See https://auth0.com/docs/api/management/v2#!/Users/post_user_roles """ url = self._url(f"{id}/roles") body = {"roles": roles} return self.client.post(url, data=body) def list_permissions( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True ): """List the permissions associated to the user. Args: id (str): The user's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See https://auth0.com/docs/api/management/v2#!/Users/get_permissions """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), } url = self._url(f"{id}/permissions") return self.client.get(url, params=params) def remove_permissions(self, id: str, permissions: List[str]) -> Any: """Removes permissions from a user. Args: id (str): The user's id. permissions (list of str): A list of permission ids to unassociate from the user. See https://auth0.com/docs/api/management/v2#!/Users/delete_permissions """ url = self._url(f"{id}/permissions") body = {"permissions": permissions} return self.client.delete(url, data=body) def add_permissions(self, id: str, permissions: List[str]) -> dict[str, Any]: """Assign permissions to a user. Args: id (str): The user's id. permissions (list of str): A list of permission ids to associated with the user. See https://auth0.com/docs/api/management/v2#!/Users/post_permissions """ url = self._url(f"{id}/permissions") body = {"permissions": permissions} return self.client.post(url, data=body) def delete_multifactor(self, id: str, provider: str) -> Any: """Delete a user's multifactor provider. Args: id (str): The user's id. provider (str): The multifactor provider. Supported values 'duo' or 'google-authenticator'. See: https://auth0.com/docs/api/management/v2#!/Users/delete_multifactor_by_provider """ url = self._url(f"{id}/multifactor/{provider}") return self.client.delete(url) def delete_authenticators(self, id: str) -> Any: """Delete a user's MFA enrollments. Args: id (str): The user's id. See: https://auth0.com/docs/api/management/v2#!/Users/delete_authenticators """ url = self._url(f"{id}/authenticators") return self.client.delete(url) def unlink_user_account(self, id: str, provider: str, user_id: str) -> Any: """Unlink a user account Args: id (str): The user_id of the user identity. provider (str): The type of identity provider (e.g: facebook). user_id (str): The unique identifier for the user for the identity. See: https://auth0.com/docs/api/management/v2#!/Users/delete_user_identity_by_user_id """ url = self._url(f"{id}/identities/{provider}/{user_id}") return self.client.delete(url) def link_user_account(self, user_id: str, body: dict[str, Any]) -> list[dict[str, Any]]: """Link user accounts. Links the account specified in the body (secondary account) to the account specified by the id param of the URL (primary account). Args: id (str): The user_id of the primary identity where you are linking the secondary account to. body (dict): the attributes to send as part of this request. See: https://auth0.com/docs/api/v2#!/Users/post_identities """ url = self._url(f"{user_id}/identities") return self.client.post(url, data=body) def regenerate_recovery_code(self, user_id: str) -> dict[str, Any]: """Removes the current recovery token, generates and returns a new one Args: user_id (str): The user_id of the user identity. See: https://auth0.com/docs/api/management/v2#!/Users/post_recovery_code_regeneration """ url = self._url(f"{user_id}/recovery-code-regeneration") return self.client.post(url) def get_guardian_enrollments(self, user_id: str) -> dict[str, Any]: """Retrieve the first confirmed Guardian enrollment for a user. Args: user_id (str): The user_id of the user to retrieve. See: https://auth0.com/docs/api/management/v2#!/Users/get_enrollments """ url = self._url(f"{user_id}/enrollments") return self.client.get(url) def get_log_events( self, user_id: str, page: int = 0, per_page: int = 50, sort: str | None = None, include_totals: bool = False, ): """Retrieve every log event for a specific user id. Args: user_id (str): The user_id of the logs to retrieve. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 50 results per page. sort (str, optional): The field to use for sorting. Use field:order where order is 1 for ascending and -1 for descending. For example date:-1 When not set, the default value is up to the server. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to False. See: https://auth0.com/docs/api/management/v2#!/Users/get_logs_by_user """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), "sort": sort, } url = self._url(f"{user_id}/logs") return self.client.get(url, params=params) def invalidate_remembered_browsers(self, user_id: str) -> dict[str, Any]: """Invalidate all remembered browsers across all authentication factors for a user. Args: user_id (str): The user_id to invalidate remembered browsers for. See: https://auth0.com/docs/api/management/v2#!/Users/post_invalidate_remember_browser """ url = self._url(f"{user_id}/multifactor/actions/invalidate-remember-browser") return self.client.post(url) def get_authentication_methods(self, user_id: str) -> dict[str, Any]: """Gets a list of authentication methods Args: user_id (str): The user_id to get a list of authentication methods for. See: https://auth0.com/docs/api/management/v2#!/Users/get_authentication_methods """ url = self._url(f"{user_id}/authentication-methods") return self.client.get(url) def get_authentication_method_by_id( self, user_id: str, authentication_method_id: str ) -> dict[str, Any]: """Gets an authentication method by ID. Args: user_id (str): The user_id to get an authentication method by ID for. authentication_method_id (str): The authentication_method_id to get an authentication method by ID for. See: https://auth0.com/docs/api/management/v2#!/Users/get_authentication_methods_by_authentication_method_id """ url = self._url(f"{user_id}/authentication-methods/{authentication_method_id}") return self.client.get(url) def create_authentication_method( self, user_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Creates an authentication method for a given user. Args: user_id (str): The user_id to create an authentication method for a given user. body (dict): the request body to create an authentication method for a given user. See: https://auth0.com/docs/api/management/v2#!/Users/post_authentication_methods """ url = self._url(f"{user_id}/authentication-methods") return self.client.post(url, data=body) def update_authentication_methods( self, user_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Updates all authentication methods for a user by replacing them with the given ones. Args: user_id (str): The user_id to update all authentication methods for. body (dict): the request body to update all authentication methods with. See: https://auth0.com/docs/api/management/v2#!/Users/put_authentication_methods """ url = self._url(f"{user_id}/authentication-methods") return self.client.put(url, data=body) def update_authentication_method_by_id( self, user_id: str, authentication_method_id: str, body: dict[str, Any] ) -> dict[str, Any]: """Updates an authentication method. Args: user_id (str): The user_id to update an authentication method. authentication_method_id (str): The authentication_method_id to update an authentication method for. body (dict): the request body to update an authentication method. See: https://auth0.com/docs/api/management/v2#!/Users/patch_authentication_methods_by_authentication_method_id """ url = self._url(f"{user_id}/authentication-methods/{authentication_method_id}") return self.client.patch(url, data=body) def delete_authentication_methods(self, user_id: str) -> Any: """Deletes all authentication methods for the given user. Args: user_id (str): The user_id to delete all authentication methods for the given user for. See: https://auth0.com/docs/api/management/v2#!/Users/delete_authentication_methods """ url = self._url(f"{user_id}/authentication-methods") return self.client.delete(url) def delete_authentication_method_by_id( self, user_id: str, authentication_method_id: str ) -> Any: """Deletes an authentication method by ID. Args: user_id (str): The user_id to delete an authentication method by ID for. authentication_method_id (str): The authentication_method_id to delete an authentication method by ID for. See: https://auth0.com/docs/api/management/v2#!/Users/delete_authentication_methods_by_authentication_method_id """ url = self._url(f"{user_id}/authentication-methods/{authentication_method_id}") return self.client.delete(url) def list_tokensets( self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True ): """List all the tokenset(s) associated to the user. Args: id (str): The user's id. page (int, optional): The result's page number (zero based). By default, retrieves the first page of results. per_page (int, optional): The amount of entries per page. By default, retrieves 25 results per page. include_totals (bool, optional): True if the query summary is to be included in the result, False otherwise. Defaults to True. See https://auth0.com/docs/api/management/v2#!/Users/get_tokensets """ params = { "per_page": per_page, "page": page, "include_totals": str(include_totals).lower(), } url = self._url(f"{id}/federated-connections-tokensets") return self.client.get(url, params=params) def delete_tokenset_by_id( self, user_id: str, tokenset_id: str ) -> Any: """Deletes an tokenset by ID. Args: user_id (str): The user_id to delete an authentication method by ID for. tokenset_id (str): The tokenset_id to delete an tokenset by ID for. See: https://auth0.com/docs/api/management/v2#!/Users/delete_tokenset_by_id """ url = self._url(f"{user_id}/federated-connections-tokensets/{tokenset_id}") return self.client.delete(url)auth0-auth0-python-0f6dbea/auth0/management/users_by_email.py000066400000000000000000000045641506260514000243630ustar00rootroot00000000000000from __future__ import annotations from typing import Any from ..rest import RestClient, RestClientOptions from ..types import TimeoutType class UsersByEmail: """Auth0 users by email endpoints Args: domain (str): Your Auth0 domain, e.g: 'username.auth0.com' token (str): Management API v2 Token telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) protocol (str, optional): Protocol to use when making requests. (defaults to "https") rest_options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. (defaults to None) """ def __init__( self, domain: str, token: str, telemetry: bool = True, timeout: TimeoutType = 5.0, protocol: str = "https", rest_options: RestClientOptions | None = None, ) -> None: self.domain = domain self.protocol = protocol self.client = RestClient( jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options ) def _url(self) -> str: return f"{self.protocol}://{self.domain}/api/v2/users-by-email" def search_users_by_email( self, email: str, fields: list[str] | None = None, include_fields: bool = True ) -> list[dict[str, Any]]: """List or search users. Args: email: Email to search. fields (list of str, optional): A list of fields to include or exclude from the result (depending on include_fields). Leave empty to retrieve all fields. include_fields (bool, optional): True if the fields specified are to be include in the result, False otherwise. See: https://auth0.com/docs/api/management/v2#!/Users_By_Email/get_users_by_email """ params = { "email": email, "fields": fields and ",".join(fields) or None, "include_fields": str(include_fields).lower(), } return self.client.get(self._url(), params=params) auth0-auth0-python-0f6dbea/auth0/rest.py000066400000000000000000000303431506260514000202140ustar00rootroot00000000000000from __future__ import annotations import base64 import platform import sys from json import dumps, loads from random import randint from time import sleep from typing import TYPE_CHECKING, Any, Mapping from urllib.parse import urlencode import requests from auth0.exceptions import Auth0Error, RateLimitError from auth0.types import RequestData, TimeoutType if TYPE_CHECKING: from auth0.rest_async import RequestsResponse UNKNOWN_ERROR = "a0.sdk.internal.unknown" class RestClientOptions: """Configuration object for RestClient. Used for configuring additional RestClient options, such as rate-limit retries. Args: telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) retries (integer): In the event an API request returns a 429 response header (indicating rate-limit has been hit), the RestClient will retry the request this many times using an exponential backoff strategy, before raising a RateLimitError exception. 10 retries max. (defaults to 3) """ def __init__( self, telemetry: bool = True, timeout: TimeoutType = 5.0, retries: int = 3, ) -> None: self.telemetry = telemetry self.timeout = timeout self.retries = retries class RestClient: """Provides simple methods for handling all RESTful api endpoints. Args: jwt (str, optional): The JWT to be used with the RestClient. telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. Overrides matching options passed to the constructor. (defaults to 3) """ def __init__( self, jwt: str | None, telemetry: bool = True, timeout: TimeoutType = 5.0, options: RestClientOptions | None = None, ) -> None: if options is None: options = RestClientOptions(telemetry=telemetry, timeout=timeout) self.options = options self.jwt = jwt self._metrics = {"retries": 0, "backoff": []} self._skip_sleep = False self.base_headers = { "Content-Type": "application/json", } if jwt is not None: self.base_headers["Authorization"] = f"Bearer {self.jwt}" if options.telemetry: py_version = platform.python_version() version = sys.modules["auth0"].__version__ auth0_client = dumps( { "name": "auth0-python", "version": version, "env": { "python": py_version, }, } ).encode("utf-8") self.base_headers.update( { "User-Agent": f"Python/{py_version}", "Auth0-Client": base64.b64encode(auth0_client).decode(), } ) # Cap the maximum number of retries to 10 or fewer. Floor the retries at 0. self._retries = min(self.MAX_REQUEST_RETRIES(), max(0, options.retries)) # For backwards compatibility reasons only # TODO: Deprecate in the next major so we can prune these arguments. Guidance should be to use RestClient.options.* self.telemetry = options.telemetry self.timeout = options.timeout # Returns a hard cap for the maximum number of retries allowed (10) def MAX_REQUEST_RETRIES(self) -> int: return 10 # Returns the maximum amount of jitter to introduce in milliseconds (100ms) def MAX_REQUEST_RETRY_JITTER(self) -> int: return 100 # Returns the maximum delay window allowed (1000ms) def MAX_REQUEST_RETRY_DELAY(self) -> int: return 1000 # Returns the minimum delay window allowed (100ms) def MIN_REQUEST_RETRY_DELAY(self) -> int: return 100 def _request( self, method: str, url: str, params: dict[str, Any] | None = None, data: RequestData | None = None, json: RequestData | None = None, headers: dict[str, str] | None = None, files: dict[str, Any] | None = None, ) -> Any: # Track the API request attempt number attempt = 0 # Reset the metrics tracker self._metrics = {"retries": 0, "backoff": []} if data is None and json is not None and headers: content_type = headers.get("Content-Type", "").lower() # Get Content-Type if "application/x-www-form-urlencoded" in content_type: data = urlencode(json) # Copy JSON data into data json = None # Prevent JSON from being sent kwargs = { k: v for k, v in { "params": params, "json": json, "data": data, "headers": headers, "files": files, "timeout": self.options.timeout, }.items() if v is not None } while True: # Increment attempt number attempt += 1 # Issue the request response = requests.request(method, url, **kwargs) # If the response did not have a 429 header, or the attempt number is greater than the configured retries, break if response.status_code != 429 or attempt > self._retries: break wait = self._calculate_wait(attempt) # Skip calling sleep() when running unit tests if self._skip_sleep is False: # sleep() functions in seconds, so convert the milliseconds formula above accordingly sleep(wait / 1000) # Return the final Response return self._process_response(response) def get( self, url: str, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, ) -> Any: request_headers = self.base_headers.copy() request_headers.update(headers or {}) return self._request("GET", url, params=params, headers=request_headers) def post( self, url: str, data: RequestData | None = None, headers: dict[str, str] | None = None, ) -> Any: request_headers = self.base_headers.copy() request_headers.update(headers or {}) return self._request("POST", url, json=data, headers=request_headers) def file_post( self, url: str, data: RequestData | None = None, files: dict[str, Any] | None = None, ) -> Any: headers = self.base_headers.copy() headers.pop("Content-Type", None) return self._request("POST", url, data=data, files=files, headers=headers) def patch(self, url: str, data: RequestData | None = None) -> Any: headers = self.base_headers.copy() return self._request("PATCH", url, json=data, headers=headers) def put(self, url: str, data: RequestData | None = None) -> Any: headers = self.base_headers.copy() return self._request("PUT", url, json=data, headers=headers) def delete( self, url: str, params: dict[str, Any] | None = None, data: RequestData | None = None, ) -> Any: headers = self.base_headers.copy() return self._request("DELETE", url, params=params, json=data, headers=headers) def _calculate_wait(self, attempt: int) -> int: # Retry the request. Apply a exponential backoff for subsequent attempts, using this formula: # max(MIN_REQUEST_RETRY_DELAY, min(MAX_REQUEST_RETRY_DELAY, (100ms * (2 ** attempt - 1)) + random_between(1, MAX_REQUEST_RETRY_JITTER))) # Increases base delay by (100ms * (2 ** attempt - 1)) wait = 100 * 2 ** (attempt - 1) # Introduces jitter to the base delay; increases delay between 1ms to MAX_REQUEST_RETRY_JITTER (100ms) wait += randint(1, self.MAX_REQUEST_RETRY_JITTER()) # Is never more than MAX_REQUEST_RETRY_DELAY (1s) wait = min(self.MAX_REQUEST_RETRY_DELAY(), wait) # Is never less than MIN_REQUEST_RETRY_DELAY (100ms) wait = max(self.MIN_REQUEST_RETRY_DELAY(), wait) self._metrics["retries"] = attempt self._metrics["backoff"].append(wait) # type: ignore[attr-defined] return wait def _process_response(self, response: requests.Response) -> Any: return self._parse(response).content() def _parse(self, response: requests.Response) -> Response: if not response.text: return EmptyResponse(response.status_code) try: return JsonResponse(response) except ValueError: return PlainResponse(response) class Response: def __init__( self, status_code: int, content: Any, headers: Mapping[str, str] ) -> None: self._status_code = status_code self._content = content self._headers = headers def content(self) -> Any: if self._is_error(): if self._status_code == 429: reset_at = int(self._headers.get("x-ratelimit-reset", "-1")) raise RateLimitError( error_code=self._error_code(), message=self._error_message(), reset_at=reset_at, headers=self._headers, ) if self._error_code() == "mfa_required": raise Auth0Error( status_code=self._status_code, error_code=self._error_code(), message=self._error_message(), content=self._content, headers=self._headers ) raise Auth0Error( status_code=self._status_code, error_code=self._error_code(), message=self._error_message(), headers=self._headers ) else: return self._content def _is_error(self) -> bool: return self._status_code is None or self._status_code >= 400 # Adding these methods to force implementation in subclasses because they are references in this parent class def _error_code(self): raise NotImplementedError def _error_message(self): raise NotImplementedError class JsonResponse(Response): def __init__(self, response: requests.Response | RequestsResponse) -> None: content = loads(response.text) super().__init__(response.status_code, content, response.headers) def _error_code(self) -> str: if "errorCode" in self._content: return self._content.get("errorCode") elif "error" in self._content: return self._content.get("error") elif "code" in self._content: return self._content.get("code") else: return UNKNOWN_ERROR def _error_message(self) -> str: if "error_description" in self._content: return self._content.get("error_description") message = self._content.get("message", "") if message is not None and message != "": return message return self._content.get("error", "") class PlainResponse(Response): def __init__(self, response: requests.Response | RequestsResponse) -> None: super().__init__(response.status_code, response.text, response.headers) def _error_code(self) -> str: return UNKNOWN_ERROR def _error_message(self) -> str: return self._content class EmptyResponse(Response): def __init__(self, status_code: int) -> None: super().__init__(status_code, "", {}) def _error_code(self) -> str: return UNKNOWN_ERROR def _error_message(self) -> str: return "" auth0-auth0-python-0f6dbea/auth0/rest_async.py000066400000000000000000000132531506260514000214120ustar00rootroot00000000000000from __future__ import annotations import asyncio from typing import Any import aiohttp from auth0.exceptions import RateLimitError from auth0.types import RequestData from .rest import EmptyResponse, JsonResponse, PlainResponse, Response, RestClient def _clean_params(params: dict[Any, Any] | None) -> dict[Any, Any] | None: if params is None: return params return {k: v for k, v in params.items() if v is not None} class AsyncRestClient(RestClient): """Provides simple methods for handling all RESTful api endpoints. Args: telemetry (bool, optional): Enable or disable Telemetry (defaults to True) timeout (float or tuple, optional): Change the requests connect and read timeout. Pass a tuple to specify both values separately or a float to set both to it. (defaults to 5.0 for both) options (RestClientOptions): Pass an instance of RestClientOptions to configure additional RestClient options, such as rate-limit retries. Overrides matching options passed to the constructor. (defaults to 3) """ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._session: aiohttp.ClientSession | None = None sock_connect, sock_read = ( self.timeout if isinstance(self.timeout, tuple) else (self.timeout, self.timeout) ) self.timeout = aiohttp.ClientTimeout( sock_connect=sock_connect, sock_read=sock_read ) # type: ignore[assignment] def set_session(self, session: aiohttp.ClientSession) -> None: """Set Client Session to improve performance by reusing session. Session should be closed manually or within context manager. """ self._session = session async def _request_with_session( self, session: aiohttp.ClientSession, *args: Any, **kwargs: Any ) -> Any: # Track the API request attempt number attempt = 0 # Reset the metrics tracker self._metrics = {"retries": 0, "backoff": []} while True: # Increment attempt number attempt += 1 try: async with session.request(*args, **kwargs) as response: return await self._process_response(response) except RateLimitError as e: # If the attempt number is greater than the configured retries, raise RateLimitError if attempt > self._retries: raise e wait = self._calculate_wait(attempt) # Skip calling sleep() when running unit tests if self._skip_sleep is False: # sleep() functions in seconds, so convert the milliseconds formula above accordingly await asyncio.sleep(wait / 1000) async def _request(self, *args: Any, **kwargs: Any) -> Any: kwargs["headers"] = kwargs.get("headers", self.base_headers) kwargs["timeout"] = self.timeout if self._session is not None: # Request with re-usable session return await self._request_with_session(self._session, *args, **kwargs) else: # Request without re-usable session async with aiohttp.ClientSession() as session: return await self._request_with_session(session, *args, **kwargs) async def get( self, url: str, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, ) -> Any: request_headers = self.base_headers.copy() request_headers.update(headers or {}) return await self._request( "get", url, params=_clean_params(params), headers=request_headers ) async def post( self, url: str, data: RequestData | None = None, headers: dict[str, str] | None = None, ) -> Any: request_headers = self.base_headers.copy() request_headers.update(headers or {}) return await self._request("post", url, json=data, headers=request_headers) async def file_post( self, url: str, data: dict[str, Any], files: dict[str, Any], ) -> Any: headers = self.base_headers.copy() headers.pop("Content-Type") return await self._request("post", url, data={**data, **files}, headers=headers) async def patch(self, url: str, data: RequestData | None = None) -> Any: return await self._request("patch", url, json=data) async def put(self, url: str, data: RequestData | None = None) -> Any: return await self._request("put", url, json=data) async def delete( self, url: str, params: dict[str, Any] | None = None, data: RequestData | None = None, ) -> Any: return await self._request( "delete", url, json=data, params=_clean_params(params) or {} ) async def _process_response(self, response: aiohttp.ClientResponse) -> Any: parsed_response = await self._parse(response) return parsed_response.content() async def _parse(self, response: aiohttp.ClientResponse) -> Response: text = await response.text() requests_response = RequestsResponse(response, text) if not text: return EmptyResponse(response.status) try: return JsonResponse(requests_response) except ValueError: return PlainResponse(requests_response) class RequestsResponse: def __init__(self, response: aiohttp.ClientResponse, text: str) -> None: self.status_code = response.status self.headers = response.headers self.text = text auth0-auth0-python-0f6dbea/auth0/test/000077500000000000000000000000001506260514000176415ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/__init__.py000066400000000000000000000000001506260514000217400ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/authentication/000077500000000000000000000000001506260514000226605ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/authentication/__init__.py000066400000000000000000000000001506260514000247570ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/authentication/test_back_channel_login.py000066400000000000000000000201361506260514000300530ustar00rootroot00000000000000 import unittest from unittest import mock import json import requests from ...exceptions import Auth0Error, RateLimitError from ...authentication.back_channel_login import BackChannelLogin class TestBackChannelLogin(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_ciba(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") g.back_channel_login( binding_message="This is a binding message", login_hint="{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }", scope="openid", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/bc-authorize") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "binding_message": "This is a binding message", "login_hint": "{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }", "scope": "openid", }, ) @mock.patch("requests.request") def test_server_error(self, mock_requests_request): response = requests.Response() response.status_code = 400 response._content = b'{"error":"foo"}' mock_requests_request.return_value = response g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") with self.assertRaises(Auth0Error) as context: g.back_channel_login( binding_message="msg", login_hint="hint", scope="openid" ) self.assertEqual(context.exception.status_code, 400) self.assertEqual(context.exception.message, 'foo') @mock.patch("auth0.rest.RestClient.post") def test_should_require_binding_message(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") # Expecting an exception to be raised when binding_message is missing with self.assertRaises(Exception) as context: g.back_channel_login( login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }', scope="openid", ) # Assert the error message is correct self.assertIn("missing 1 required positional argument: \'binding_message\'", str(context.exception)) @mock.patch("auth0.rest.RestClient.post") def test_should_require_login_hint(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") # Expecting an exception to be raised when login_hint is missing with self.assertRaises(Exception) as context: g.back_channel_login( binding_message="This is a binding message.", scope="openid", ) # Assert the error message is correct self.assertIn("missing 1 required positional argument: \'login_hint\'", str(context.exception)) @mock.patch("auth0.rest.RestClient.post") def test_should_require_scope(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") # Expecting an exception to be raised when scope is missing with self.assertRaises(Exception) as context: g.back_channel_login( binding_message="This is a binding message.", login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }', ) # Assert the error message is correct self.assertIn("missing 1 required positional argument: \'scope\'", str(context.exception)) @mock.patch("auth0.rest.RestClient.post") def test_with_authorization_details(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") g.back_channel_login( binding_message="This is a binding message.", login_hint= json.dumps({"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}), scope="openid", authorization_details=[ { "type":"payment_initiation","locations":["https://example.com/payments"], "instructedAmount": { "currency":"EUR","amount":"123.50" }, "creditorName":"Merchant A", "creditorAccount": { "bic":"ABCIDEFFXXX", "iban":"DE021001001093071118603" }, "remittanceInformationUnstructured":"Ref Number Merchant" } ], ) args, kwargs = mock_post.call_args expected_data = { "client_id": "cid", "client_secret": "clsec", "binding_message": "This is a binding message.", "login_hint": json.dumps({"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}), "scope": "openid", "authorization_details": json.dumps([ { "type":"payment_initiation","locations":["https://example.com/payments"], "instructedAmount": { "currency":"EUR","amount":"123.50" }, "creditorName":"Merchant A", "creditorAccount": { "bic":"ABCIDEFFXXX", "iban":"DE021001001093071118603" }, "remittanceInformationUnstructured":"Ref Number Merchant" } ]), } actual_data = kwargs["data"] self.assertEqual(args[0], "https://my.domain.com/bc-authorize") self.assertEqual( actual_data, expected_data, "Request data does not match expected data after JSON serialization." ) @mock.patch("auth0.rest.RestClient.post") def test_with_request_expiry(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") g.back_channel_login( binding_message="This is a binding message", login_hint="{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }", scope="openid", requested_expiry=100 ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/bc-authorize") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "binding_message": "This is a binding message", "login_hint": "{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }", "scope": "openid", "requested_expiry": "100", }, ) def test_requested_expiry_negative_raises(self): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") with self.assertRaises(ValueError): g.back_channel_login( binding_message="msg", login_hint="hint", scope="openid", requested_expiry=-10 ) def test_requested_expiry_zero_raises(self): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") with self.assertRaises(ValueError): g.back_channel_login( binding_message="msg", login_hint="hint", scope="openid", requested_expiry=0 ) def test_requested_non_int_raises(self): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") with self.assertRaises(ValueError): g.back_channel_login( binding_message="msg", login_hint="hint", scope="openid", requested_expiry="string_instead_of_int" ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_base.py000066400000000000000000000307371506260514000252150ustar00rootroot00000000000000import base64 import json import sys import unittest from unittest import mock import requests from ...authentication.base import AuthenticationBase from ...exceptions import Auth0Error, RateLimitError class TestBase(unittest.TestCase): def test_telemetry_enabled_by_default(self): ab = AuthenticationBase("auth0.com", "cid") base_headers = ab.client.base_headers user_agent = base_headers["User-Agent"] auth0_client_bytes = base64.b64decode(base_headers["Auth0-Client"]) auth0_client_json = auth0_client_bytes.decode("utf-8") auth0_client = json.loads(auth0_client_json) content_type = base_headers["Content-Type"] from auth0 import __version__ as auth0_version python_version = "{}.{}.{}".format( sys.version_info.major, sys.version_info.minor, sys.version_info.micro ) client_info = { "name": "auth0-python", "version": auth0_version, "env": {"python": python_version}, } self.assertEqual(user_agent, f"Python/{python_version}") self.assertEqual(auth0_client, client_info) self.assertEqual(content_type, "application/json") def test_telemetry_disabled(self): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) self.assertEqual(ab.client.base_headers, {"Content-Type": "application/json"}) @mock.patch("requests.request") def test_post(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False, timeout=(10, 2)) mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' data = ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) mock_request.assert_called_with( "POST", "the-url", json={"a": "b"}, headers={"c": "d", "Content-Type": "application/json"}, timeout=(10, 2), ) self.assertEqual(data, {"x": "y"}) @mock.patch("requests.request") def test_post_with_defaults(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' # Only required params are passed data = ab.post("the-url") mock_request.assert_called_with( "POST", "the-url", headers={"Content-Type": "application/json"}, timeout=5.0, ) self.assertEqual(data, {"x": "y"}) @mock.patch("requests.request") def test_post_includes_telemetry(self, mock_request): ab = AuthenticationBase("auth0.com", "cid") mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' data = ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(mock_request.call_count, 1) call_args, call_kwargs = mock_request.call_args self.assertEqual(call_args[0], "POST") self.assertEqual(call_args[1], "the-url") self.assertEqual(call_kwargs["json"], {"a": "b"}) headers = call_kwargs["headers"] self.assertEqual(headers["c"], "d") self.assertEqual(headers["Content-Type"], "application/json") self.assertIn("User-Agent", headers) self.assertIn("Auth0-Client", headers) self.assertEqual(data, {"x": "y"}) @mock.patch("requests.request") def test_post_error(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = ( '{"error": "e0","error_description": "desc"}' ) with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "e0") self.assertEqual(context.exception.message, "desc") @mock.patch("requests.request") def test_post_error_mfa_required(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) mock_request.return_value.status_code = 403 mock_request.return_value.text = '{"error": "mfa_required", "error_description": "Multifactor authentication required", "mfa_token": "Fe26...Ha"}' with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, 403) self.assertEqual(context.exception.error_code, "mfa_required") self.assertEqual( context.exception.message, "Multifactor authentication required" ) self.assertEqual(context.exception.content.get("mfa_token"), "Fe26...Ha") @mock.patch("requests.request") def test_post_rate_limit_error(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 429, "error": "e0", "error_description": "desc"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "e0") self.assertEqual(context.exception.message, "desc") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertIsNotNone(context.exception.headers) self.assertEqual(context.exception.headers["x-ratelimit-limit"], "3") self.assertEqual(context.exception.headers["x-ratelimit-remaining"], "6") self.assertEqual(context.exception.headers["x-ratelimit-reset"], "9") @mock.patch("requests.request") def test_post_rate_limit_error_without_headers(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 429, "error": "e0", "error_description": "desc"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = {} with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "e0") self.assertEqual(context.exception.message, "desc") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, -1) self.assertIsNotNone(context.exception.headers) self.assertEqual(context.exception.headers, {}) @mock.patch("requests.request") def test_post_error_with_code_property(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = ( '{"code": "e0","error_description": "desc"}' ) with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "e0") self.assertEqual(context.exception.message, "desc") @mock.patch("requests.request") def test_post_error_with_no_error_code(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = '{"error_description": "desc"}' with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual(context.exception.message, "desc") @mock.patch("requests.request") def test_post_error_with_text_response(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = "there has been a terrible error" with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual( context.exception.message, "there has been a terrible error" ) @mock.patch("requests.request") def test_post_error_with_no_response_text(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = None with self.assertRaises(Auth0Error) as context: ab.post("the-url", data={"a": "b"}, headers={"c": "d"}) self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual(context.exception.message, "") @mock.patch("requests.request") def test_get(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False, timeout=(10, 2)) mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' data = ab.get("the-url", params={"a": "b"}, headers={"c": "d"}) mock_request.assert_called_with( "GET", "the-url", params={"a": "b"}, headers={"c": "d", "Content-Type": "application/json"}, timeout=(10, 2), ) self.assertEqual(data, {"x": "y"}) @mock.patch("requests.request") def test_get_with_defaults(self, mock_request): ab = AuthenticationBase("auth0.com", "cid", telemetry=False) mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' # Only required params are passed data = ab.get("the-url") mock_request.assert_called_with( "GET", "the-url", headers={"Content-Type": "application/json"}, timeout=5.0, ) self.assertEqual(data, {"x": "y"}) @mock.patch("requests.request") def test_get_includes_telemetry(self, mock_request): ab = AuthenticationBase("auth0.com", "cid") mock_request.return_value.status_code = 200 mock_request.return_value.text = '{"x": "y"}' data = ab.get("the-url", params={"a": "b"}, headers={"c": "d"}) self.assertEqual(mock_request.call_count, 1) call_args, call_kwargs = mock_request.call_args self.assertEqual(call_args[0], "GET") self.assertEqual(call_args[1], "the-url") self.assertEqual(call_kwargs["params"], {"a": "b"}) headers = call_kwargs["headers"] self.assertEqual(headers["c"], "d") self.assertEqual(headers["Content-Type"], "application/json") self.assertIn("User-Agent", headers) self.assertIn("Auth0-Client", headers) self.assertEqual(data, {"x": "y"}) # TODO: Replace the following with more reliable tests. Failing on GitHub Actions. # def test_get_can_timeout(self): # ab = AuthenticationBase("auth0.com", "cid", timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # ab.get("https://google.com", params={"a": "b"}, headers={"c": "d"}) # def test_post_can_timeout(self): # ab = AuthenticationBase("auth0.com", "cid", timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # ab.post("https://google.com", data={"a": "b"}, headers={"c": "d"}) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_database.py000066400000000000000000000061211506260514000260350ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.database import Database class TestDatabase(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_signup(self, mock_post): d = Database("my.domain.com", "cid") # using only email and password d.signup(email="a@b.com", password="pswd", connection="conn") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/dbconnections/signup") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "password": "pswd", "connection": "conn", }, ) # Using also optional properties sample_meta = {"hobby": "surfing", "preference": {"color": "pink"}} d.signup( email="a@b.com", password="pswd", connection="conn", username="usr", user_metadata=sample_meta, given_name="john", family_name="doe", name="john doe", nickname="johnny", picture="avatars.com/john-doe", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/dbconnections/signup") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "password": "pswd", "connection": "conn", "username": "usr", "user_metadata": sample_meta, "given_name": "john", "family_name": "doe", "name": "john doe", "nickname": "johnny", "picture": "avatars.com/john-doe", }, ) @mock.patch("auth0.rest.RestClient.post") def test_change_password(self, mock_post): d = Database("my.domain.com", "cid") # ignores the password argument d.change_password(email="a@b.com", password="pswd", connection="conn") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/dbconnections/change_password") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "connection": "conn", }, ) @mock.patch("auth0.rest.RestClient.post") def test_change_password_with_organization_param(self, mock_post): d = Database("my.domain.com", "cid") # ignores the password argument d.change_password( email="a@b.com", password="pswd", connection="conn", organization="org_id" ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/dbconnections/change_password") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "connection": "conn", "organization": "org_id", }, ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_delegated.py000066400000000000000000000040041506260514000262050ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.delegated import Delegated class TestDelegated(unittest.TestCase): @mock.patch("auth0.authentication.delegated.Delegated.post") def test_get_token_id_token(self, mock_post): d = Delegated("my.domain.com", "cid") d.get_token( target="tgt", api_type="apt", grant_type="gt", id_token="idt", scope="openid profile", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/delegation") self.assertEqual( kwargs["data"], { "client_id": "cid", "grant_type": "gt", "id_token": "idt", "target": "tgt", "scope": "openid profile", "api_type": "apt", }, ) @mock.patch("auth0.authentication.delegated.Delegated.post") def test_get_token_refresh_token(self, mock_post): d = Delegated("my.domain.com", "cid") d.get_token( target="tgt", api_type="apt", grant_type="gt", refresh_token="rtk", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/delegation") self.assertEqual( kwargs["data"], { "client_id": "cid", "grant_type": "gt", "refresh_token": "rtk", "target": "tgt", "scope": "openid", "api_type": "apt", }, ) @mock.patch("auth0.authentication.delegated.Delegated.post") def test_get_token_value_error(self, mock_post): d = Delegated("my.domain.com", "cid") with self.assertRaises(ValueError): d.get_token( target="tgt", api_type="apt", grant_type="gt", refresh_token="rtk", id_token="idt", ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_enterprise.py000066400000000000000000000014251506260514000264530ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.enterprise import Enterprise class TestEnterprise(unittest.TestCase): @mock.patch("auth0.authentication.enterprise.Enterprise.get") def test_saml_metadata(self, mock_get): e = Enterprise("my.domain.com", "cid") e.saml_metadata() mock_get.assert_called_with(url="https://my.domain.com/samlp/metadata/cid") @mock.patch("auth0.authentication.enterprise.Enterprise.get") def test_wsfed_metadata(self, mock_get): e = Enterprise("my.domain.com", "cid") e.wsfed_metadata() mock_get.assert_called_with( url=( "https://my.domain.com/wsfed/FederationMetadata" "/2007-06/FederationMetadata.xml" ) ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_get_token.py000066400000000000000000000326351506260514000262610ustar00rootroot00000000000000import unittest import requests from fnmatch import fnmatch from unittest import mock from unittest.mock import ANY from cryptography.hazmat.primitives import asymmetric, serialization from ...exceptions import RateLimitError from ...authentication.get_token import GetToken def get_private_key(): private_key = asymmetric.rsa.generate_private_key( public_exponent=65537, key_size=2048, ) return private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) class TestGetToken(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_authorization_code(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.authorization_code( code="cd", grant_type="gt", redirect_uri="idt", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "code": "cd", "grant_type": "gt", "redirect_uri": "idt", }, ) @mock.patch("auth0.rest.RestClient.post") def test_authorization_code_with_client_assertion(self, mock_post): g = GetToken( "my.domain.com", "cid", client_assertion_signing_key=get_private_key() ) g.authorization_code(code="cd", grant_type="gt", redirect_uri="idt") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_assertion": ANY, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "code": "cd", "grant_type": "gt", "redirect_uri": "idt", }, ) self.assertTrue(fnmatch(kwargs["data"]["client_assertion"], "*.*.*")) @mock.patch("auth0.rest.RestClient.post") def test_authorization_code_pkce(self, mock_post): g = GetToken("my.domain.com", "cid") g.authorization_code_pkce( code_verifier="cdver", code="cd", grant_type="gt", redirect_uri="idt", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "code_verifier": "cdver", "code": "cd", "grant_type": "gt", "redirect_uri": "idt", }, ) @mock.patch("auth0.rest.RestClient.post") def test_client_credentials(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.client_credentials(audience="aud", grant_type="gt") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "audience": "aud", "grant_type": "gt", "organization": None, }, ) @mock.patch("auth0.rest.RestClient.post") def test_client_credentials_with_client_assertion(self, mock_post): g = GetToken( "my.domain.com", "cid", client_assertion_signing_key=get_private_key() ) g.client_credentials("aud", grant_type="gt") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_assertion": ANY, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "audience": "aud", "grant_type": "gt", "organization": None, }, ) self.assertTrue(fnmatch(kwargs["data"]["client_assertion"], "*.*.*")) @mock.patch("auth0.rest.RestClient.post") def test_client_credentials_with_organization(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.client_credentials("aud", organization="my-org") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "grant_type": "client_credentials", "client_secret": "clsec", "audience": "aud", "organization": "my-org", }, ) @mock.patch("auth0.rest.RestClient.post") def test_login(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.login( username="usrnm", password="pswd", scope="http://test.com/api", realm="rlm", audience="aud", grant_type="gt", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "username": "usrnm", "password": "pswd", "scope": "http://test.com/api", "realm": "rlm", "audience": "aud", "grant_type": "gt", }, ) @mock.patch("auth0.rest.RestClient.post") def test_login_simple(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.login( username="usrnm", password="pswd", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "username": "usrnm", "password": "pswd", "realm": None, "scope": None, "audience": None, "grant_type": "http://auth0.com/oauth/grant-type/password-realm", }, ) @mock.patch("auth0.rest.RestClient.post") def test_login_with_forwarded_for(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.login(username="usrnm", password="pswd", forwarded_for="192.168.0.1") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["headers"], { "auth0-forwarded-for": "192.168.0.1", }, ) @mock.patch("auth0.rest.RestClient.post") def test_refresh_token(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="clsec") g.refresh_token( refresh_token="rt", grant_type="gt", scope="s", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "clsec", "refresh_token": "rt", "grant_type": "gt", "scope": "s", }, ) @mock.patch("auth0.rest.RestClient.post") def test_passwordless_login_with_sms(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") g.passwordless_login( username="123456", otp="abcd", realm="sms", audience="aud", scope="openid", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "csec", "realm": "sms", "grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp", "username": "123456", "otp": "abcd", "audience": "aud", "scope": "openid", }, ) @mock.patch("auth0.rest.RestClient.post") def test_passwordless_login_with_email(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") g.passwordless_login( username="a@b.c", otp="abcd", realm="email", audience="aud", scope="openid", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "csec", "realm": "email", "grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp", "username": "a@b.c", "otp": "abcd", "audience": "aud", "scope": "openid", }, ) @mock.patch("auth0.rest.RestClient.post") def test_backchannel_login(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") g.backchannel_login( auth_req_id="reqid", grant_type="urn:openid:params:grant-type:ciba", ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "csec", "auth_req_id": "reqid", "grant_type": "urn:openid:params:grant-type:ciba", }, ) @mock.patch("requests.request") def test_backchannel_login_headers_on_slow_down(self, mock_requests_request): response = requests.Response() response.status_code = 429 response.headers = {"Retry-After": "100"} response._content = b'{"error":"slow_down"}' mock_requests_request.return_value = response g = GetToken("my.domain.com", "cid", client_secret="csec") with self.assertRaises(RateLimitError) as context: g.backchannel_login( auth_req_id="reqid", grant_type="urn:openid:params:grant-type:ciba", ) self.assertEqual(context.exception.headers["Retry-After"], "100") self.assertEqual(context.exception.status_code, 429) @mock.patch("auth0.rest.RestClient.post") def test_connection_login(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") g.access_token_for_connection( grant_type="urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", subject_token_type="urn:ietf:params:oauth:token-type:refresh_token", subject_token="refid", requested_token_type="http://auth0.com/oauth/token-type/federated-connection-access-token", connection="google-oauth2" ) args, kwargs = mock_post.call_args print(kwargs["data"]) self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "grant_type": "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", "client_id": "cid", "client_secret": "csec", "subject_token_type": "urn:ietf:params:oauth:token-type:refresh_token", "subject_token": "refid", "requested_token_type": "http://auth0.com/oauth/token-type/federated-connection-access-token", "connection": "google-oauth2" }, ) @mock.patch("auth0.rest.RestClient.post") def test_connection_login_with_login_hint(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") g.access_token_for_connection( subject_token_type="urn:ietf:params:oauth:token-type:refresh_token", subject_token="refid", requested_token_type="http://auth0.com/oauth/token-type/federated-connection-access-token", connection="google-oauth2", login_hint="john.doe@example.com" ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/token") self.assertEqual( kwargs["data"], { "grant_type": "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", "client_id": "cid", "client_secret": "csec", "subject_token_type": "urn:ietf:params:oauth:token-type:refresh_token", "subject_token": "refid", "requested_token_type": "http://auth0.com/oauth/token-type/federated-connection-access-token", "connection": "google-oauth2", "login_hint": "john.doe@example.com" }, )auth0-auth0-python-0f6dbea/auth0/test/authentication/test_passwordless.py000066400000000000000000000057361506260514000270350ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.passwordless import Passwordless class TestPasswordless(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_send_email(self, mock_post): p = Passwordless("my.domain.com", "cid") p.email(email="a@b.com", send="snd") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/passwordless/start") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "send": "snd", "connection": "email", }, ) @mock.patch("auth0.rest.RestClient.post") def test_send_email_with_auth_params(self, mock_post): p = Passwordless("my.domain.com", "cid") p.email(email="a@b.com", send="snd", auth_params={"a": "b"}) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/passwordless/start") self.assertEqual( kwargs["data"], { "client_id": "cid", "email": "a@b.com", "send": "snd", "authParams": {"a": "b"}, "connection": "email", }, ) @mock.patch("auth0.rest.RestClient.post") def test_send_email_with_client_secret(self, mock_post): p = Passwordless("my.domain.com", "cid", client_secret="csecret") p.email(email="a@b.com", send="snd") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/passwordless/start") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "csecret", "email": "a@b.com", "send": "snd", "connection": "email", }, ) @mock.patch("auth0.rest.RestClient.post") def test_send_sms(self, mock_post): p = Passwordless("my.domain.com", "cid") p.sms(phone_number="123456") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/passwordless/start") self.assertEqual( kwargs["data"], { "client_id": "cid", "phone_number": "123456", "connection": "sms", }, ) @mock.patch("auth0.rest.RestClient.post") def test_send_sms_with_client_secret(self, mock_post): p = Passwordless("my.domain.com", "cid", client_secret="csecret") p.sms(phone_number="123456") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/passwordless/start") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "csecret", "phone_number": "123456", "connection": "sms", }, ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_pushed_authorization_requests.py000066400000000000000000000070111506260514000324730ustar00rootroot00000000000000import unittest import json from unittest import mock from ...authentication.pushed_authorization_requests import PushedAuthorizationRequests class TestRevokeToken(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_par(self, mock_post): a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!") a.pushed_authorization_request( response_type="code", redirect_uri="https://example.com/callback" ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/par") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "sh!", "response_type": "code", "redirect_uri": "https://example.com/callback", }, ) @mock.patch("auth0.rest.RestClient.post") def test_par_custom_params(self, mock_post): a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!") a.pushed_authorization_request( response_type="code", redirect_uri="https://example.com/callback", foo="bar" ) args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/par") self.assertEqual( kwargs["data"], { "client_id": "cid", "client_secret": "sh!", "response_type": "code", "redirect_uri": "https://example.com/callback", "foo": "bar", }, ) @mock.patch("auth0.rest.RestClient.post") def test_with_authorization_details(self, mock_post): a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!") a.pushed_authorization_request( response_type="code", redirect_uri="https://example.com/callback", authorization_details=[{"type": "money_transfer", "instructedAmount": {"amount": 2500, "currency": "USD"}}], ) args, kwargs = mock_post.call_args expected_data = { "client_id": "cid", "client_secret": "sh!", "response_type": "code", "redirect_uri": "https://example.com/callback", "authorization_details": [{"type": "money_transfer", "instructedAmount": {"amount": 2500, "currency": "USD"}}], } actual_data = kwargs["data"] self.assertEqual(args[0], "https://my.domain.com/oauth/par") self.assertEqual( json.dumps(actual_data, sort_keys=True), json.dumps(expected_data, sort_keys=True) ) @mock.patch("auth0.rest.RestClient.post") def test_jar(self, mock_post): a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!") a.pushed_authorization_request( response_type="code", redirect_uri="https://example.com/callback", request="my-jwt-request", ) args, kwargs = mock_post.call_args expected_data = { "client_id": "cid", "client_secret": "sh!", "response_type": "code", "redirect_uri": "https://example.com/callback", "request": 'my-jwt-request', } actual_data = kwargs["data"] self.assertEqual(args[0], "https://my.domain.com/oauth/par") self.assertEqual( json.dumps(actual_data, sort_keys=True), json.dumps(expected_data, sort_keys=True) )auth0-auth0-python-0f6dbea/auth0/test/authentication/test_revoke_token.py000066400000000000000000000016501506260514000267660ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.revoke_token import RevokeToken class TestRevokeToken(unittest.TestCase): @mock.patch("auth0.rest.RestClient.post") def test_revoke_refresh_token(self, mock_post): a = RevokeToken("my.domain.com", "cid") # regular apps a.revoke_refresh_token(token="tkn") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/revoke") self.assertEqual(kwargs["data"], {"client_id": "cid", "token": "tkn"}) # confidential apps a = RevokeToken("my.domain.com", "cid", client_secret="sh!") a.revoke_refresh_token(token="tkn") args, kwargs = mock_post.call_args self.assertEqual(args[0], "https://my.domain.com/oauth/revoke") self.assertEqual( kwargs["data"], {"client_id": "cid", "token": "tkn", "client_secret": "sh!"} ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_social.py000066400000000000000000000024251506260514000255460ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.social import Social class TestSocial(unittest.TestCase): @mock.patch("auth0.authentication.social.Social.post") def test_login(self, mock_post): s = Social("a.b.c", "cid") s.login(access_token="atk", connection="conn") args, kwargs = mock_post.call_args self.assertEqual("https://a.b.c/oauth/access_token", args[0]) self.assertEqual( kwargs["data"], { "client_id": "cid", "access_token": "atk", "connection": "conn", "scope": "openid", }, ) @mock.patch("auth0.authentication.social.Social.post") def test_login_with_scope(self, mock_post): s = Social("a.b.c", "cid") s.login( access_token="atk", connection="conn", scope="openid profile", ) args, kwargs = mock_post.call_args self.assertEqual("https://a.b.c/oauth/access_token", args[0]) self.assertEqual( kwargs["data"], { "client_id": "cid", "access_token": "atk", "connection": "conn", "scope": "openid profile", }, ) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_token_verifier.py000066400000000000000000001062741506260514000273160ustar00rootroot00000000000000import json import time import unittest from unittest.mock import MagicMock, patch import jwt from ...authentication.token_verifier import ( AsymmetricSignatureVerifier, JwksFetcher, SignatureVerifier, SymmetricSignatureVerifier, TokenVerifier, ) from ...exceptions import TokenValidationError RSA_PUB_KEY_1_PEM = b"""-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4\nyCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9\n83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs\nWXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT\n69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8\nAziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0\nYwIDAQAB\n-----END PUBLIC KEY-----\n""" RSA_PUB_KEY_2_PEM = b"""-----BEGIN PUBLIC KEY-----\nMDowDQYJKoZIhvcNAQEBBQADKQAwJgIfAI7TBUCn8e1hj/fNpb5dqMf8Jj6Ja6qN\npqyeOGYEzAIDAQAB\n-----END PUBLIC KEY-----\n""" RSA_PUB_KEY_1_JWK = { "kty": "RSA", "use": "sig", "n": "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-spi5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVsWXI9C-yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT69s7of9-I9l5lsJ9cozf1rxrXX4V1u_SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8AziMCxS-VrRPDM-zfvpIJg3JljAh3PJHDiLu902v9w-Iplu1WyoB2aPfitxEhRN0Yw", "e": "AQAB", "kid": "test-key-1", } RSA_PUB_KEY_2_JWK = { "kty": "RSA", "use": "sig", "n": "jtMFQKfx7WGP982lvl2ox_wmPolrqo2mrJ44ZgTM", "e": "AQAB", "kid": "test-key-2", } JWKS_RESPONSE_SINGLE_KEY = {"keys": [RSA_PUB_KEY_1_JWK]} JWKS_RESPONSE_MULTIPLE_KEYS = {"keys": [RSA_PUB_KEY_1_JWK, RSA_PUB_KEY_2_JWK]} HMAC_SHARED_SECRET = "secret" MOCKED_CLOCK = 1587592561 # Apr 22 2020 21:56:01 UTC DEFAULT_LEEWAY = 60 expectations = { "audience": "tokens-test-123", "audience_alt": "external-test-999", "issuer": "https://tokens-test.auth0.com/", "nonce": "a1b2c3d4e5", } class TestSignatureVerifier(unittest.TestCase): def test_fail_at_creation_with_invalid_algorithm(self): alg = 12345 with self.assertRaises(ValueError) as err: SymmetricSignatureVerifier("some secret", algorithm=alg) self.assertEqual(str(err.exception), "algorithm must be specified.") with self.assertRaises(ValueError) as err: AsymmetricSignatureVerifier("some url", algorithm=alg) self.assertEqual(str(err.exception), "algorithm must be specified.") with self.assertRaises(ValueError) as err: SignatureVerifier(algorithm=alg) self.assertEqual(str(err.exception), "algorithm must be specified.") def test_symmetric_verifier_uses_hs256_alg(self): verifier = SymmetricSignatureVerifier("some secret") self.assertEqual(verifier._algorithm, "HS256") def test_asymmetric_verifier_uses_rs256_alg(self): verifier = AsymmetricSignatureVerifier("some URL") self.assertEqual(verifier._algorithm, "RS256") def test_asymmetric_verifier_uses_default_jwks_cache_ttl(self): verifier = AsymmetricSignatureVerifier("some URL") self.assertEqual(verifier._fetcher._cache_ttl, JwksFetcher.CACHE_TTL) def test_asymmetric_verifier_uses_provided_jwks_cache_ttl(self): verifier = AsymmetricSignatureVerifier("some URL", cache_ttl=3600) self.assertEqual(verifier._fetcher._cache_ttl, 3600) def test_symmetric_verifier_fetches_key(self): verifier = SymmetricSignatureVerifier("some secret") key = verifier._fetch_key() self.assertEqual(verifier._shared_secret, "some secret") self.assertEqual(key, "some secret") def test_asymmetric_verifier_fetches_key(self): mock_fetcher = JwksFetcher("some URL") mock_fetcher.get_key = MagicMock("get_key") mock_fetcher.get_key.return_value = RSA_PUB_KEY_1_JWK verifier = AsymmetricSignatureVerifier("some URL") verifier._fetcher = mock_fetcher key = verifier._fetch_key("test-key") args, kwargs = mock_fetcher.get_key.call_args self.assertEqual(args[0], "test-key") self.assertEqual(mock_fetcher, verifier._fetcher) self.assertEqual(mock_fetcher._jwks_url, "some URL") self.assertEqual(key, RSA_PUB_KEY_1_JWK) def test_fails_with_none_algorithm(self): # below is a token without signature and "signed" with none jwt = "ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.eyJ1c2VybmFtZSI6ImFkbWluIn0." verifier = SymmetricSignatureVerifier("some secret") with self.assertRaises(Exception) as err: verifier.verify_signature(jwt) self.assertEqual( str(err.exception), 'Signature algorithm of "none" is not supported. Expected the token to be' ' signed with "HS256"', ) verifier = AsymmetricSignatureVerifier("some url") with self.assertRaises(Exception) as err: verifier.verify_signature(jwt) self.assertEqual( str(err.exception), 'Signature algorithm of "none" is not supported. Expected the token to be' ' signed with "RS256"', ) class TestJwksFetcher(unittest.TestCase): @staticmethod def _get_pem_bytes(rsa_public_key): # noinspection PyPackageRequirements # requirement already includes cryptography -> pyjwt[crypto] from cryptography.hazmat.primitives import serialization return rsa_public_key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo ) @patch("requests.get") def test_get_jwks_json_twice_on_cache_expired(self, mock_get): JWKS_URL = "https://app.myhosting.com/.well-known/jwks.json" fetcher = JwksFetcher(JWKS_URL, cache_ttl=1) mock_get.return_value.ok = True mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = JWKS_RESPONSE_SINGLE_KEY key_1 = fetcher.get_key("test-key-1") expected_key_1_pem = self._get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock_get.assert_called_with(JWKS_URL) self.assertEqual(mock_get.call_count, 1) time.sleep(2) # 2 seconds has passed, cache should be expired key_1 = fetcher.get_key("test-key-1") expected_key_1_pem = self._get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock_get.assert_called_with(JWKS_URL) self.assertEqual(mock_get.call_count, 2) @patch("requests.get") def test_get_jwks_json_once_on_cache_hit(self, mock_get): JWKS_URL = "https://app.myhosting.com/.well-known/jwks.json" fetcher = JwksFetcher(JWKS_URL) mock_get.return_value.ok = True mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = JWKS_RESPONSE_MULTIPLE_KEYS key_1 = fetcher.get_key("test-key-1") key_2 = fetcher.get_key("test-key-2") expected_key_1_pem = self._get_pem_bytes(key_1) expected_key_2_pem = self._get_pem_bytes(key_2) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) self.assertEqual(expected_key_2_pem, RSA_PUB_KEY_2_PEM) mock_get.assert_called_with(JWKS_URL) self.assertEqual(mock_get.call_count, 1) @patch("requests.get") def test_fetches_jwks_json_forced_on_cache_miss(self, mock_get): JWKS_URL = "https://app.myhosting.com/.well-known/jwks.json" fetcher = JwksFetcher(JWKS_URL) mock_get.return_value.ok = True mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = {"keys": [RSA_PUB_KEY_1_JWK]} # Triggers the first call key_1 = fetcher.get_key("test-key-1") expected_key_1_pem = self._get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock_get.assert_called_with(JWKS_URL) self.assertEqual(mock_get.call_count, 1) mock_get.return_value.json.return_value = JWKS_RESPONSE_MULTIPLE_KEYS # Triggers the second call key_2 = fetcher.get_key("test-key-2") expected_key_2_pem = self._get_pem_bytes(key_2) self.assertEqual(expected_key_2_pem, RSA_PUB_KEY_2_PEM) mock_get.assert_called_with(JWKS_URL) self.assertEqual(mock_get.call_count, 2) @patch("requests.get") def test_fetches_jwks_json_once_on_cache_miss(self, mock_get): JWKS_URL = "https://app.myhosting.com/.well-known/jwks.json" fetcher = JwksFetcher(JWKS_URL) mock_get.return_value.ok = True mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = JWKS_RESPONSE_SINGLE_KEY with self.assertRaises(Exception) as err: fetcher.get_key("missing-key") mock_get.assert_called_with(JWKS_URL) self.assertEqual( str(err.exception), 'RSA Public Key with ID "missing-key" was not found.' ) self.assertEqual(mock_get.call_count, 1) @patch("requests.get") def test_fails_to_fetch_jwks_json_after_retrying_twice(self, mock_get): JWKS_URL = "https://app.myhosting.com/.well-known/jwks.json" fetcher = JwksFetcher(JWKS_URL) mock_get.return_value.ok = False mock_get.return_value.status_code = 500 mock_get.return_value.text = "Some error happened" with self.assertRaises(Exception) as err: fetcher.get_key("id1") mock_get.assert_called_with(JWKS_URL) self.assertEqual( str(err.exception), 'RSA Public Key with ID "id1" was not found.' ) self.assertEqual(mock_get.call_count, 2) class TestTokenVerifier(unittest.TestCase): @staticmethod def asymmetric_signature_verifier_mock(): verifier = AsymmetricSignatureVerifier("some URL") verifier._fetch_key = MagicMock("_fetch_key") # noinspection PyUnresolvedReferences # requirement already includes cryptography -> pyjwt[crypto] verifier._fetch_key.return_value = jwt.algorithms.RSAAlgorithm.from_jwk( json.dumps(RSA_PUB_KEY_1_JWK) ) return verifier def assert_fails_with_error( self, token, error_message, signature_verifier=None, audience=expectations["audience"], issuer=expectations["issuer"], nonce=None, max_age=None, clock=MOCKED_CLOCK, organization=None, ): sv = signature_verifier or self.asymmetric_signature_verifier_mock() tv = TokenVerifier( signature_verifier=sv, issuer=issuer, audience=audience, leeway=DEFAULT_LEEWAY, ) tv._clock = clock with self.assertRaises(TokenValidationError) as err: tv.verify(token, nonce, max_age, organization) self.assertEqual(str(err.exception), error_message) def test_fails_at_creation_with_invalid_signature_verifier(self): sv = "string is not an instance of signature verifier" with self.assertRaises(TypeError) as err: # noinspection PyTypeChecker TokenVerifier( signature_verifier=sv, issuer="valid issuer", audience="valid audience" ) self.assertEqual( str(err.exception), "signature_verifier must be an instance of SignatureVerifier.", ) def test_err_token_empty(self): token = "" self.assert_fails_with_error(token, "ID token is required but missing.") token = None self.assert_fails_with_error(token, "ID token is required but missing.") def test_err_token_format_invalid(self): token = "a.b" self.assert_fails_with_error(token, "token could not be decoded.") token = "a.b." self.assert_fails_with_error(token, "token could not be decoded.") token = "a.b.c.d" self.assert_fails_with_error(token, "token could not be decoded.") def test_HS256_token_signature_passes(self): token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Hn38QVtN_mWN0c-jOa-Fqq69kXpbBp0THsvE-CQ47Ps" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK tv.verify(token) def test_RS256_token_signature_passes(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Eo2jxdlrKmutIeyn9Un6VHorMJaCL5txPDCC3QiAQn0pYYnrRU7VMQwqbTiXLQ9zPYh5Q4pQmT-XRaGL-HwDH8vCUieVJKOm0-gNFAMzx1i8sRH1ubw75sn69y09AQKcitYtjnBmahgfZrswtsxOXM7XovlLftPjv6goAi_U38GYsS_V_zOBvdbX2cM5zdooJAC0e7vlCr3bXNo90qwgCuezvCGt1ZrgWyDNO9oMzK-TlK86q36LuIkux7XZboF5rc3zsThEce_tPufA5qoEa-7I_ybmjwlvOCWmngYLT52_S2CbHeRNarePMjZIlmAuG-DcetwO8jJsX84Ra0SdUw" sv = self.asymmetric_signature_verifier_mock() tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK tv.verify(token) def test_HS256_token_signature_fails(self): token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.invalidsignature" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) self.assert_fails_with_error( token, "Invalid token signature.", signature_verifier=sv ) def test_RS256_token_signature_fails(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.invalidsignature" sv = self.asymmetric_signature_verifier_mock() self.assert_fails_with_error( token, "Invalid token signature.", signature_verifier=sv ) def test_fails_with_algorithm_not_supported(self): token = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0." self.assert_fails_with_error( token, 'Signature algorithm of "none" is not supported. Expected the token to be' ' signed with "RS256"', ) return def test_fails_with_iss_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.XuWmo_XxNET0mOW1AaLwi8koUOd05TZULWCGF_3WbeR5VJB6aK0rzo8AkHXrSv9Yr6he_1N8xFDKBIIyXFa4Y2PN8kdwUQtsJcj-cj8_2Ta2S0vV6O7XqbW58eXhX8Ng0OUrqgkHT1leIUJnBZ10YhM0-0zmdIq_WlNnwTdmvAGtYAUGcjyUmq-QEKBc2YYnf83vtGuFT2xGUGsTKR_Jj7lH_QTYdFaiT4t7gwFyXhP5KVUkG3ebdQUkIAQnoY0TXwrgDDCQhAWiUYZehMlv7Ml4tqLsiIUMgm4w5wSfdTdhVEMa7wHPj7gp4s-bfEqaOuyg0xH24rP19LkJROITDw" self.assert_fails_with_error( token, "Issuer (iss) claim must be a string present in the ID token" ) def test_fails_with_iss_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzb21ldGhpbmctZWxzZSIsInN1YiI6ImF1dGgwfDEyMzQ1Njc4OSIsImF1ZCI6WyJ0b2tlbnMtdGVzdC0xMjMiLCJleHRlcm5hbC10ZXN0LTk5OSJdLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MSwibm9uY2UiOiJhMWIyYzNkNGU1IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.HNQ1lXWWQOC4D1-D4XfYlF2MBE6F7TW1EoBqt3ayxehNqtc8ZUJ6MR4aE_o7NnY0aBNbp7J9okgRC2PIKPHZkXUdxHvGZNldrEDDKeKPe0kZFxF5sEK8RYCnJk5m28JFpgRYvXA9KjKnLsBsbV--8VnkgRlw0-LClxqp3ynoGgmh2dVvBqXV8DiAbRvvRPZOg7CVFqCxJoMFD0FJ_dej7ChxMDSe_NDW-CjG9rgEsw_el-_vUcKSp7bzZ1jKm0zOcPDRPfgda5oek0xR6_bg2es_TarYKCwlQCVG1NEmgcJ5gNeVIsrwaPrMXqGr9KNs-nLerQO9Jl1EhCU8No5Sfg" self.assert_fails_with_error( token, 'Issuer (iss) claim mismatch in the ID token; expected "{}", found' ' "something-else"'.format(expectations["issuer"]), ) def test_fails_with_sub_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.VtPtxkKh7vZKECzFiNXLWBeWcRgaX1lIOhW0fKU5WSgTcjYZRYoxW-wwI8pai7vgJMQQCizBpLpjMPpCBYBGEiYgGpa9D7vAD8XmYjcVZujG1FLFGkOTkgisCtgVT6WpvwxIejNrl_TcgSEBkCcD9f7gGnFVOjJe4YtSMEUdtuDz-pHEGGNbJLdq0L-pPUrO2Fyw3NspX1RrEYVn7uGuAlDQWQ4x6IOtM40NPzAmyLVrsOPmz_5Igyi7ZZar6epcfd5dBeUbgp8yK178XV-r6-UMuj39NJE4Bx8cDQR1qjxMsxgZ3Lem6OLfFvKWXsgJs7dh13kJDqrx2jXfhvpd-w" self.assert_fails_with_error( token, "Subject (sub) claim must be a string present in the ID token" ) def test_fails_with_aud_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MSwibm9uY2UiOiJhMWIyYzNkNGU1IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.t5SBd_J-k_GwPHfyGfPxOcT8n0Pbwy9R-pj7tK8231m3My1Zg3LyKx3tl7MFtymgRHcs2hd4WrWrKjyFrHMOzUWX8dQ2-b6KVRuFQjc70gnW54igj-cT-oo07Lzen0Ol6_7w_5rabWCOL9lM0UM9jpau_llVh97zyYgcUEBeA5lLld5ZLTB-JKMVehjJelBR-MPEDvMr2zT9nRPPUXqezAWZOPYG83oRRB2ktoafaUM4RVvp34q6uUWJq49m-qY2DfKuyDGK4axo1fHKE3JmrsayEDpuGDYDFNDQzy4g1lJvzBKxV2SJl0LKP6sxbM8sw7qaH4ViRNZpFQBZ7veGPQ" self.assert_fails_with_error( token, "Audience (aud) claim must be a string or array of strings present in the" " ID token", ) def test_fails_with_aud_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.d1BFaw_h5VOAw0tXAQ98hrru4gWCNjIxcCQktFVcLIqrX9m74-vWv2SVoFBAQlXihEXoDS-5QSMhVPG1iry9arseN16PnSOmilBhSebiqAVSBojLxq5KFEDuUz90lApt4d5BSCMAIAQ1Dp1pGKwJC0BiLrFNOQ2KrmoEvQMgaD0PLlCLy1lL7MntABE86tX_BoqI4ZkWJ1lX1n2-SZAn-ldoOK8W8RUYiwBUDTktpgAfICFUSPAZXj_vn05vwvQBoozhMQkuJrPziz81Tj8lPh0iPsnMBtsAqvAhdwtp3p-eadXPVcjNu4yE3dkBgFDQwoNtV_elWQtmFBn49FEKyw" self.assert_fails_with_error( token, 'Audience (aud) claim mismatch in the ID token; expected "{}" but was not' ' one of "external-test-999"'.format(expectations["audience"]), ) def test_fails_with_exp_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiaWF0IjoxNTg3NTkyNTYxLCJub25jZSI6ImExYjJjM2Q0ZTUiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.qq4Tyz8VGnAJy2KmKo3zmThnWXviGwJB-Bz18lnJlicjAXk4ObeUVoX0WHB3I8vmDMscC9JXhkDP5KIGSAkeaLzil0FFJwwQB_saFE_y3zjfMQgdsz_qtDR96S1SQtnZ7AhChIAZ7cV4p_wmj_-TQYuZVQ0F6MoBmFsJITIr7gxnNFJWw6KJEU94nq8-q_IAyC5-9epdEtphUi_wkalHzEy6w8tQawKXCLYxo12VNlEy5mi8PlwqGsqVcwFwqec-ERt2hajyuqL1210-cZJMA-NmXz4mv4scHdiE09KZcLScKcezs9KaM5iaButMBL0Fyec0KcwwfELX_zghekALOA" self.assert_fails_with_error( token, "Expiration Time (exp) claim must be a number present in the ID token", ) def test_fails_with_exp_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NTkyNTYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.KUbd2s3Km-PVpP8KEJo1e0lyQv19TjiKMFX-lVebFoiPNwlVTXS08g5qe_G8pcOrwNfX6cRkRLbp7TNQ7tGDCuEcdia9KOaWeVWla5B3UPCv1qozCyMv4ZYrA0qdT2KgwytRMVWSov9ly29FSo6SRQksAMKZdnAzPaqnJGKBgVIjKN3a5ePIeX5yBIGxlNjS3nyWt8LIQJ9BFaQWk3i0vAKYpDeco3VLNLX-wH7739MzS7ll6t6LyuZi6kBaRG6XZc394glKidTvCp06ViQlPlcuV7JsCJfbkBc0AS5TmzOEdUCype-gzNqbuLcSXihS-qOx7Yjv8y3farV1_7qYqw" mocked_clock = MOCKED_CLOCK + DEFAULT_LEEWAY + 1 self.assert_fails_with_error( token, "Expiration Time (exp) claim error in the ID token; current time ({}) is" " after expiration time (1587592621)".format(mocked_clock), clock=mocked_clock, ) def test_fails_with_iat_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJub25jZSI6ImExYjJjM2Q0ZTUiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1ODc2Nzg5NjF9.CWW7mWUhiI-rramQ2dIGi7vBsOMmsouIg32IL9u2g4Dg3PV0C55R7dL6Jvf9hqaZXvx9Psrw0vLnOlhFztAC6LlQuq2eCaLYsDme36NxeYGC7CFXygvlU_eXD5IdNK35GriTfpG_b5hC7tl2glMmNQcZWlsIyKw7eq8o1POpqo0K2bCVjoyJkHL6WUpw6_08HPspmTL_Qd0km08z6zgvbl8Hpzk-tLmXqN7LjmuhEsjnIFphu-dGwcQsoY3RAomYxAFXAPYT8siEIf2w3zlIoUde-mujiMUtMD-Od7t7w36GO6Kubb9M9inVwPEg1yFKlFTXZBKXu91xmOmvMJ5Qfg" self.assert_fails_with_error( token, "Issued At (iat) claim must be a number present in the ID token" ) def test_passes_when_nonce_missing_but_not_required(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.L-DveLCDf4Te7x3JZmQ6rCkUQrenl1NFpHqKD8Fs-glhd2iyc-TYffk1M30T0-wBri-3tTgraDAjZAjXuwSk0gV_V5uKCHyIoSRphrC88aX8IeECteQpHa4KR15lbzA5JdVhJu7LuCZ2EFvdjHh5GiViLRWsTSHGUM-uqcMK0q2kWGvCEgfOIXqocnQiyCNITxfgMYJd38zOsVeP7HFf9riuFEQz65oER22o3xyIZ-ILSaU10n6Ob559Rbjc0NVKH4hrggRg8kG7cJCiXbRxXnzO_VM8LmRHhF56jh3ZSrO4bzQa5xv04bMbX6A77muMZD0vghsaslvpWerWbwaSQQ" sv = self.asymmetric_signature_verifier_mock() tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK tv.verify(token) def test_fails_with_nonce_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.L-DveLCDf4Te7x3JZmQ6rCkUQrenl1NFpHqKD8Fs-glhd2iyc-TYffk1M30T0-wBri-3tTgraDAjZAjXuwSk0gV_V5uKCHyIoSRphrC88aX8IeECteQpHa4KR15lbzA5JdVhJu7LuCZ2EFvdjHh5GiViLRWsTSHGUM-uqcMK0q2kWGvCEgfOIXqocnQiyCNITxfgMYJd38zOsVeP7HFf9riuFEQz65oER22o3xyIZ-ILSaU10n6Ob559Rbjc0NVKH4hrggRg8kG7cJCiXbRxXnzO_VM8LmRHhF56jh3ZSrO4bzQa5xv04bMbX6A77muMZD0vghsaslvpWerWbwaSQQ" self.assert_fails_with_error( token, "Nonce (nonce) claim must be a string present in the ID token", nonce=expectations["nonce"], ) def test_fails_with_nonce_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiMDAwOTk5IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.Z0n4vKcTCTrKJXUx56TyoQk3-okpjOqCDRN9NqH5zkCi0at7WV-E3TxGbXpIN0UsXwUWxrHiuvL9lN2M4PoIvL4XvzUqgsepAYYBCPGR9Wxb-ALmhhWdS_LNRVAgfUCn94khc_G51XtyeP0bQgWRkV7VbeWxkBTnrhmGwEkVx6XbfpnTRUCDSR_luJfUu84LkFJf1n2ohnEU7Q74BXJjxIIJnhZrg4J65E3cNtZ9N7AOIrbpbZ0oB7NhcZP0xA0A75qt7ZnKOuLsbRppZjcz56QmVIArOSCkkegl3qLx4cNdVa-O840AQWCwkAcHxS9lHBIWyaToC7IVMOLxIcGVlQ" self.assert_fails_with_error( token, 'Nonce (nonce) claim mismatch in the ID token; expected "{}", found' ' "000999"'.format(expectations["nonce"]), nonce=expectations["nonce"], ) def test_fails_with_aud_array_and_azp_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF1dGhfdGltZSI6MTU4NzY3ODk2MX0.Xpxc2tj3sDwAYftYAcoiLO3kq0X54KSngDzQu_foTjlDQFTPApVVrX_BQqMAUFsmiNdt-3Tf9lkUlAagpvXy_VUY5LIjzEihEKDzqQFMQ8wm7RK7qV2XLS1abltxXd8AuOHcPnVHbtERpsCXR5eRt0-ESSPUw_scqHOwmYQOFF0sOQJ72r9EYZFMGhojyzpbzhBF0jgi9wMqj0VSpLEzZ3MnkWBCTlmc5OAPXQGdq6C1dVfcBXy4iiIBEaPCG962Yqrr-bbL92_T_XG-FmtpIViuRjHDWRJ26uoD0cmXwePwojxlyeY7VrAoKX3VtA4lm1Co0BMh9DjMKv-p3zT6XA" self.assert_fails_with_error( token, "Authorized Party (azp) claim must be a string present in the ID token when" " Audience (aud) claim has multiple values", ) def test_fails_with_aud_array_and_azp_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6ImV4dGVybmFsLXRlc3QtOTk5IiwiYXV0aF90aW1lIjoxNTg3Njc4OTYxfQ.Bx56kdY8rBwlAZ8Walh6NjONs94Tdv37iP0EPKFxvpELFt_8RENhPp8Lqe52zrrgXqUdA1eeBynegqH7_duJawQ0l86u2dsaPonMOsh_W8ZjaqPOVHLv1z7xQb84UdjMSSJbMGMLPmuX2GMlc5hcjW5YgWU1xp-gpNpKMIzW19gNxpwtIWkLZ5zkjEVBYHSTAw7CO6HkncTZBqdYA3bq3ziQPljqvSvyPehuJ-2Q5TlrdVLRO5HS4-C6NEs-h8fpX25NP537FM9g7T7pRB1wDxsrJTny6uKBKFCwtNSF5laojV2edEDlDUsEEUCGh6zUzITGeZNa0M52ZsxGoAehIw" self.assert_fails_with_error( token, 'Authorized Party (azp) claim mismatch in the ID token; expected "{}",' ' found "external-test-999"'.format(expectations["audience"]), ) def test_fails_when_max_age_sent_with_auth_time_missing(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyJ9.pWr6hjQ9Mi9cYSYIFWG1d-KJ98OeKeKb3F7X_DdUgQ5Xir8wHLLuZDrFAalKlxYiTJTMXqh9YkxKLcEjFQsTECEXA4VMliUv4A2Egk8EDUi5SQtoQ11xGJo-S7qM4cL-x-69ZnJvNWlZZ8NnvtTOSgzpa_fsG7T3PScr0b9ukxOQ-o8suV2fLE7bOliBKZn9PC7sowtF_oeQ03f0e0thtZFl121ROL65ARh9C-ic2azgmIn3YgsvguaoT5ZpAFRe2McaP026bNimOmfEzVIwHCDuR6rkFZvX4QUduX6UQ4bOQp_EC9G0XDk08H0nBXx2JXSHCW4YPZz9bz1f12B4kg" self.assert_fails_with_error( token, "Authentication Time (auth_time) claim must be a number present in the ID" " token when Max Age (max_age) is specified", max_age=120, ) def test_fails_when_max_age_sent_with_auth_time_invalid(self): token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC05OTkiXSwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjEsIm5vbmNlIjoiYTFiMmMzZDRlNSIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU4NzU5MjU2MX0.VcVv1cGthPtuHZAZa-x7XZjhrcKEV3xUW6rVfhNMM_zCRBxLdyJl6gVv396eyfMX5-3dhr0-9kAYQAjPrmcvUDFLOR4Qjamg83U-TMa4agnYwQ_Iv3u_zhYmSrKzZlQvbOhT5imZShL11hycyukv2D1ODbFpvCdsHWXUFF0LotiXrBRr45AoEie2bASNSMmCZbQh-_Pq7gdhKOZMhBTErrk-aEOZrmsUG0sL2ZcVLdZ0_U-23ysR2GVpNg8jyv1HLZaPw5IJC4XucRw5r-5UiIcIIdxbUNphamPgq1cqL3QP_UsCGotCIUQTDNMbXB7-J_opBM2uGFp-cW95-Wq7qg" max_age = 120 expected_auth_time = MOCKED_CLOCK + DEFAULT_LEEWAY + max_age mocked_clock = expected_auth_time + 1 self.assert_fails_with_error( token, "Authentication Time (auth_time) claim in the ID token indicates that too" " much time has passed since the last end-user authentication. Current time" " ({}) is after last auth at ({})".format(mocked_clock, expected_auth_time), max_age=max_age, clock=mocked_clock, ) def test_passes_when_org_present_but_not_required(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwiaXNzIjoiaHR0cHM6Ly90b2tlbnMtdGVzdC5hdXRoMC5jb20vIiwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjF9.hjSPgJpg0Dn2z0giCdGqVLD5Kmqy_yMYlSkgwKD7ahQ" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK tv.verify(token) def test_passes_when_org_present_and_matches(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwiaXNzIjoiaHR0cHM6Ly90b2tlbnMtdGVzdC5hdXRoMC5jb20vIiwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjF9.hjSPgJpg0Dn2z0giCdGqVLD5Kmqy_yMYlSkgwKD7ahQ" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK tv.verify(token, organization="org_123") def test_fails_when_org_name_specified_but_not_present(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.wotJnUdD5IfdZMewF_-BnHc0pI56uwzwr5qaSXvSu9w" self.assert_fails_with_error( token, "Organization (org_name) claim must be a string present in the ID token", signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org-123", ) def test_fails_when_org_name_specified_but_not_string(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6NDIsImlzcyI6Imh0dHBzOi8vdG9rZW5zLXRlc3QuYXV0aDAuY29tLyIsImV4cCI6MTU4Nzc2NTM2MSwiaWF0IjoxNTg3NTkyNTYxfQ.RXu-dz1u2pftk_iInk1To8z9g1B6TVA-5FAwoCx85T0" self.assert_fails_with_error( token, "Organization (org_name) claim must be a string present in the ID token", signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org-123", ) def test_fails_when_org_name_specified_but_does_not_match(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6Im9yZy1hYmMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.P_ldJGEaFg58cARwGMtog_KTsqv7cGJZXoS9xdTEkvQ" self.assert_fails_with_error( token, 'Organization (org_name) claim mismatch in the ID token; expected "org-123",' ' found "org-abc"', signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org-123", ) def test_succeeds_when_org_name_specified_matches(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6Im9yZy0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.P8Kba8Fgamyiw1qw_lBfp2OAzWn6NOLL6fBCDQhGvyc" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK response = tv.verify(token) self.assertIn("org_name", response) self.assertEqual("org-123", response["org_name"]) def test_fails_when_org_id_specified_but_not_present(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.wotJnUdD5IfdZMewF_-BnHc0pI56uwzwr5qaSXvSu9w" self.assert_fails_with_error( token, "Organization (org_id) claim must be a string present in the ID token", signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org_123", ) def test_fails_when_org_id_specified_but_not_string(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOjQyLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.fGL1_akaHikdovS7NRYla3flne1xdtCjP0ei_CRxO6k" self.assert_fails_with_error( token, "Organization (org_id) claim must be a string present in the ID token", signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org_123", ) def test_fails_when_org_id_specified_but_does_not_match(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwiaXNzIjoiaHR0cHM6Ly90b2tlbnMtdGVzdC5hdXRoMC5jb20vIiwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjF9.hjSPgJpg0Dn2z0giCdGqVLD5Kmqy_yMYlSkgwKD7ahQ" self.assert_fails_with_error( token, 'Organization (org_id) claim mismatch in the ID token; expected "org_abc",' ' found "org_123"', signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), organization="org_abc", ) def test_verify_returns_payload(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwiaXNzIjoiaHR0cHM6Ly90b2tlbnMtdGVzdC5hdXRoMC5jb20vIiwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjF9.hjSPgJpg0Dn2z0giCdGqVLD5Kmqy_yMYlSkgwKD7ahQ" sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) tv = TokenVerifier( signature_verifier=sv, issuer=expectations["issuer"], audience=expectations["audience"], ) tv._clock = MOCKED_CLOCK response = tv.verify(token) self.assertIn("sub", response) self.assertIn("aud", response) self.assertIn("org_id", response) self.assertIn("iss", response) self.assertIn("exp", response) self.assertIn("iat", response) self.assertEqual("org_123", response["org_id"]) auth0-auth0-python-0f6dbea/auth0/test/authentication/test_users.py000066400000000000000000000006611506260514000254350ustar00rootroot00000000000000import unittest from unittest import mock from ...authentication.users import Users class TestUsers(unittest.TestCase): @mock.patch("auth0.rest.RestClient.get") def test_userinfo(self, mock_get): u = Users("my.domain.com") u.userinfo(access_token="atk") mock_get.assert_called_with( url="https://my.domain.com/userinfo", headers={"Authorization": "Bearer atk"}, ) auth0-auth0-python-0f6dbea/auth0/test/conftest.py000066400000000000000000000002061506260514000220360ustar00rootroot00000000000000import pytest import random @pytest.fixture(autouse=True) def set_random_seed(): random.seed(42) print("Random seeded to 42")auth0-auth0-python-0f6dbea/auth0/test/management/000077500000000000000000000000001506260514000217555ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/management/__init__.py000066400000000000000000000000001506260514000240540ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test/management/test_actions.py000066400000000000000000000203521506260514000250300ustar00rootroot00000000000000import unittest from unittest import mock from ...management.actions import Actions class TestActions(unittest.TestCase): def test_init_with_optionals(self): t = Actions(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.actions.RestClient") def test_get_actions(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_actions() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/actions/actions", args[0]) self.assertEqual( kwargs["params"], { "triggerId": None, "actionName": None, "deployed": None, "installed": "false", "page": None, "per_page": None, }, ) c.get_actions("trigger-id", "action-name", True, True, 0, 5) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/actions/actions", args[0]) self.assertEqual( kwargs["params"], { "triggerId": "trigger-id", "actionName": "action-name", "deployed": "true", "installed": "true", "page": 0, "per_page": 5, }, ) c.get_actions("trigger-id", "action-name", False, True, 0, 5) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/actions/actions", args[0]) self.assertEqual( kwargs["params"], { "triggerId": "trigger-id", "actionName": "action-name", "deployed": "false", "installed": "true", "page": 0, "per_page": 5, }, ) @mock.patch("auth0.management.actions.RestClient") def test_create_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.create_action({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/actions/actions", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.actions.RestClient") def test_update_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.update_action("action-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/actions/actions/action-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.actions.RestClient") def test_get_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_action("action-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/actions/actions/action-id", args[0]) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.actions.RestClient") def test_get_triggers(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_triggers() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/actions/triggers", args[0]) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.actions.RestClient") def test_delete_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.delete_action("action-id") args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/actions/actions/action-id", args[0]) self.assertEqual(kwargs["params"], {"force": "false"}) c.delete_action("action-id", True) args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/actions/actions/action-id", args[0]) self.assertEqual(kwargs["params"], {"force": "true"}) @mock.patch("auth0.management.actions.RestClient") def test_get_execution(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_execution("execution-id") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/executions/execution-id", args[0] ) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.actions.RestClient") def test_get_action_versions(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_action_versions("action-id") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/actions/action-id/versions", args[0] ) self.assertEqual(kwargs["params"], {"page": None, "per_page": None}) c.get_action_versions("action-id", 0, 5) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/actions/action-id/versions", args[0] ) self.assertEqual(kwargs["params"], {"page": 0, "per_page": 5}) @mock.patch("auth0.management.actions.RestClient") def test_get_trigger_bindings(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_trigger_bindings("trigger-id") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/triggers/trigger-id/bindings", args[0] ) self.assertEqual(kwargs["params"], {"page": None, "per_page": None}) c.get_trigger_bindings("trigger-id", 0, 5) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/triggers/trigger-id/bindings", args[0] ) self.assertEqual(kwargs["params"], {"page": 0, "per_page": 5}) @mock.patch("auth0.management.actions.RestClient") def test_get_action_version(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.get_action_version("action-id", "version-id") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/actions/actions/action-id/versions/version-id", args[0], ) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.actions.RestClient") def test_deploy_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.deploy_action("action-id") args, kwargs = mock_instance.post.call_args self.assertEqual( "https://domain/api/v2/actions/actions/action-id/deploy", args[0] ) @mock.patch("auth0.management.actions.RestClient") def test_rollback_action(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.rollback_action_version("action-id", "version-id") args, kwargs = mock_instance.post.call_args self.assertEqual( "https://domain/api/v2/actions/actions/action-id/versions/version-id/deploy", args[0], ) self.assertEqual(kwargs["data"], {}) @mock.patch("auth0.management.actions.RestClient") def test_update_trigger_bindings(self, mock_rc): mock_instance = mock_rc.return_value c = Actions(domain="domain", token="jwttoken") c.update_trigger_bindings("trigger-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual( "https://domain/api/v2/actions/triggers/trigger-id/bindings", args[0] ) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) auth0-auth0-python-0f6dbea/auth0/test/management/test_atack_protection.py000066400000000000000000000070011506260514000267150ustar00rootroot00000000000000import unittest from unittest import mock from ...management.attack_protection import AttackProtection class TestAttackProtection(unittest.TestCase): def test_init_with_optionals(self): t = AttackProtection( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.attack_protection.RestClient") def test_get_breached_password_detection(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} ap = AttackProtection(domain="domain", token="jwttoken") ap.get_breached_password_detection() args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/attack-protection/breached-password-detection", args[0], ) @mock.patch("auth0.management.attack_protection.RestClient") def test_update_breached_password_detection(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.patch.return_value = {} c = AttackProtection(domain="domain", token="jwttoken") c.update_breached_password_detection({"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/attack-protection/breached-password-detection", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.attack_protection.RestClient") def test_get_brute_force_protection(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} ap = AttackProtection(domain="domain", token="jwttoken") ap.get_brute_force_protection() args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/attack-protection/brute-force-protection", args[0] ) @mock.patch("auth0.management.attack_protection.RestClient") def test_update_brute_force_protection(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.patch.return_value = {} c = AttackProtection(domain="domain", token="jwttoken") c.update_brute_force_protection({"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/attack-protection/brute-force-protection", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.attack_protection.RestClient") def test_get_suspicious_ip_throttling(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} ap = AttackProtection(domain="domain", token="jwttoken") ap.get_suspicious_ip_throttling() args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/attack-protection/suspicious-ip-throttling", args[0] ) @mock.patch("auth0.management.attack_protection.RestClient") def test_update_suspicious_ip_throttling(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.patch.return_value = {} c = AttackProtection(domain="domain", token="jwttoken") c.update_suspicious_ip_throttling({"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/attack-protection/suspicious-ip-throttling", data={"a": "b", "c": "d"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_auth0.py000066400000000000000000000111331506260514000244060ustar00rootroot00000000000000import unittest from ...management.actions import Actions from ...management.attack_protection import AttackProtection from ...management.auth0 import Auth0 from ...management.blacklists import Blacklists from ...management.client_credentials import ClientCredentials from ...management.client_grants import ClientGrants from ...management.clients import Clients from ...management.connections import Connections from ...management.custom_domains import CustomDomains from ...management.device_credentials import DeviceCredentials from ...management.email_templates import EmailTemplates from ...management.emails import Emails from ...management.grants import Grants from ...management.guardian import Guardian from ...management.hooks import Hooks from ...management.jobs import Jobs from ...management.log_streams import LogStreams from ...management.logs import Logs from ...management.network_acls import NetworkAcls from ...management.organizations import Organizations from ...management.prompts import Prompts from ...management.resource_servers import ResourceServers from ...management.roles import Roles from ...management.rules import Rules from ...management.rules_configs import RulesConfigs from ...management.stats import Stats from ...management.tenants import Tenants from ...management.tickets import Tickets from ...management.user_blocks import UserBlocks from ...management.users import Users from ...management.users_by_email import UsersByEmail from ...rest import RestClientOptions class TestAuth0(unittest.TestCase): def setUp(self): self.domain = "user.some.domain" self.token = "a-token" self.a0 = Auth0(self.domain, self.token) def test_actions(self): self.assertIsInstance(self.a0.actions, Actions) def test_attack_protection(self): self.assertIsInstance(self.a0.attack_protection, AttackProtection) def test_blacklists(self): self.assertIsInstance(self.a0.blacklists, Blacklists) def test_client_credentials(self): self.assertIsInstance(self.a0.client_credentials, ClientCredentials) def test_client_grants(self): self.assertIsInstance(self.a0.client_grants, ClientGrants) def test_clients(self): self.assertIsInstance(self.a0.clients, Clients) def test_connections(self): self.assertIsInstance(self.a0.connections, Connections) def test_custom_domains(self): self.assertIsInstance(self.a0.custom_domains, CustomDomains) def test_device_credentials(self): self.assertIsInstance(self.a0.device_credentials, DeviceCredentials) def test_email_templates(self): self.assertIsInstance(self.a0.email_templates, EmailTemplates) def test_emails(self): self.assertIsInstance(self.a0.emails, Emails) def test_grants(self): self.assertIsInstance(self.a0.grants, Grants) def test_guardian(self): self.assertIsInstance(self.a0.guardian, Guardian) def test_hooks(self): self.assertIsInstance(self.a0.hooks, Hooks) def test_jobs(self): self.assertIsInstance(self.a0.jobs, Jobs) def test_log_streams(self): self.assertIsInstance(self.a0.log_streams, LogStreams) def test_logs(self): self.assertIsInstance(self.a0.logs, Logs) def test_network_acls(self): self.assertIsInstance(self.a0.network_acls, NetworkAcls) def test_organizations(self): self.assertIsInstance(self.a0.organizations, Organizations) def test_prompts(self): self.assertIsInstance(self.a0.prompts, Prompts) def test_resource_servers(self): self.assertIsInstance(self.a0.resource_servers, ResourceServers) def test_roles(self): self.assertIsInstance(self.a0.roles, Roles) def test_rules_configs(self): self.assertIsInstance(self.a0.rules_configs, RulesConfigs) def test_rules(self): self.assertIsInstance(self.a0.rules, Rules) def test_stats(self): self.assertIsInstance(self.a0.stats, Stats) def test_tenants(self): self.assertIsInstance(self.a0.tenants, Tenants) def test_tickets(self): self.assertIsInstance(self.a0.tickets, Tickets) def test_user_blocks(self): self.assertIsInstance(self.a0.user_blocks, UserBlocks) def test_users_by_email(self): self.assertIsInstance(self.a0.users_by_email, UsersByEmail) def test_users(self): self.assertIsInstance(self.a0.users, Users) def test_args(self): rest_options = RestClientOptions(retries=99) auth0 = Auth0(self.domain, self.token, rest_options=rest_options) self.assertEqual(auth0.users.client.options.retries, 99) auth0-auth0-python-0f6dbea/auth0/test/management/test_blacklists.py000066400000000000000000000030521506260514000255210ustar00rootroot00000000000000import unittest from unittest import mock from ...management.blacklists import Blacklists class TestBlacklists(unittest.TestCase): def test_init_with_optionals(self): t = Blacklists( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.blacklists.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value t = Blacklists(domain="domain", token="jwttoken") t.get(aud="an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/blacklists/tokens", params={"aud": "an-id"} ) @mock.patch("auth0.management.blacklists.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value t = Blacklists(domain="domain", token="jwttoken") # create without audience t.create(jti="the-jti") args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/blacklists/tokens", args[0]) self.assertEqual(kwargs["data"], {"jti": "the-jti"}) # create with audience t.create(jti="the-jti", aud="the-aud") args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/blacklists/tokens", args[0]) self.assertEqual(kwargs["data"], {"jti": "the-jti", "aud": "the-aud"}) auth0-auth0-python-0f6dbea/auth0/test/management/test_branding.py000066400000000000000000000107201506260514000251520ustar00rootroot00000000000000import unittest from unittest import mock from ...management.branding import Branding class TestBranding(unittest.TestCase): def test_init_with_optionals(self): branding = Branding( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(branding.client.options.timeout, (10, 2)) telemetry = branding.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry, None) @mock.patch("auth0.management.branding.RestClient") def test_get(self, mock_rc): api = mock_rc.return_value branding = Branding(domain="domain", token="jwttoken") branding.get() api.get.assert_called_with( "https://domain/api/v2/branding", ) @mock.patch("auth0.management.branding.RestClient") def test_update(self, mock_rc): api = mock_rc.return_value api.patch.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.update({"a": "b", "c": "d"}) api.patch.assert_called_with( "https://domain/api/v2/branding", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.branding.RestClient") def test_get_template_universal_login(self, mock_rc): api = mock_rc.return_value branding = Branding(domain="domain", token="jwttoken") branding.get_template_universal_login() api.get.assert_called_with( "https://domain/api/v2/branding/templates/universal-login", ) @mock.patch("auth0.management.branding.RestClient") def test_delete_template_universal_login(self, mock_rc): api = mock_rc.return_value branding = Branding(domain="domain", token="jwttoken") branding.delete_template_universal_login() api.delete.assert_called_with( "https://domain/api/v2/branding/templates/universal-login", ) @mock.patch("auth0.rest.requests.request") def test_update_template_universal_login(self, mock_rc): mock_rc.return_value.status_code = 200 mock_rc.return_value.text = "{}" branding = Branding(domain="domain", token="jwttoken") branding.update_template_universal_login({"a": "b", "c": "d"}) mock_rc.assert_called_with( "PUT", "https://domain/api/v2/branding/templates/universal-login", json={"template": {"a": "b", "c": "d"}}, headers=mock.ANY, timeout=5.0, ) @mock.patch("auth0.management.branding.RestClient") def test_get_default_branding_theme(self, mock_rc): api = mock_rc.return_value api.get.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.get_default_branding_theme() api.get.assert_called_with( "https://domain/api/v2/branding/themes/default", ) @mock.patch("auth0.management.branding.RestClient") def test_get_branding_theme(self, mock_rc): api = mock_rc.return_value api.get.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.get_branding_theme("theme_id") api.get.assert_called_with( "https://domain/api/v2/branding/themes/theme_id", ) @mock.patch("auth0.management.branding.RestClient") def test_delete_branding_theme(self, mock_rc): api = mock_rc.return_value api.delete.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.delete_branding_theme("theme_id") api.delete.assert_called_with( "https://domain/api/v2/branding/themes/theme_id", ) @mock.patch("auth0.management.branding.RestClient") def test_update_branding_theme(self, mock_rc): api = mock_rc.return_value api.patch.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.update_branding_theme("theme_id", {}) api.patch.assert_called_with( "https://domain/api/v2/branding/themes/theme_id", data={}, ) @mock.patch("auth0.management.branding.RestClient") def test_create_branding_theme(self, mock_rc): api = mock_rc.return_value api.post.return_value = {} branding = Branding(domain="domain", token="jwttoken") branding.create_branding_theme({}) api.post.assert_called_with( "https://domain/api/v2/branding/themes", data={}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_client_credentials.py000066400000000000000000000037101506260514000272220ustar00rootroot00000000000000import unittest from unittest import mock from ...management.client_credentials import ClientCredentials class TestClientCredentials(unittest.TestCase): def test_init_with_optionals(self): t = ClientCredentials( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.client_credentials.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = ClientCredentials(domain="domain", token="jwttoken") c.all("cid") mock_instance.get.assert_called_with( "https://domain/api/v2/clients/cid/credentials" ) @mock.patch("auth0.management.client_credentials.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = ClientCredentials(domain="domain", token="jwttoken") c.get("cid", "this-id") mock_instance.get.assert_called_with( "https://domain/api/v2/clients/cid/credentials/this-id" ) @mock.patch("auth0.management.client_credentials.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = ClientCredentials(domain="domain", token="jwttoken") c.create("cid", {"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/clients/cid/credentials", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.client_credentials.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = ClientCredentials(domain="domain", token="jwttoken") c.delete("cid", "this-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/clients/cid/credentials/this-id" ) auth0-auth0-python-0f6dbea/auth0/test/management/test_client_grants.py000066400000000000000000000120611506260514000262220ustar00rootroot00000000000000import unittest from unittest import mock from ...management.client_grants import ClientGrants class TestClientGrants(unittest.TestCase): def test_init_with_optionals(self): t = ClientGrants( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.client_grants.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = ClientGrants(domain="domain", token="jwttoken") # With default params c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/client-grants", args[0]) self.assertEqual( kwargs["params"], { "audience": None, "page": None, "per_page": None, "include_totals": "false", "client_id": None, "allow_any_organization": None, }, ) # With audience c.all(audience="http://domain.auth0.com/api/v2/") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/client-grants", args[0]) self.assertEqual( kwargs["params"], { "audience": "http://domain.auth0.com/api/v2/", "page": None, "per_page": None, "include_totals": "false", "client_id": None, "allow_any_organization": None, }, ) # With pagination params c.all(per_page=23, page=7, include_totals=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/client-grants", args[0]) self.assertEqual( kwargs["params"], { "audience": None, "page": 7, "per_page": 23, "include_totals": "true", "client_id": None, "allow_any_organization": None, }, ) # With client_id param c.all(client_id="exampleid") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/client-grants", args[0]) self.assertEqual( kwargs["params"], { "audience": None, "page": None, "per_page": None, "include_totals": "false", "client_id": "exampleid", "allow_any_organization": None, }, ) # With allow any organization c.all(allow_any_organization=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/client-grants", args[0]) self.assertEqual( kwargs["params"], { "audience": None, "page": None, "per_page": None, "include_totals": "false", "client_id": None, "allow_any_organization": True, }, ) @mock.patch("auth0.management.client_grants.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = ClientGrants(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/client-grants", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.client_grants.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = ClientGrants(domain="domain", token="jwttoken") c.delete("this-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/client-grants/this-id" ) @mock.patch("auth0.management.client_grants.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value c = ClientGrants(domain="domain", token="jwttoken") c.update("this-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/client-grants/this-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.client_grants.RestClient") def test_get_organizations(self, mock_rc): mock_instance = mock_rc.return_value c = ClientGrants(domain="domain", token="jwttoken") c.get_organizations("cgid") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/client-grants/cgid/organizations", args[0] ) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "false", "from": None, "take": None, }, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_clients.py000066400000000000000000000105141506260514000250300ustar00rootroot00000000000000import unittest from unittest import mock from ...management.clients import Clients class TestClients(unittest.TestCase): def test_init_with_optionals(self): t = Clients(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.clients.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") # Default parameters are requested c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients", args[0]) self.assertEqual( kwargs["params"], {"fields": None, "include_fields": "true", "page": None, "per_page": None}, ) # Fields filter c.all(fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients", args[0]) self.assertEqual( kwargs["params"], { "fields": "a,b", "include_fields": "false", "page": None, "per_page": None, }, ) # Specific pagination c.all(page=7, per_page=25) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients", args[0]) self.assertEqual( kwargs["params"], {"fields": None, "include_fields": "true", "page": 7, "per_page": 25}, ) # Extra parameters c.all(extra_params={"some_key": "some_value"}) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "page": None, "per_page": None, "some_key": "some_value", }, ) @mock.patch("auth0.management.clients.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/clients", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.clients.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") c.get("this-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients/this-id", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) c.get("this-id", fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/clients/this-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) @mock.patch("auth0.management.clients.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") c.delete("this-id") mock_instance.delete.assert_called_with("https://domain/api/v2/clients/this-id") @mock.patch("auth0.management.clients.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") c.update("this-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/clients/this-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.clients.RestClient") def test_rotate_secret(self, mock_rc): mock_instance = mock_rc.return_value c = Clients(domain="domain", token="jwttoken") c.rotate_secret("this-id") mock_instance.post.assert_called_with( "https://domain/api/v2/clients/this-id/rotate-secret", data={"id": "this-id"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_connections.py000066400000000000000000000145451506260514000257210ustar00rootroot00000000000000import unittest from unittest import mock from ...management.connections import Connections class TestConnection(unittest.TestCase): def test_init_with_optionals(self): t = Connections( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.connections.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} # Default parameters are requested c = Connections(domain="domain", token="jwttoken") c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "strategy": None, "page": None, "per_page": None, "include_fields": "true", "name": None, }, ) # Fields filter c.all(fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": "a,b", "strategy": None, "page": None, "per_page": None, "include_fields": "false", "name": None, }, ) # Fields + strategy filter c.all(fields=["a", "b"], strategy="auth0", include_fields=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": "a,b", "strategy": "auth0", "page": None, "per_page": None, "include_fields": "true", "name": None, }, ) # Specific pagination c.all(page=7, per_page=25) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "strategy": None, "page": 7, "per_page": 25, "include_fields": "true", "name": None, }, ) # Extra parameters c.all(extra_params={"some_key": "some_value"}) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "strategy": None, "page": None, "per_page": None, "include_fields": "true", "some_key": "some_value", "name": None, }, ) # Name c.all(name="foo") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "strategy": None, "page": None, "per_page": None, "include_fields": "true", "name": "foo", }, ) @mock.patch("auth0.management.connections.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} c = Connections(domain="domain", token="jwttoken") c.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) c.get("an-id", fields=["a", "b"]) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "true"}) c.get("an-id", fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/connections/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) @mock.patch("auth0.management.connections.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.delete.return_value = {} c = Connections(domain="domain", token="jwttoken") c.delete("this-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/connections/this-id" ) @mock.patch("auth0.management.connections.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.patch.return_value = {} c = Connections(domain="domain", token="jwttoken") c.update("that-id", {"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/connections/that-id", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.connections.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.post.return_value = {} c = Connections(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/connections", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.connections.RestClient") def test_delete_user_by_email(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.delete_user_by_email.return_value = {} c = Connections(domain="domain", token="jwttoken") c.delete_user_by_email("123", "test@example.com") mock_instance.delete.assert_called_with( "https://domain/api/v2/connections/123/users", params={"email": "test@example.com"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_custom_domains.py000066400000000000000000000037051506260514000264170ustar00rootroot00000000000000import unittest from unittest import mock from ...management.custom_domains import CustomDomains class TestCustomDomains(unittest.TestCase): def test_init_with_optionals(self): t = CustomDomains( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.custom_domains.RestClient") def test_get_all(self, mock_rc): mock_instance = mock_rc.return_value g = CustomDomains(domain="domain", token="jwttoken") g.all() mock_instance.get.assert_called_with("https://domain/api/v2/custom-domains") @mock.patch("auth0.management.custom_domains.RestClient") def test_create_new(self, mock_rc): mock_instance = mock_rc.return_value g = CustomDomains(domain="domain", token="jwttoken") g.create_new(body={"a": "b", "c": "d", "e": "f"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/custom-domains", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d", "e": "f"}) @mock.patch("auth0.management.custom_domains.RestClient") def test_get_domain_by_id(self, mock_rc): mock_instance = mock_rc.return_value g = CustomDomains(domain="domain", token="jwttoken") g.get("an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/custom-domains/an-id" ) @mock.patch("auth0.management.custom_domains.RestClient") def test_verify(self, mock_rc): mock_instance = mock_rc.return_value g = CustomDomains(domain="domain", token="jwttoken") g.verify("an-id") args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/custom-domains/an-id/verify", args[0]) auth0-auth0-python-0f6dbea/auth0/test/management/test_device_credentials.py000066400000000000000000000053521506260514000272070ustar00rootroot00000000000000import unittest from unittest import mock from ...management.device_credentials import DeviceCredentials class TestDeviceCredentials(unittest.TestCase): def test_init_with_optionals(self): t = DeviceCredentials( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.device_credentials.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = DeviceCredentials(domain="domain", token="jwttoken") c.get(user_id="uid", client_id="cid", type="type", page=0, per_page=20) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/device-credentials", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "user_id": "uid", "client_id": "cid", "type": "type", "page": 0, "per_page": 20, "include_totals": "false", }, ) c.get( user_id="uid", client_id="cid", type="type", page=5, per_page=50, include_totals=True, ) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/device-credentials", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "user_id": "uid", "client_id": "cid", "type": "type", "page": 5, "per_page": 50, "include_totals": "true", }, ) @mock.patch("auth0.management.device_credentials.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = DeviceCredentials(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/device-credentials", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.device_credentials.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = DeviceCredentials(domain="domain", token="jwttoken") c.delete("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/device-credentials/an-id", ) auth0-auth0-python-0f6dbea/auth0/test/management/test_email_endpoints.py000066400000000000000000000032121506260514000265360ustar00rootroot00000000000000import unittest from unittest import mock from ...management.email_templates import EmailTemplates class TestClients(unittest.TestCase): def test_init_with_optionals(self): t = EmailTemplates( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.email_templates.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = EmailTemplates(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/email-templates", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.email_templates.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = EmailTemplates(domain="domain", token="jwttoken") c.get("this-template-name") mock_instance.get.assert_called_with( "https://domain/api/v2/email-templates/this-template-name" ) @mock.patch("auth0.management.email_templates.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value c = EmailTemplates(domain="domain", token="jwttoken") c.update("this-template-name", {"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/email-templates/this-template-name", data={"a": "b", "c": "d"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_emails.py000066400000000000000000000043371506260514000246470ustar00rootroot00000000000000import unittest from unittest import mock from ...management.emails import Emails class TestEmails(unittest.TestCase): def test_init_with_optionals(self): t = Emails(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.emails.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value e = Emails(domain="domain", token="jwttoken") e.get() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/emails/provider", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) e.get(fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/emails/provider", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) @mock.patch("auth0.management.emails.RestClient") def test_config(self, mock_rc): mock_instance = mock_rc.return_value e = Emails(domain="domain", token="jwttoken") e.config({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/emails/provider", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.emails.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value e = Emails(domain="domain", token="jwttoken") e.update({"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/emails/provider", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.emails.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value e = Emails(domain="domain", token="jwttoken") e.delete() mock_instance.delete.assert_called_with("https://domain/api/v2/emails/provider") auth0-auth0-python-0f6dbea/auth0/test/management/test_grants.py000066400000000000000000000026511506260514000246700ustar00rootroot00000000000000import unittest from unittest import mock from ...management.grants import Grants class TestGrants(unittest.TestCase): def test_init_with_optionals(self): t = Grants(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.grants.RestClient") def test_get_all(self, mock_rc): mock_instance = mock_rc.return_value g = Grants(domain="domain", token="jwttoken") g.all( extra_params={"user_id": "an-id", "client_id": "an-id", "audience": "test"} ) args, kwargs = mock_instance.get.call_args mock_instance.get.assert_called_with( "https://domain/api/v2/grants", params={ "user_id": "an-id", "client_id": "an-id", "audience": "test", "page": None, "per_page": None, "include_totals": "false", }, ) @mock.patch("auth0.management.grants.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = Grants(domain="domain", token="jwttoken") c.delete("an-id") mock_instance.delete.assert_called_with("https://domain/api/v2/grants/an-id") auth0-auth0-python-0f6dbea/auth0/test/management/test_guardian.py000066400000000000000000000120751506260514000251650ustar00rootroot00000000000000import unittest from unittest import mock from ...management.guardian import Guardian class TestGuardian(unittest.TestCase): def test_init_with_optionals(self): t = Guardian( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.guardian.RestClient") def test_all_factors(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.all_factors() mock_instance.get.assert_called_with("https://domain/api/v2/guardian/factors") @mock.patch("auth0.management.guardian.RestClient") def test_update_factor(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.update_factor("push-notification", {"enabled": True}) args, kwargs = mock_instance.put.call_args self.assertEqual( "https://domain/api/v2/guardian/factors/push-notification", args[0] ) self.assertEqual(kwargs["data"], {"enabled": True}) g.update_factor("sms", {"enabled": False}) args, kwargs = mock_instance.put.call_args self.assertEqual("https://domain/api/v2/guardian/factors/sms", args[0]) self.assertEqual(kwargs["data"], {"enabled": False}) @mock.patch("auth0.management.guardian.RestClient") def test_update_templates(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.update_templates( {"enrollment_message": "hello", "verification_message": "verified"} ) args, kwargs = mock_instance.put.call_args self.assertEqual( "https://domain/api/v2/guardian/factors/sms/templates", args[0] ) self.assertEqual( kwargs["data"], {"enrollment_message": "hello", "verification_message": "verified"}, ) @mock.patch("auth0.management.guardian.RestClient") def test_get_templates(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.get_templates() mock_instance.get.assert_called_with( "https://domain/api/v2/guardian/factors/sms/templates" ) @mock.patch("auth0.management.guardian.RestClient") def test_get_enrollment(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.get_enrollment("some_id") mock_instance.get.assert_called_with( "https://domain/api/v2/guardian/enrollments/some_id" ) @mock.patch("auth0.management.guardian.RestClient") def test_delete_enrollment(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.delete_enrollment("some_id") mock_instance.delete.assert_called_with( "https://domain/api/v2/guardian/enrollments/some_id" ) @mock.patch("auth0.management.guardian.RestClient") def test_create_enrollment_ticket(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.create_enrollment_ticket( {"user_id": "some_id", "email": "test@test.com", "send_mail": "false"} ) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/guardian/enrollments/ticket", args[0]) self.assertEqual( kwargs["data"], {"user_id": "some_id", "email": "test@test.com", "send_mail": "false"}, ) @mock.patch("auth0.management.guardian.RestClient") def test_get_factor_providers(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.get_factor_providers("sms", "twilio") mock_instance.get.assert_called_with( "https://domain/api/v2/guardian/factors/sms/providers/twilio" ) @mock.patch("auth0.management.guardian.RestClient") def test_update_factor_providers(self, mock_rc): mock_instance = mock_rc.return_value g = Guardian(domain="domain", token="jwttoken") g.update_factor_providers( "sms", "twilio", { "from": "test@test.com", "messaging_service_sid": "qwerty", "auth_token": "abc.xyz.123", "sid": "abc.xyz", }, ) args, kwargs = mock_instance.put.call_args self.assertEqual( "https://domain/api/v2/guardian/factors/sms/providers/twilio", args[0] ) self.assertEqual( kwargs["data"], { "from": "test@test.com", "messaging_service_sid": "qwerty", "auth_token": "abc.xyz.123", "sid": "abc.xyz", }, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_hooks.py000066400000000000000000000130211506260514000245060ustar00rootroot00000000000000import unittest from unittest import mock from ...management.hooks import Hooks class TestRules(unittest.TestCase): def test_init_with_optionals(self): t = Hooks(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.hooks.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") # with default params c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks", args[0]) self.assertEqual( kwargs["params"], { "enabled": "true", "fields": None, "include_fields": "true", "page": None, "per_page": None, "include_totals": "false", }, ) # with fields params c.all(enabled=False, fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks", args[0]) self.assertEqual( kwargs["params"], { "fields": "a,b", "include_fields": "false", "enabled": "false", "page": None, "per_page": None, "include_totals": "false", }, ) # with pagination params c.all(page=3, per_page=27, include_totals=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "enabled": "true", "page": 3, "per_page": 27, "include_totals": "true", }, ) @mock.patch("auth0.management.hooks.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/hooks", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.hooks.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": None}) c.get("an-id", fields=["a", "b"]) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b"}) @mock.patch("auth0.management.hooks.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.delete("an-id") mock_instance.delete.assert_called_with("https://domain/api/v2/hooks/an-id") @mock.patch("auth0.management.hooks.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.update("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/hooks/an-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) # test for hooks secrets @mock.patch("auth0.management.hooks.RestClient") def test_add_secret(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.add_secrets("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/hooks/an-id/secrets", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.hooks.RestClient") def test_get_secrets(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.get_secrets("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/hooks/an-id/secrets", args[0]) self.assertNotIn("params", kwargs) @mock.patch("auth0.management.hooks.RestClient") def test_delete_secrets(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.delete_secrets("an-id", ["a", "b"]) args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/hooks/an-id/secrets", args[0]) self.assertEqual(kwargs["data"], ["a", "b"]) @mock.patch("auth0.management.hooks.RestClient") def test_update_secrets(self, mock_rc): mock_instance = mock_rc.return_value c = Hooks(domain="domain", token="jwttoken") c.update_secrets("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/hooks/an-id/secrets", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) auth0-auth0-python-0f6dbea/auth0/test/management/test_jobs.py000066400000000000000000000067261506260514000243360ustar00rootroot00000000000000import unittest from unittest import mock from ...management.jobs import Jobs class TestJobs(unittest.TestCase): def test_init_with_optionals(self): t = Jobs(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.jobs.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value j = Jobs(domain="domain", token="jwttoken") j.get("an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/jobs/an-id", ) @mock.patch("auth0.management.jobs.RestClient") def test_get_failed_job(self, mock_rc): mock_instance = mock_rc.return_value j = Jobs(domain="domain", token="jwttoken") j.get_failed_job("an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/jobs/an-id/errors", ) @mock.patch("auth0.management.jobs.RestClient") def test_export_users(self, mock_rc): mock_instance = mock_rc.return_value j = Jobs(domain="domain", token="jwttoken") j.export_users({"connection_id": "cxn_id", "format": "json"}) mock_instance.post.assert_called_with( "https://domain/api/v2/jobs/users-exports", data={"connection_id": "cxn_id", "format": "json"}, ) @mock.patch("auth0.management.jobs.RestClient") def test_import_users(self, mock_rc): mock_instance = mock_rc.return_value j = Jobs(domain="domain", token="jwttoken") j.import_users(connection_id="1234", file_obj={}) mock_instance.file_post.assert_called_with( "https://domain/api/v2/jobs/users-imports", data={ "connection_id": "1234", "upsert": "false", "send_completion_email": "true", "external_id": None, }, files={"users": {}}, ) j.import_users( connection_id="1234", file_obj={}, upsert=True, send_completion_email=False, external_id="ext-id-123", ) mock_instance.file_post.assert_called_with( "https://domain/api/v2/jobs/users-imports", data={ "connection_id": "1234", "upsert": "true", "send_completion_email": "false", "external_id": "ext-id-123", }, files={"users": {}}, ) j.import_users( connection_id="1234", file_obj={}, upsert=False, send_completion_email=True ) mock_instance.file_post.assert_called_with( "https://domain/api/v2/jobs/users-imports", data={ "connection_id": "1234", "upsert": "false", "send_completion_email": "true", "external_id": None, }, files={"users": {}}, ) @mock.patch("auth0.management.jobs.RestClient") def test_verification_email(self, mock_rc): mock_instance = mock_rc.return_value j = Jobs(domain="domain", token="jwttoken") j.send_verification_email({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/jobs/verification-email", data={"a": "b", "c": "d"} ) auth0-auth0-python-0f6dbea/auth0/test/management/test_log_streams.py000066400000000000000000000053351506260514000257130ustar00rootroot00000000000000import unittest from unittest import mock from ...management.log_streams import LogStreams class TestLogStreams(unittest.TestCase): def test_init_with_optionals(self): t = LogStreams( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.log_streams.RestClient") def test_list(self, mock_rc): mock_instance = mock_rc.return_value c = LogStreams(domain="domain", token="jwttoken") c.list() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/log-streams", args[0]) @mock.patch("auth0.management.log_streams.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = LogStreams(domain="domain", token="jwttoken") c.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/log-streams/an-id", args[0]) @mock.patch("auth0.management.log_streams.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = LogStreams(domain="domain", token="jwttoken") # Sample data belongs to an `http` stream log_stream_data = { "name": "string", "type": "http", "sink": { "httpEndpoint": "string", "httpContentType": "string", "httpContentFormat": "JSONLINES|JSONARRAY", "httpAuthorization": "string", }, } c.create(log_stream_data) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/log-streams", args[0]) self.assertEqual(kwargs["data"], log_stream_data) @mock.patch("auth0.management.log_streams.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = LogStreams(domain="domain", token="jwttoken") c.delete("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/log-streams/an-id" ) @mock.patch("auth0.management.log_streams.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value log_stream_update = {"name": "string"} c = LogStreams(domain="domain", token="jwttoken") c.update("an-id", log_stream_update) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/log-streams/an-id", args[0]) self.assertEqual(kwargs["data"], log_stream_update) auth0-auth0-python-0f6dbea/auth0/test/management/test_logs.py000066400000000000000000000036421506260514000243370ustar00rootroot00000000000000import unittest from unittest import mock from ...management.logs import Logs class TestLogs(unittest.TestCase): def test_init_with_optionals(self): t = Logs(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.logs.RestClient") def test_search(self, mock_rc): mock_instance = mock_rc.return_value logs = Logs(domain="domain", token="jwttoken") logs.search() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/logs", args[0]) self.assertIsNone(kwargs["params"]["sort"]) self.assertIsNone(kwargs["params"]["q"]) self.assertIsNone(kwargs["params"]["from"]) self.assertIsNone(kwargs["params"]["take"]) self.assertEqual(kwargs["params"]["include_fields"], "true") self.assertEqual(kwargs["params"]["include_totals"], "true") self.assertEqual(kwargs["params"]["per_page"], 50) self.assertEqual(kwargs["params"]["page"], 0) self.assertIsNone(kwargs["params"]["fields"]) logs.search(fields=["description", "client_id"]) args, kwargs = mock_instance.get.call_args self.assertEqual(kwargs["params"]["fields"], "description,client_id") logs.search(page=0, per_page=2) args, kwargs = mock_instance.get.call_args self.assertEqual(kwargs["params"]["per_page"], 2) self.assertEqual(kwargs["params"]["page"], 0) @mock.patch("auth0.management.logs.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value logs = Logs(domain="domain", token="jwttoken") logs.get("get_id") mock_instance.get.assert_called_with("https://domain/api/v2/logs/get_id") auth0-auth0-python-0f6dbea/auth0/test/management/test_network_acls.py000066400000000000000000000056421506260514000260700ustar00rootroot00000000000000import unittest from unittest import mock from ...management.network_acls import NetworkAcls class TestNetworkAcls(unittest.TestCase): def test_init_with_optionals(self): t = NetworkAcls( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.network_acls.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.all() mock_instance.get.assert_called_with( "https://domain/api/v2/network-acls", params={"page": 0, "per_page": 25, "include_totals": "true"}, ) s.all(page=1, per_page=50, include_totals=False) mock_instance.get.assert_called_with( "https://domain/api/v2/network-acls", params={"page": 1, "per_page": 50, "include_totals": "false"}, ) @mock.patch("auth0.management.network_acls.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.create({"name": "test"}) mock_instance.post.assert_called_with( "https://domain/api/v2/network-acls", data={"name": "test"} ) @mock.patch("auth0.management.network_acls.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.get("an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/network-acls/an-id" ) @mock.patch("auth0.management.network_acls.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.delete("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/network-acls/an-id" ) @mock.patch("auth0.management.network_acls.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.update("an-id", {"a": "b", "c": "d"}) mock_instance.put.assert_called_with( "https://domain/api/v2/network-acls/an-id", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.network_acls.RestClient") def test_update_partial(self, mock_rc): mock_instance = mock_rc.return_value s = NetworkAcls(domain="domain", token="jwttoken") s.update_partial("an-id", {"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/network-acls/an-id", data={"a": "b", "c": "d"}, )auth0-auth0-python-0f6dbea/auth0/test/management/test_organizations.py000066400000000000000000000427071506260514000262670ustar00rootroot00000000000000import unittest from unittest import mock from ...management.organizations import Organizations class TestOrganizations(unittest.TestCase): def test_init_with_optionals(self): t = Organizations( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) # Organizations @mock.patch("auth0.management.organizations.RestClient") def test_all_organizations(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") # Default parameters are requested c.all_organizations() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/organizations", args[0]) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "true", "from": None, "take": None, }, ) # Basic pagination c.all_organizations(page=7, per_page=25, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/organizations", args[0]) self.assertEqual( kwargs["params"], { "page": 7, "per_page": 25, "include_totals": "false", "from": None, "take": None, }, ) # Checkpoint pagination c.all_organizations(from_param=8675309, take=75) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/organizations", args[0]) self.assertEqual( kwargs["params"], { "from": 8675309, "take": 75, "page": None, "per_page": None, "include_totals": "true", }, ) @mock.patch("auth0.management.organizations.RestClient") def test_get_organization_by_name(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.get_organization_by_name("test-org") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/organizations/name/test-org", args[0]) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.organizations.RestClient") def test_get_organization(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.get_organization("org123") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/organizations/org123", args[0]) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.organizations.RestClient") def test_create_organization(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.create_organization({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/organizations", data={"a": "b", "c": "d"} ) @mock.patch("auth0.management.organizations.RestClient") def test_update_organization(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.update_organization("this-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/organizations/this-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.organizations.RestClient") def test_delete_organization(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_organization("this-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/this-id" ) # Organization Connections @mock.patch("auth0.management.organizations.RestClient") def test_all_organization_connections(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") # Default parameters are requested c.all_organization_connections("test-org") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/enabled_connections", args[0] ) self.assertEqual(kwargs["params"], {"page": None, "per_page": None}) # Specific pagination c.all_organization_connections("test-org", page=7, per_page=25) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/enabled_connections", args[0] ) self.assertEqual(kwargs["params"], {"page": 7, "per_page": 25}) @mock.patch("auth0.management.organizations.RestClient") def test_get_organization_connection(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.get_organization_connection("test-org", "test-con") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/enabled_connections/test-con", args[0], ) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.organizations.RestClient") def test_create_organization_connection(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.create_organization_connection("test-org", {"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/organizations/test-org/enabled_connections", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.organizations.RestClient") def test_update_organization_connection(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.update_organization_connection("test-org", "test-con", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/enabled_connections/test-con", args[0], ) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.organizations.RestClient") def test_delete_organization_connection(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_organization_connection("test-org", "test-con") mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/test-org/enabled_connections/test-con" ) # Organization Members @mock.patch("auth0.management.organizations.RestClient") def test_all_organization_members(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") # Default parameters are requested c.all_organization_members("test-org") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members", args[0] ) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "true", "from": None, "take": None, "fields": None, "include_fields": "true", }, ) # Specific pagination c.all_organization_members( "test-org", page=7, per_page=25, include_totals=False ) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members", args[0] ) self.assertEqual( kwargs["params"], { "page": 7, "per_page": 25, "include_totals": "false", "from": None, "take": None, "fields": None, "include_fields": "true", }, ) # Checkpoint pagination c.all_organization_members("test-org", from_param=8675309, take=75) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members", args[0] ) self.assertEqual( kwargs["params"], { "from": 8675309, "take": 75, "page": None, "per_page": None, "include_totals": "true", "fields": None, "include_fields": "true", }, ) # With fields c.all_organization_members("test-org", fields=["a,b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members", args[0] ) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "true", "from": None, "take": None, "fields": "a,b", "include_fields": "false", }, ) @mock.patch("auth0.management.organizations.RestClient") def test_create_organization_members(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.create_organization_members("test-org", {"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/organizations/test-org/members", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.organizations.RestClient") def test_delete_organization_members(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_organization_members("test-org", {"a": "b", "c": "d"}) mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/test-org/members", data={"a": "b", "c": "d"}, ) # Organization Member Roles @mock.patch("auth0.management.organizations.RestClient") def test_all_organization_member_roles(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") # Default parameters are requested c.all_organization_member_roles("test-org", "test-user") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members/test-user/roles", args[0], ) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "false", } ) # Specific pagination c.all_organization_member_roles("test-org", "test-user", page=7, per_page=25, include_totals=True) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/members/test-user/roles", args[0], ) self.assertEqual( kwargs["params"], { "page": 7, "per_page": 25, "include_totals": "true", } ) @mock.patch("auth0.management.organizations.RestClient") def test_create_organization_member_roles(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.create_organization_member_roles( "test-org", "test-user", {"a": "b", "c": "d"} ) mock_instance.post.assert_called_with( "https://domain/api/v2/organizations/test-org/members/test-user/roles", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.organizations.RestClient") def test_delete_organization_member_roles(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_organization_member_roles( "test-org", "test-user", {"a": "b", "c": "d"} ) mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/test-org/members/test-user/roles", data={"a": "b", "c": "d"}, ) # Organization Invitations @mock.patch("auth0.management.organizations.RestClient") def test_all_organization_invitations(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") # Default parameters are requested c.all_organization_invitations("test-org") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/invitations", args[0] ) self.assertEqual( kwargs["params"], { "page": None, "per_page": None, "include_totals": "false", }, ) # Specific pagination c.all_organization_invitations("test-org", page=7, per_page=25) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/invitations", args[0] ) self.assertEqual( kwargs["params"], { "page": 7, "per_page": 25, "include_totals": "false", }, ) # Return paged collection with paging properties c.all_organization_invitations( "test-org", page=7, per_page=25, include_totals=True ) args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/invitations", args[0] ) self.assertEqual( kwargs["params"], { "page": 7, "per_page": 25, "include_totals": "true", }, ) @mock.patch("auth0.management.organizations.RestClient") def test_get_organization_invitation(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.get_organization_invitation("test-org", "test-inv") args, kwargs = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/organizations/test-org/invitations/test-inv", args[0] ) self.assertEqual(kwargs["params"], {}) @mock.patch("auth0.management.organizations.RestClient") def test_create_organization_invitation(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.create_organization_invitation("test-org", {"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/organizations/test-org/invitations", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.organizations.RestClient") def test_delete_organization_invitation(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_organization_invitation("test-org", "test-inv") mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/test-org/invitations/test-inv" ) @mock.patch("auth0.management.organizations.RestClient") def test_get_client_grants(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.get_client_grants("test-org") mock_instance.get.assert_called_with( "https://domain/api/v2/organizations/test-org/client-grants", params={ "audience": None, "client_id": None, "page": None, "per_page": None, "include_totals": "false", }, ) @mock.patch("auth0.management.organizations.RestClient") def test_add_client_grant(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.add_client_grant("test-org", "test-cg") mock_instance.post.assert_called_with( "https://domain/api/v2/organizations/test-org/client-grants", data={"grant_id": "test-cg"}, ) @mock.patch("auth0.management.organizations.RestClient") def test_delete_client_grant(self, mock_rc): mock_instance = mock_rc.return_value c = Organizations(domain="domain", token="jwttoken") c.delete_client_grant("test-org", "test-cg") mock_instance.delete.assert_called_with( "https://domain/api/v2/organizations/test-org/client-grants/test-cg", ) auth0-auth0-python-0f6dbea/auth0/test/management/test_prompts.py000066400000000000000000000041701506260514000250740ustar00rootroot00000000000000import unittest from unittest import mock from ...management.prompts import Prompts class TestPrompts(unittest.TestCase): def test_init_with_optionals(self): t = Prompts(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.prompts.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value p = Prompts(domain="domain", token="jwttoken") p.get() args, _ = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/prompts", args[0]) @mock.patch("auth0.management.prompts.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value p = Prompts(domain="domain", token="jwttoken") p.update({"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/prompts", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.prompts.RestClient") def test_get_custom_text(self, mock_rc): mock_instance = mock_rc.return_value p = Prompts(domain="domain", token="jwttoken") p.get_custom_text("some-prompt", "some-language") args, _ = mock_instance.get.call_args self.assertEqual( "https://domain/api/v2/prompts/some-prompt/custom-text/some-language", args[0], ) @mock.patch("auth0.management.prompts.RestClient") def test_update_custom_text(self, mock_rc): mock_instance = mock_rc.return_value p = Prompts(domain="domain", token="jwttoken") p.update_custom_text("some-prompt", "some-language", {"a": "b", "c": "d"}) args, kwargs = mock_instance.put.call_args self.assertEqual( "https://domain/api/v2/prompts/some-prompt/custom-text/some-language", args[0], ) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) auth0-auth0-python-0f6dbea/auth0/test/management/test_resource_servers.py000066400000000000000000000055361506260514000267770ustar00rootroot00000000000000import unittest from unittest import mock from ...management.resource_servers import ResourceServers class TestResourceServers(unittest.TestCase): def test_init_with_optionals(self): t = ResourceServers( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.resource_servers.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value r = ResourceServers(domain="domain", token="jwttoken") r.create({"name": "TestApi", "identifier": "https://test.com/api"}) mock_instance.post.assert_called_with( "https://domain/api/v2/resource-servers", data={"name": "TestApi", "identifier": "https://test.com/api"}, ) @mock.patch("auth0.management.resource_servers.RestClient") def test_get_all(self, mock_rc): mock_instance = mock_rc.return_value r = ResourceServers(domain="domain", token="jwttoken") # with default params r.get_all() mock_instance.get.assert_called_with( "https://domain/api/v2/resource-servers", params={"page": None, "per_page": None, "include_totals": "false"}, ) # with pagination params r.get_all(page=3, per_page=27, include_totals=True) mock_instance.get.assert_called_with( "https://domain/api/v2/resource-servers", params={"page": 3, "per_page": 27, "include_totals": "true"}, ) @mock.patch("auth0.management.resource_servers.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value r = ResourceServers(domain="domain", token="jwttoken") r.get("some_id") mock_instance.get.assert_called_with( "https://domain/api/v2/resource-servers/some_id" ) @mock.patch("auth0.management.resource_servers.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value r = ResourceServers(domain="domain", token="jwttoken") r.delete("some_id") mock_instance.delete.assert_called_with( "https://domain/api/v2/resource-servers/some_id" ) @mock.patch("auth0.management.resource_servers.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value r = ResourceServers(domain="domain", token="jwttoken") r.update("some_id", {"name": "TestApi2", "identifier": "https://test.com/api2"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/resource-servers/some_id", data={"name": "TestApi2", "identifier": "https://test.com/api2"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_rest.py000066400000000000000000000776241506260514000243630ustar00rootroot00000000000000import base64 import json import sys import unittest from unittest import mock from auth0.rest import RestClient, RestClientOptions from ...exceptions import Auth0Error, RateLimitError class TestRest(unittest.TestCase): # def test_options_are_used_and_override(self): # """ # This test ensures RestClientOptions are read when passed to # RestClient's constructor by (1) configuring a timeout and (2) # turning off Telemetry. This proves that RestClient is inheriting # those options, and overriding it's own constructor arguments. # """ # options = RestClientOptions(telemetry=False, timeout=0.00002, retries=10) # rc = RestClient(jwt="a-token", telemetry=True, timeout=30, options=options) # # Does a timeout occur as expected? # with self.assertRaises(requests.exceptions.Timeout): # rc.get("http://google.com") # # Is RestClient using the RestClientOptions.timeout value properly? # self.assertEqual(rc.options.timeout, 0.00002) # # Is RestClient using the RestClientOptions.retries value properly? # self.assertEqual(rc.options.retries, 10) # # Is RestClient using the RestClientOptions.telemetry value properly? # self.assertEqual(rc.options.telemetry, False) # # Is RestClient using the RestClientOptions.telemetry value properly? # self.assertEqual( # rc.base_headers, # { # "Content-Type": "application/json", # "Authorization": "Bearer a-token", # }, # ) # def test_options_are_created_by_default(self): # """ # This test ensures RestClientOptions are read when passed to # RestClient's constructor by (1) configuring a timeout and (2) # turning off Telemetry. This proves that RestClient is inheriting # those options, and overriding it's own constructor arguments. # """ # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # # Does a timeout occur as expected? # with self.assertRaises(requests.exceptions.Timeout): # rc.get("http://google.com") # # Did RestClient create a RestClientOptions for us? # self.assertIsNotNone(rc.options) # # Did RestClient assign the new RestClientOptions instance the proper timeout value from the constructor? # self.assertEqual(rc.options.timeout, 0.00002) # # Did RestClient use the default RestClientOptions value for retries? # self.assertEqual(rc.options.retries, 3) # # Did RestClient assign the new RestClientOptions instance the proper telemetry value from the constructor? # self.assertEqual(rc.options.telemetry, False) # # Is RestClient using the RestClientOptions.telemetry value properly? # self.assertEqual( # rc.base_headers, # { # "Content-Type": "application/json", # "Authorization": "Bearer a-token", # }, # ) def test_default_options_are_used(self): """ This test ensures RestClientOptions are read when passed to RestClient's constructor by (1) configuring a timeout and (2) turning off Telemetry. This proves that RestClient is inheriting those options, and overriding it's own constructor arguments. """ options = RestClientOptions() rc = RestClient(jwt="a-token", options=options) # Did RestClient store the RestClientOptions? self.assertIsNotNone(rc.options) # Did RestClientOptions use the default 5.0 timeout? self.assertEqual(rc.options.timeout, 5.0) # Did RestClientOptions use the default 3 retries? self.assertEqual(rc.options.retries, 3) # Did RestClientOptions use the default True telemetry value? self.assertEqual(rc.options.telemetry, True) # TODO: Replace the following with more reliable tests. Failing on GitHub Actions. # def test_get_can_timeout(self): # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # rc.get("https://google.com") # def test_post_can_timeout(self): # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # rc.post("https://google.com") # def test_put_can_timeout(self): # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # rc.put("https://google.com") # def test_patch_can_timeout(self): # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # rc.patch("https://google.com") # def test_delete_can_timeout(self): # rc = RestClient(jwt="a-token", telemetry=False, timeout=0.00002) # with self.assertRaises(requests.exceptions.Timeout): # rc.delete("https://google.com") @mock.patch("requests.request") def test_get_custom_timeout(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False, timeout=(10, 2)) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 rc.get("the-url") mock_request.assert_called_with( "GET", "the-url", headers=headers, timeout=(10, 2) ) @mock.patch("requests.request") def test_post_custom_timeout(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False, timeout=(10, 2)) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 rc.post("the-url") mock_request.assert_called_with( "POST", "the-url", headers=headers, timeout=(10, 2) ) @mock.patch("requests.request") def test_put_custom_timeout(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False, timeout=(10, 2)) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 rc.put("the-url") mock_request.assert_called_with( "PUT", "the-url", headers=headers, timeout=(10, 2) ) @mock.patch("requests.request") def test_patch_custom_timeout(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False, timeout=(10, 2)) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 rc.patch("the-url") mock_request.assert_called_with( "PATCH", "the-url", headers=headers, timeout=(10, 2) ) @mock.patch("requests.request") def test_delete_custom_timeout(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False, timeout=(10, 2)) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 rc.delete("the-url") mock_request.assert_called_with( "DELETE", "the-url", headers=headers, timeout=(10, 2) ) @mock.patch("requests.request") def test_get(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 response = rc.get("the-url") mock_request.assert_called_with("GET", "the-url", headers=headers, timeout=5.0) self.assertEqual(response, ["a", "b"]) response = rc.get(url="the/url", params={"A": "param", "B": "param"}) mock_request.assert_called_with( "GET", "the/url", params={"A": "param", "B": "param"}, headers=headers, timeout=5.0, ) self.assertEqual(response, ["a", "b"]) mock_request.return_value.text = "" response = rc.get("the/url") self.assertEqual(response, "") @mock.patch("requests.request") def test_get_errors(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 999, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") @mock.patch("requests.request") def test_get_rate_limit_error(self, mock_request): options = RestClientOptions(telemetry=False, retries=0) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertIsNotNone(context.exception.headers) self.assertEqual(context.exception.headers["x-ratelimit-limit"], "3") self.assertEqual(context.exception.headers["x-ratelimit-remaining"], "6") self.assertEqual(context.exception.headers["x-ratelimit-reset"], "9") self.assertEqual(rc._metrics["retries"], 0) @mock.patch("requests.request") def test_get_rate_limit_error_without_headers(self, mock_request): options = RestClientOptions(telemetry=False, retries=1) rc = RestClient(jwt="a-token", options=options) mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = {} with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, -1) self.assertIsNotNone(context.exception.headers) self.assertEqual(context.exception.headers, {}) self.assertEqual(rc._metrics["retries"], 1) @mock.patch("requests.request") def test_get_rate_limit_custom_retries(self, mock_request): options = RestClientOptions(telemetry=False, retries=5) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertEqual(rc._metrics["retries"], 5) self.assertEqual(rc._metrics["retries"], len(rc._metrics["backoff"])) @mock.patch("requests.request") def test_get_rate_limit_invalid_retries_below_min(self, mock_request): options = RestClientOptions(telemetry=False, retries=-1) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertEqual(rc._metrics["retries"], 0) @mock.patch("requests.request") def test_get_rate_limit_invalid_retries_above_max(self, mock_request): options = RestClientOptions(telemetry=False, retries=11) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertEqual(rc._metrics["retries"], rc.MAX_REQUEST_RETRIES()) @mock.patch("requests.request") def test_get_rate_limit_retries_use_exponential_backoff(self, mock_request): options = RestClientOptions(telemetry=False, retries=10) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 mock_request.return_value.headers = { "x-ratelimit-limit": "3", "x-ratelimit-remaining": "6", "x-ratelimit-reset": "9", } with self.assertRaises(Auth0Error) as context: rc.get("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") self.assertIsInstance(context.exception, RateLimitError) self.assertEqual(context.exception.reset_at, 9) self.assertEqual(rc._metrics["retries"], 10) self.assertEqual(rc._metrics["retries"], len(rc._metrics["backoff"])) baseBackoff = [0] baseBackoffSum = 0 finalBackoff = 0 for i in range(0, 9): backoff = 100 * 2**i baseBackoff.append(backoff) baseBackoffSum += backoff for backoff in rc._metrics["backoff"]: finalBackoff += backoff # Assert that exponential backoff is happening. self.assertGreaterEqual(rc._metrics["backoff"][1], rc._metrics["backoff"][0]) self.assertGreaterEqual(rc._metrics["backoff"][2], rc._metrics["backoff"][1]) self.assertGreaterEqual(rc._metrics["backoff"][3], rc._metrics["backoff"][2]) self.assertGreaterEqual(rc._metrics["backoff"][4], rc._metrics["backoff"][3]) self.assertGreaterEqual(rc._metrics["backoff"][5], rc._metrics["backoff"][4]) self.assertGreaterEqual(rc._metrics["backoff"][6], rc._metrics["backoff"][5]) self.assertGreaterEqual(rc._metrics["backoff"][7], rc._metrics["backoff"][6]) self.assertGreaterEqual(rc._metrics["backoff"][8], rc._metrics["backoff"][7]) self.assertGreaterEqual(rc._metrics["backoff"][9], rc._metrics["backoff"][8]) # Ensure jitter is being applied. self.assertNotEqual(rc._metrics["backoff"][1], baseBackoff[1]) self.assertNotEqual(rc._metrics["backoff"][2], baseBackoff[2]) self.assertNotEqual(rc._metrics["backoff"][3], baseBackoff[3]) self.assertNotEqual(rc._metrics["backoff"][4], baseBackoff[4]) self.assertNotEqual(rc._metrics["backoff"][5], baseBackoff[5]) self.assertNotEqual(rc._metrics["backoff"][6], baseBackoff[6]) self.assertNotEqual(rc._metrics["backoff"][7], baseBackoff[7]) self.assertNotEqual(rc._metrics["backoff"][8], baseBackoff[8]) self.assertNotEqual(rc._metrics["backoff"][9], baseBackoff[9]) # Ensure subsequent delay is never less than the minimum. self.assertGreaterEqual(rc._metrics["backoff"][1], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][2], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][3], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][4], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][5], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][6], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][7], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][8], rc.MIN_REQUEST_RETRY_DELAY()) self.assertGreaterEqual(rc._metrics["backoff"][9], rc.MIN_REQUEST_RETRY_DELAY()) # Ensure delay is never more than the maximum. self.assertLessEqual(rc._metrics["backoff"][0], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][1], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][2], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][3], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][4], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][5], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][6], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][7], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][8], rc.MAX_REQUEST_RETRY_DELAY()) self.assertLessEqual(rc._metrics["backoff"][9], rc.MAX_REQUEST_RETRY_DELAY()) # Ensure total delay sum is never more than 10s. self.assertLessEqual(finalBackoff, 10000) @mock.patch("requests.request") def test_post_rate_limit_retries(self, mock_request): options = RestClientOptions(telemetry=False, retries=10) rc = RestClient(jwt="a-token", options=options) rc._skip_sleep = True mock_request.return_value.text = ( '{"statusCode": 429, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 429 with self.assertRaises(Auth0Error) as context: rc.post("the/url") self.assertEqual(context.exception.status_code, 429) self.assertEqual(len(rc._metrics["backoff"]), 10) @mock.patch("requests.request") def test_post(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '{"a": "b"}' data = {"some": "data"} mock_request.return_value.status_code = 200 response = rc.post("the/url", data=data) mock_request.assert_called_with( "POST", "the/url", json=data, headers=headers, timeout=5.0 ) self.assertEqual(response, {"a": "b"}) @mock.patch("requests.request") def test_post_errors(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 999, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") @mock.patch("requests.request") def test_post_errors_with_no_message_property(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = json.dumps( {"statusCode": 999, "errorCode": "code", "error": "error"} ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "error") @mock.patch("requests.request") def test_post_errors_with_no_message_or_error_property(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = json.dumps( {"statusCode": 999, "errorCode": "code"} ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "") @mock.patch("requests.request") def test_post_errors_with_message_and_error_property(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = json.dumps( { "statusCode": 999, "errorCode": "code", "error": "error", "message": "message", } ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") @mock.patch("requests.request") def test_post_error_with_code_property(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = '{"errorCode": "e0","message": "desc"}' with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "e0") self.assertEqual(context.exception.message, "desc") @mock.patch("requests.request") def test_post_error_with_no_error_code(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = '{"message": "desc"}' with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual(context.exception.message, "desc") @mock.patch("requests.request") def test_post_error_with_text_response(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = "there has been a terrible error" with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual( context.exception.message, "there has been a terrible error" ) @mock.patch("requests.request") def test_post_error_with_no_response_text(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) for error_status in [400, 500, None]: mock_request.return_value.status_code = error_status mock_request.return_value.text = None with self.assertRaises(Auth0Error) as context: rc.post("the-url") self.assertEqual(context.exception.status_code, error_status) self.assertEqual(context.exception.error_code, "a0.sdk.internal.unknown") self.assertEqual(context.exception.message, "") @mock.patch("requests.request") def test_file_post_content_type_is_none(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = {"Authorization": "Bearer a-token"} mock_request.return_value.status_code = 200 mock_request.return_value.text = "Success" data = {"some": "data"} files = [mock.Mock()] rc.file_post("the-url", data=data, files=files) mock_request.assert_called_once_with( "POST", "the-url", data=data, files=files, headers=headers, timeout=5.0 ) @mock.patch("requests.request") def test_put(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 data = {"some": "data"} response = rc.put(url="the-url", data=data) mock_request.assert_called_with( "PUT", "the-url", json=data, headers=headers, timeout=5.0 ) self.assertEqual(response, ["a", "b"]) @mock.patch("requests.request") def test_put_errors(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 999, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.put(url="the/url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") @mock.patch("requests.request") def test_patch(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 data = {"some": "data"} response = rc.patch(url="the-url", data=data) mock_request.assert_called_with( "PATCH", "the-url", json=data, headers=headers, timeout=5.0 ) self.assertEqual(response, ["a", "b"]) @mock.patch("requests.request") def test_patch_errors(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 999, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.patch(url="the/url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") @mock.patch("requests.request") def test_delete(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 response = rc.delete(url="the-url/ID") mock_request.assert_called_with( "DELETE", "the-url/ID", headers=headers, timeout=5.0 ) self.assertEqual(response, ["a", "b"]) @mock.patch("requests.request") def test_delete_with_body_and_params(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) headers = { "Authorization": "Bearer a-token", "Content-Type": "application/json", } mock_request.return_value.text = '["a", "b"]' mock_request.return_value.status_code = 200 data = {"some": "data"} params = {"A": "param", "B": "param"} response = rc.delete(url="the-url/ID", params=params, data=data) mock_request.assert_called_with( "DELETE", "the-url/ID", headers=headers, params=params, json=data, timeout=5.0, ) self.assertEqual(response, ["a", "b"]) @mock.patch("requests.request") def test_delete_errors(self, mock_request): rc = RestClient(jwt="a-token", telemetry=False) mock_request.return_value.text = ( '{"statusCode": 999, "errorCode": "code", "message": "message"}' ) mock_request.return_value.status_code = 999 with self.assertRaises(Auth0Error) as context: rc.delete(url="the-url") self.assertEqual(context.exception.status_code, 999) self.assertEqual(context.exception.error_code, "code") self.assertEqual(context.exception.message, "message") def test_disabled_telemetry(self): rc = RestClient(jwt="a-token", telemetry=False) expected_headers = { "Content-Type": "application/json", "Authorization": "Bearer a-token", } self.assertEqual(rc.base_headers, expected_headers) def test_enabled_telemetry(self): rc = RestClient(jwt="a-token", telemetry=True) user_agent = rc.base_headers["User-Agent"] auth0_client_bytes = base64.b64decode(rc.base_headers["Auth0-Client"]) auth0_client_json = auth0_client_bytes.decode("utf-8") auth0_client = json.loads(auth0_client_json) content_type = rc.base_headers["Content-Type"] from auth0 import __version__ as auth0_version python_version = "{}.{}.{}".format( sys.version_info.major, sys.version_info.minor, sys.version_info.micro ) client_info = { "name": "auth0-python", "version": auth0_version, "env": {"python": python_version}, } self.assertEqual(user_agent, f"Python/{python_version}") self.assertEqual(auth0_client, client_info) self.assertEqual(content_type, "application/json") auth0-auth0-python-0f6dbea/auth0/test/management/test_roles.py000066400000000000000000000145631506260514000245230ustar00rootroot00000000000000import unittest from unittest import mock from ...management.roles import Roles class TestRoles(unittest.TestCase): def test_init_with_optionals(self): t = Roles(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.roles.RestClient") def test_list(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.list() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true", "name_filter": None}, ) u.list(page=1, per_page=50, include_totals=False, name_filter="little-role") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles", args[0]) self.assertEqual( kwargs["params"], { "per_page": 50, "page": 1, "include_totals": "false", "name_filter": "little-role", }, ) @mock.patch("auth0.management.roles.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.create({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/roles", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.roles.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id", args[0]) @mock.patch("auth0.management.roles.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.delete("an-id") mock_instance.delete.assert_called_with("https://domain/api/v2/roles/an-id") @mock.patch("auth0.management.roles.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.update("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/roles/an-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.roles.RestClient") def test_list_users(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.list_users("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id/users", args[0]) self.assertEqual( kwargs["params"], { "per_page": 25, "page": 0, "include_totals": "true", "from": None, "take": None, }, ) u.list_users(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id/users", args[0]) self.assertEqual( kwargs["params"], { "per_page": 50, "page": 1, "include_totals": "false", "from": None, "take": None, }, ) u.list_users(id="an-id", from_param=8675309, take=75) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id/users", args[0]) self.assertEqual( kwargs["params"], { "from": 8675309, "take": 75, "per_page": 25, "page": 0, "include_totals": "true", }, ) @mock.patch("auth0.management.roles.RestClient") def test_add_users(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.add_users("an-id", ["a", "b"]) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/roles/an-id/users", args[0]) self.assertEqual(kwargs["data"], {"users": ["a", "b"]}) @mock.patch("auth0.management.roles.RestClient") def test_list_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.list_permissions("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id/permissions", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} ) u.list_permissions(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/roles/an-id/permissions", args[0]) self.assertEqual( kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} ) @mock.patch("auth0.management.roles.RestClient") def test_remove_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.remove_permissions("an-id", ["a", "b"]) args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/roles/an-id/permissions", args[0]) self.assertEqual(kwargs["data"], {"permissions": ["a", "b"]}) @mock.patch("auth0.management.roles.RestClient") def test_add_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Roles(domain="domain", token="jwttoken") u.add_permissions("an-id", ["a", "b"]) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/roles/an-id/permissions", args[0]) self.assertEqual(kwargs["data"], {"permissions": ["a", "b"]}) auth0-auth0-python-0f6dbea/auth0/test/management/test_rules.py000066400000000000000000000100231506260514000245140ustar00rootroot00000000000000import unittest from unittest import mock from ...management.rules import Rules class TestRules(unittest.TestCase): def test_init_with_optionals(self): t = Rules(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.rules.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = Rules(domain="domain", token="jwttoken") # with default params c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "enabled": "true", "stage": "login_success", "page": None, "per_page": None, "include_totals": "false", }, ) # with stage and fields params c.all(stage="stage", enabled=False, fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules", args[0]) self.assertEqual( kwargs["params"], { "fields": "a,b", "include_fields": "false", "enabled": "false", "stage": "stage", "page": None, "per_page": None, "include_totals": "false", }, ) # with pagination params c.all(page=3, per_page=27, include_totals=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules", args[0]) self.assertEqual( kwargs["params"], { "fields": None, "include_fields": "true", "enabled": "true", "stage": "login_success", "page": 3, "per_page": 27, "include_totals": "true", }, ) @mock.patch("auth0.management.rules.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value c = Rules(domain="domain", token="jwttoken") c.create({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/rules", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.rules.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value c = Rules(domain="domain", token="jwttoken") c.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) c.get("an-id", fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) @mock.patch("auth0.management.rules.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value c = Rules(domain="domain", token="jwttoken") c.delete("an-id") mock_instance.delete.assert_called_with("https://domain/api/v2/rules/an-id") @mock.patch("auth0.management.rules.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value c = Rules(domain="domain", token="jwttoken") c.update("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/rules/an-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) auth0-auth0-python-0f6dbea/auth0/test/management/test_rules_configs.py000066400000000000000000000030121506260514000262240ustar00rootroot00000000000000import unittest from unittest import mock from ...management.rules_configs import RulesConfigs class TestRulesConfigs(unittest.TestCase): def test_init_with_optionals(self): t = RulesConfigs( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.rules_configs.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value c = RulesConfigs(domain="domain", token="jwttoken") c.all() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/rules-configs", args[0]) @mock.patch("auth0.management.rules_configs.RestClient") def test_unset(self, mock_rc): mock_instance = mock_rc.return_value c = RulesConfigs(domain="domain", token="jwttoken") c.unset("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/rules-configs/an-id" ) @mock.patch("auth0.management.rules_configs.RestClient") def test_set(self, mock_rc): mock_instance = mock_rc.return_value g = RulesConfigs(domain="domain", token="jwttoken") g.set("key", "MY_RULES_CONFIG_VALUES") args, kwargs = mock_instance.put.call_args self.assertEqual("https://domain/api/v2/rules-configs/key", args[0]) auth0-auth0-python-0f6dbea/auth0/test/management/test_self_service_profiles.py000066400000000000000000000106601506260514000277450ustar00rootroot00000000000000import unittest from unittest import mock from ...management.self_service_profiles import SelfServiceProfiles class TestSelfServiceProfiles(unittest.TestCase): def test_init_with_optionals(self): t = SelfServiceProfiles( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_all(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.all() mock_instance.get.assert_called_with( "https://domain/api/v2/self-service-profiles", params={"page": 0, "per_page": 25, "include_totals": "true"}, ) s.all(page=1, per_page=50, include_totals=False) mock_instance.get.assert_called_with( "https://domain/api/v2/self-service-profiles", params={"page": 1, "per_page": 50, "include_totals": "false"}, ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.create({"name": "test"}) mock_instance.post.assert_called_with( "https://domain/api/v2/self-service-profiles", data={"name": "test"} ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.get("an-id") mock_instance.get.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id" ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.delete("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id" ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.update("an-id", {"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_get_custom_text(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.get_custom_text("an-id", "en", "page") mock_instance.get.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id/custom-text/en/page" ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_update_custom_text(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.update_custom_text("an-id", "en", "page", {"a": "b", "c": "d"}) mock_instance.put.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id/custom-text/en/page", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_create_sso_ticket(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.create_sso_ticket("an-id", {"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id/sso-ticket", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.self_service_profiles.RestClient") def test_revoke_sso_ticket(self, mock_rc): mock_instance = mock_rc.return_value s = SelfServiceProfiles(domain="domain", token="jwttoken") s.revoke_sso_ticket("an-id", "ticket-id") mock_instance.post.assert_called_with( "https://domain/api/v2/self-service-profiles/an-id/sso-ticket/ticket-id/revoke" )auth0-auth0-python-0f6dbea/auth0/test/management/test_stats.py000066400000000000000000000025241506260514000245270ustar00rootroot00000000000000import unittest from unittest import mock from ...management.stats import Stats class TestStats(unittest.TestCase): def test_init_with_optionals(self): t = Stats(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.stats.RestClient") def test_active_users(self, mock_rc): mock_instance = mock_rc.return_value s = Stats(domain="domain", token="jwttoken") s.active_users() mock_instance.get.assert_called_with( "https://domain/api/v2/stats/active-users", ) @mock.patch("auth0.management.stats.RestClient") def test_daily_stats(self, mock_rc): mock_instance = mock_rc.return_value s = Stats(domain="domain", token="jwttoken") s.daily_stats() mock_instance.get.assert_called_with( "https://domain/api/v2/stats/daily", params={"from": None, "to": None}, ) s.daily_stats(from_date="12341212", to_date="56785656") mock_instance.get.assert_called_with( "https://domain/api/v2/stats/daily", params={"from": "12341212", "to": "56785656"}, ) auth0-auth0-python-0f6dbea/auth0/test/management/test_tenants.py000066400000000000000000000035131506260514000250440ustar00rootroot00000000000000import unittest from unittest import mock from ...management.tenants import Tenants class TestTenants(unittest.TestCase): def test_init_with_optionals(self): t = Tenants(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.tenants.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.get.return_value = {} t = Tenants(domain="domain", token="jwttoken") t.get() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/tenants/settings", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) t.get(fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/tenants/settings", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) t.get(fields=["a", "b"], include_fields=True) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/tenants/settings", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "true"}) @mock.patch("auth0.management.tenants.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value mock_instance.patch.return_value = {} t = Tenants(domain="domain", token="jwttoken") t.update({"a": "b", "c": "d"}) mock_instance.patch.assert_called_with( "https://domain/api/v2/tenants/settings", data={"a": "b", "c": "d"} ) auth0-auth0-python-0f6dbea/auth0/test/management/test_tickets.py000066400000000000000000000023241506260514000250350ustar00rootroot00000000000000import unittest from unittest import mock from ...management.tickets import Tickets class TestTickets(unittest.TestCase): def test_init_with_optionals(self): t = Tickets(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.tickets.RestClient") def test_email(self, mock_rc): mock_instance = mock_rc.return_value t = Tickets(domain="domain", token="jwttoken") t.create_email_verification({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/tickets/email-verification", data={"a": "b", "c": "d"}, ) @mock.patch("auth0.management.tickets.RestClient") def test_pswd(self, mock_rc): mock_instance = mock_rc.return_value t = Tickets(domain="domain", token="jwttoken") t.create_pswd_change({"a": "b", "c": "d"}) mock_instance.post.assert_called_with( "https://domain/api/v2/tickets/password-change", data={"a": "b", "c": "d"} ) auth0-auth0-python-0f6dbea/auth0/test/management/test_user_blocks.py000066400000000000000000000040401506260514000256770ustar00rootroot00000000000000import unittest from unittest import mock from ...management.user_blocks import UserBlocks class TestUserBlocks(unittest.TestCase): def test_init_with_optionals(self): t = UserBlocks( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.user_blocks.RestClient") def test_get_by_identifier(self, mock_rc): mock_instance = mock_rc.return_value u = UserBlocks(domain="domain", token="jwttoken") u.get_by_identifier("some_identifier") mock_instance.get.assert_called_with( "https://domain/api/v2/user-blocks", params={"identifier": "some_identifier"}, ) @mock.patch("auth0.management.user_blocks.RestClient") def test_unblock_by_identifier(self, mock_rc): mock_instance = mock_rc.return_value u = UserBlocks(domain="domain", token="jwttoken") u.unblock_by_identifier("test@test.com") mock_instance.delete.assert_called_with( "https://domain/api/v2/user-blocks", params={"identifier": "test@test.com"} ) @mock.patch("auth0.management.user_blocks.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value u = UserBlocks(domain="domain", token="jwttoken") u.get("auth0|584ad3c228be27504a2c80d5") mock_instance.get.assert_called_with( "https://domain/api/v2/user-blocks/auth0|584ad3c228be27504a2c80d5" ) @mock.patch("auth0.management.user_blocks.RestClient") def test_unblock(self, mock_rc): mock_instance = mock_rc.return_value u = UserBlocks(domain="domain", token="jwttoken") u.unblock("auth0|584ad3c228be27504a2c80d5") mock_instance.delete.assert_called_with( "https://domain/api/v2/user-blocks/auth0|584ad3c228be27504a2c80d5" ) auth0-auth0-python-0f6dbea/auth0/test/management/test_users.py000066400000000000000000000366041506260514000245400ustar00rootroot00000000000000import unittest from unittest import mock from ...management.users import Users class TestUsers(unittest.TestCase): def test_init_with_optionals(self): t = Users(domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.users.RestClient") def test_list(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.list() args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users", args[0]) self.assertEqual( kwargs["params"], { "per_page": 25, "page": 0, "include_totals": "true", "sort": None, "connection": None, "fields": None, "include_fields": "true", "q": None, "search_engine": None, }, ) u.list( page=1, per_page=50, sort="s", connection="con", q="q", search_engine="se", include_totals=False, fields=["a", "b"], include_fields=False, ) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users", args[0]) self.assertEqual( kwargs["params"], { "per_page": 50, "page": 1, "include_totals": "false", "sort": "s", "connection": "con", "fields": "a,b", "include_fields": "false", "q": "q", "search_engine": "se", }, ) @mock.patch("auth0.management.users.RestClient") def test_create(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.create({"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/users", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.users.RestClient") def test_get(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.get("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": None, "include_fields": "true"}) u.get("an-id", fields=["a", "b"], include_fields=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id", args[0]) self.assertEqual(kwargs["params"], {"fields": "a,b", "include_fields": "false"}) @mock.patch("auth0.management.users.RestClient") def test_delete(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete("an-id") mock_instance.delete.assert_called_with("https://domain/api/v2/users/an-id") @mock.patch("auth0.management.users.RestClient") def test_update(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.update("an-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.patch.call_args self.assertEqual("https://domain/api/v2/users/an-id", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.users.RestClient") def test_list_organizations(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.list_organizations("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/organizations", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} ) u.list_organizations(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/organizations", args[0]) self.assertEqual( kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} ) @mock.patch("auth0.management.users.RestClient") def test_list_roles(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.list_roles("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/roles", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} ) u.list_roles(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/roles", args[0]) self.assertEqual( kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} ) @mock.patch("auth0.management.users.RestClient") def test_remove_roles(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.remove_roles("an-id", ["a", "b"]) args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/users/an-id/roles", args[0]) self.assertEqual(kwargs["data"], {"roles": ["a", "b"]}) @mock.patch("auth0.management.users.RestClient") def test_add_roles(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.add_roles("an-id", ["a", "b"]) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/users/an-id/roles", args[0]) self.assertEqual(kwargs["data"], {"roles": ["a", "b"]}) @mock.patch("auth0.management.users.RestClient") def test_list_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.list_permissions("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/permissions", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} ) u.list_permissions(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/permissions", args[0]) self.assertEqual( kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} ) @mock.patch("auth0.management.users.RestClient") def test_remove_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.remove_permissions("an-id", ["a", "b"]) args, kwargs = mock_instance.delete.call_args self.assertEqual("https://domain/api/v2/users/an-id/permissions", args[0]) self.assertEqual(kwargs["data"], {"permissions": ["a", "b"]}) @mock.patch("auth0.management.users.RestClient") def test_add_permissions(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.add_permissions("an-id", ["a", "b"]) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/users/an-id/permissions", args[0]) self.assertEqual(kwargs["data"], {"permissions": ["a", "b"]}) @mock.patch("auth0.management.users.RestClient") def test_delete_multifactor(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete_multifactor("an-id", "provider") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/an-id/multifactor/provider" ) @mock.patch("auth0.management.users.RestClient") def test_delete_authenticators(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete_authenticators("an-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/an-id/authenticators" ) @mock.patch("auth0.management.users.RestClient") def test_unlink_user_account(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.unlink_user_account("an-id", "provider", "user-id") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/an-id/identities/provider/user-id" ) @mock.patch("auth0.management.users.RestClient") def test_link_user_account(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.link_user_account("user-id", {"a": "b", "c": "d"}) args, kwargs = mock_instance.post.call_args self.assertEqual("https://domain/api/v2/users/user-id/identities", args[0]) self.assertEqual(kwargs["data"], {"a": "b", "c": "d"}) @mock.patch("auth0.management.users.RestClient") def test_regenerate_recovery_code(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.regenerate_recovery_code("user-id") mock_instance.post.assert_called_with( "https://domain/api/v2/users/user-id/recovery-code-regeneration" ) @mock.patch("auth0.management.users.RestClient") def test_get_guardian_enrollments(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.get_guardian_enrollments("user-id") mock_instance.get.assert_called_with( "https://domain/api/v2/users/user-id/enrollments" ) @mock.patch("auth0.management.users.RestClient") def test_get_log_events(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.get_log_events("used_id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/used_id/logs", args[0]) self.assertEqual(kwargs["params"]["page"], 0) self.assertEqual(kwargs["params"]["per_page"], 50) self.assertIsNone(kwargs["params"]["sort"]) self.assertEqual(kwargs["params"]["include_totals"], "false") @mock.patch("auth0.management.users.RestClient") def test_invalidate_remembered_browsers(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.invalidate_remembered_browsers("user-id") args, kwargs = mock_instance.post.call_args self.assertEqual( "https://domain/api/v2/users/user-id/multifactor/actions/invalidate-remember-browser", args[0], ) @mock.patch("auth0.management.users.RestClient") def test_get_authentication_methods(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.get_authentication_methods("user_id") mock_instance.get.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods" ) @mock.patch("auth0.management.users.RestClient") def test_get_authentication_method_by_id(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.get_authentication_method_by_id("user_id", "authentication_method_id") mock_instance.get.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods/authentication_method_id" ) @mock.patch("auth0.management.users.RestClient") def test_create_authentication_method(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.create_authentication_method("user_id", {}) mock_instance.post.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods", data={} ) @mock.patch("auth0.management.users.RestClient") def test_update_authentication_methods(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.update_authentication_methods("user_id", {}) mock_instance.put.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods", data={} ) @mock.patch("auth0.management.users.RestClient") def test_update_authentication_method_by_id(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.update_authentication_method_by_id("user_id", "authentication_method_id", {}) mock_instance.patch.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods/authentication_method_id", data={}, ) @mock.patch("auth0.management.users.RestClient") def test_delete_authentication_methods(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete_authentication_methods("user_id") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods" ) @mock.patch("auth0.management.users.RestClient") def test_delete_authentication_method_by_id(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete_authentication_method_by_id("user_id", "authentication_method_id") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods/authentication_method_id" ) @mock.patch("auth0.management.users.RestClient") def test_list_tokensets(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.list_tokensets("an-id") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/federated-connections-tokensets", args[0]) self.assertEqual( kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} ) u.list_tokensets(id="an-id", page=1, per_page=50, include_totals=False) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users/an-id/federated-connections-tokensets", args[0]) self.assertEqual( kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} ) @mock.patch("auth0.management.users.RestClient") def test_delete_tokenset_by_id(self, mock_rc): mock_instance = mock_rc.return_value u = Users(domain="domain", token="jwttoken") u.delete_tokenset_by_id("user_id", "tokenset_id") mock_instance.delete.assert_called_with( "https://domain/api/v2/users/user_id/federated-connections-tokensets/tokenset_id" )auth0-auth0-python-0f6dbea/auth0/test/management/test_users_by_email.py000066400000000000000000000025661506260514000264010ustar00rootroot00000000000000import unittest from unittest import mock from ...management.users_by_email import UsersByEmail class TestUsersByEmail(unittest.TestCase): def test_init_with_optionals(self): t = UsersByEmail( domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2) ) self.assertEqual(t.client.options.timeout, (10, 2)) telemetry_header = t.client.base_headers.get("Auth0-Client", None) self.assertEqual(telemetry_header, None) @mock.patch("auth0.management.users_by_email.RestClient") def test_search_users_by_email(self, mock_rc): mock_instance = mock_rc.return_value u = UsersByEmail(domain="domain", token="jwttoken") u.search_users_by_email("A@B.com") args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users-by-email", args[0]) self.assertEqual( kwargs["params"], {"email": "A@B.com", "fields": None, "include_fields": "true"}, ) u.search_users_by_email( email="a@b.com", fields=["a", "b"], include_fields=False ) args, kwargs = mock_instance.get.call_args self.assertEqual("https://domain/api/v2/users-by-email", args[0]) self.assertEqual( kwargs["params"], {"email": "a@b.com", "fields": "a,b", "include_fields": "false"}, ) auth0-auth0-python-0f6dbea/auth0/test_async/000077500000000000000000000000001506260514000210365ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test_async/__init__.py000066400000000000000000000000001506260514000231350ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/auth0/test_async/conftest.py000066400000000000000000000002061506260514000232330ustar00rootroot00000000000000import pytest import random @pytest.fixture(autouse=True) def set_random_seed(): random.seed(42) print("Random seeded to 42")auth0-auth0-python-0f6dbea/auth0/test_async/test_async_auth0.py000066400000000000000000000043051506260514000246670ustar00rootroot00000000000000import re import unittest from unittest.mock import ANY, MagicMock import pytest from aioresponses import CallbackResult, aioresponses from yarl import URL from auth0.management.async_auth0 import AsyncAuth0 as Auth0 clients = re.compile(r"^https://example\.com/api/v2/clients.*") factors = re.compile(r"^https://example\.com/api/v2/guardian/factors.*") payload = {"foo": "bar"} def get_callback(status=200): mock = MagicMock(return_value=CallbackResult(status=status, payload=payload)) def callback(url, **kwargs): return mock(url, **kwargs) return callback, mock class TestAuth0(unittest.IsolatedAsyncioTestCase): @pytest.mark.asyncio @aioresponses() async def test_get(self, mocked): callback, mock = get_callback() mocked.get(clients, callback=callback) auth0 = Auth0(domain="example.com", token="jwt") self.assertEqual(await auth0.clients.all_async(), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients?include_fields=true"), allow_redirects=True, params={"include_fields": "true"}, headers=ANY, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_shared_session(self, mocked): callback, mock = get_callback() callback2, mock2 = get_callback() mocked.get(clients, callback=callback) mocked.put(factors, callback=callback2) async with Auth0(domain="example.com", token="jwt") as auth0: self.assertEqual(await auth0.clients.all_async(), payload) self.assertEqual( await auth0.guardian.update_factor_async("factor-1", {"factor": 1}), payload, ) mock.assert_called_with( URL("https://example.com/api/v2/clients?include_fields=true"), allow_redirects=True, params={"include_fields": "true"}, headers=ANY, timeout=ANY, ) mock2.assert_called_with( URL("https://example.com/api/v2/guardian/factors/factor-1"), allow_redirects=True, json={"factor": 1}, headers=ANY, timeout=ANY, ) auth0-auth0-python-0f6dbea/auth0/test_async/test_async_token_verifier.py000066400000000000000000000233761506260514000266720ustar00rootroot00000000000000import time import unittest from unittest.mock import ANY import jwt import pytest from aioresponses import aioresponses from cryptography.hazmat.primitives import serialization from yarl import URL from .. import TokenValidationError from ..authentication.async_token_verifier import ( AsyncAsymmetricSignatureVerifier, AsyncJwksFetcher, AsyncTokenVerifier, ) from ..test.authentication.test_token_verifier import ( JWKS_RESPONSE_MULTIPLE_KEYS, JWKS_RESPONSE_SINGLE_KEY, RSA_PUB_KEY_1_JWK, RSA_PUB_KEY_1_PEM, RSA_PUB_KEY_2_PEM, ) from .test_asyncify import get_callback JWKS_URI = "https://example.auth0.com/.well-known/jwks.json" PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDfytWVSk/4Z6rNu8UZ7C4tnU9x0vj5FCaj4awKZlxVgOR1Kcen QqDOxJdrXXanTBJbZwh8pk+HpWvqDVgVmKhnt+OkgF//hIXZoJMhDOFVzX504kiZ cu3bu7kFs+PUfKw5s59tmETFPseA/fIrad9YXHisMkNmPWhuKYJ3WfZAaQIDAQAB AoGADPSfHL9qlcTanIJsTK3hln5u5PYDt9e0zPP5k7iNS93kW+wJROOUj6PN6EdG 4TSEM4ppcV3naMDo2GnhWY624P6LUB+CbDFzjQKq805vrxJuFnq50blscwVK/ffP kODBm/gwk+FaliRpQTDAAPWkKbkRfkmPx4JMEmTDBQ45diECQQDxw3qp2+wa5WP5 9w7AYrDPq4Fd6gIFcmxracROUcdhhMmVHKA9DzTWY46cSoWZoChYhQhhyj8dlP8q El8aevN9AkEA7PhxcNyff8aehqEQ/Z38bm3P+GgB9EkRinjesba2CqhEI5okzvb7 OIYdszgQUBqGKlST0a7s9KuTpd7moyy8XQJAY8hjk0HCxCMTTXMLspnJEh1eKo3P wcHFP9wKeqzEFtrAfHuxIyJok2fJz3XuiEaTAF3/5KSdwi7h1dJ5UCuY3QJAM9rF 0CGnEWngJKu4MRdSNsP232+7Bb67hOagLJlDyp85keTYKyXmoV7PvvkEsNKtCzRI yHiTx5KIE6LsK0bNzQJBAMV+1KyI8ua1XmqLDaOexvBPM86HnuP+8u5CthgrXyGm nh9gurwbs/lBRYV/d4XBLj+dzHb2zC0Jo7u96wrOObw= -----END RSA PRIVATE KEY-----""" PUBLIC_KEY = { "kty": "RSA", "e": "AQAB", "kid": "kid-1", "n": "38rVlUpP-GeqzbvFGewuLZ1PcdL4-RQmo-GsCmZcVYDkdSnHp0KgzsSXa112p0wSW2cIfKZPh6Vr6g1YFZioZ7fjpIBf_4SF2aCTIQzhVc1-dOJImXLt27u5BbPj1HysObOfbZhExT7HgP3yK2nfWFx4rDJDZj1obimCd1n2QGk", } def get_pem_bytes(rsa_public_key): return rsa_public_key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo ) class TestAsyncAsymmetricSignatureVerifier(unittest.IsolatedAsyncioTestCase): @pytest.mark.asyncio @aioresponses() async def test_async_asymmetric_verifier_fetches_key(self, mocked): callback, mock = get_callback(200, JWKS_RESPONSE_SINGLE_KEY) mocked.get(JWKS_URI, callback=callback) verifier = AsyncAsymmetricSignatureVerifier(JWKS_URI) key = await verifier._fetch_key("test-key-1") self.assertEqual(get_pem_bytes(key), RSA_PUB_KEY_1_PEM) class TestAsyncJwksFetcher(unittest.IsolatedAsyncioTestCase): @pytest.mark.asyncio @aioresponses() @unittest.mock.patch( "auth0.authentication.token_verifier.time.time", return_value=0 ) async def test_async_get_jwks_json_twice_on_cache_expired( self, mocked, mocked_time ): fetcher = AsyncJwksFetcher(JWKS_URI, cache_ttl=100) callback, mock = get_callback(200, JWKS_RESPONSE_SINGLE_KEY) mocked.get(JWKS_URI, callback=callback) mocked.get(JWKS_URI, callback=callback) key_1 = await fetcher.get_key("test-key-1") expected_key_1_pem = get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual(mock.call_count, 1) mocked_time.return_value = 200 # 2 seconds has passed, cache should be expired key_1 = await fetcher.get_key("test-key-1") expected_key_1_pem = get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual(mock.call_count, 2) @pytest.mark.asyncio @aioresponses() async def test_async_get_jwks_json_once_on_cache_hit(self, mocked): fetcher = AsyncJwksFetcher(JWKS_URI, cache_ttl=1) callback, mock = get_callback(200, JWKS_RESPONSE_MULTIPLE_KEYS) mocked.get(JWKS_URI, callback=callback) mocked.get(JWKS_URI, callback=callback) key_1 = await fetcher.get_key("test-key-1") key_2 = await fetcher.get_key("test-key-2") expected_key_1_pem = get_pem_bytes(key_1) expected_key_2_pem = get_pem_bytes(key_2) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) self.assertEqual(expected_key_2_pem, RSA_PUB_KEY_2_PEM) mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual(mock.call_count, 1) @pytest.mark.asyncio @aioresponses() async def test_async_fetches_jwks_json_forced_on_cache_miss(self, mocked): fetcher = AsyncJwksFetcher(JWKS_URI, cache_ttl=1) callback, mock = get_callback(200, {"keys": [RSA_PUB_KEY_1_JWK]}) mocked.get(JWKS_URI, callback=callback) # Triggers the first call key_1 = await fetcher.get_key("test-key-1") expected_key_1_pem = get_pem_bytes(key_1) self.assertEqual(expected_key_1_pem, RSA_PUB_KEY_1_PEM) mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual(mock.call_count, 1) callback, mock = get_callback(200, JWKS_RESPONSE_MULTIPLE_KEYS) mocked.get(JWKS_URI, callback=callback) # Triggers the second call key_2 = await fetcher.get_key("test-key-2") expected_key_2_pem = get_pem_bytes(key_2) self.assertEqual(expected_key_2_pem, RSA_PUB_KEY_2_PEM) mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual(mock.call_count, 1) @pytest.mark.asyncio @aioresponses() async def test_async_fetches_jwks_json_once_on_cache_miss(self, mocked): fetcher = AsyncJwksFetcher(JWKS_URI, cache_ttl=1) callback, mock = get_callback(200, JWKS_RESPONSE_SINGLE_KEY) mocked.get(JWKS_URI, callback=callback) with self.assertRaises(Exception) as err: await fetcher.get_key("missing-key") mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual( str(err.exception), 'RSA Public Key with ID "missing-key" was not found.' ) self.assertEqual(mock.call_count, 1) @pytest.mark.asyncio @aioresponses() async def test_async_fails_to_fetch_jwks_json_after_retrying_twice(self, mocked): fetcher = AsyncJwksFetcher(JWKS_URI, cache_ttl=1) callback, mock = get_callback(500, {}) mocked.get(JWKS_URI, callback=callback) mocked.get(JWKS_URI, callback=callback) with self.assertRaises(Exception) as err: await fetcher.get_key("id1") mock.assert_called_with( URL("https://example.auth0.com/.well-known/jwks.json"), allow_redirects=True, params=None, headers=ANY, timeout=ANY, ) self.assertEqual( str(err.exception), 'RSA Public Key with ID "id1" was not found.' ) self.assertEqual(mock.call_count, 2) class TestAsyncTokenVerifier(unittest.IsolatedAsyncioTestCase): @pytest.mark.asyncio @aioresponses() async def test_RS256_token_signature_passes(self, mocked): callback, mock = get_callback(200, {"keys": [PUBLIC_KEY]}) mocked.get(JWKS_URI, callback=callback) issuer = "https://tokens-test.auth0.com/" audience = "tokens-test-123" token = jwt.encode( { "iss": issuer, "sub": "auth0|123456789", "aud": audience, "exp": int(time.time()) + 86400, "iat": int(time.time()), }, PRIVATE_KEY, algorithm="RS256", headers={"kid": "kid-1"}, ) tv = AsyncTokenVerifier( signature_verifier=AsyncAsymmetricSignatureVerifier(JWKS_URI), issuer=issuer, audience=audience, ) payload = await tv.verify(token) self.assertEqual(payload["sub"], "auth0|123456789") @pytest.mark.asyncio @aioresponses() async def test_RS256_token_signature_fails(self, mocked): callback, mock = get_callback( 200, {"keys": [RSA_PUB_KEY_1_JWK]} ) # different pub key mocked.get(JWKS_URI, callback=callback) issuer = "https://tokens-test.auth0.com/" audience = "tokens-test-123" token = jwt.encode( { "iss": issuer, "sub": "auth0|123456789", "aud": audience, "exp": int(time.time()) + 86400, "iat": int(time.time()), }, PRIVATE_KEY, algorithm="RS256", headers={"kid": "test-key-1"}, ) tv = AsyncTokenVerifier( signature_verifier=AsyncAsymmetricSignatureVerifier(JWKS_URI), issuer=issuer, audience=audience, ) with self.assertRaises(TokenValidationError) as err: await tv.verify(token) self.assertEqual(str(err.exception), "Invalid token signature.") auth0-auth0-python-0f6dbea/auth0/test_async/test_asyncify.py000066400000000000000000000216741506260514000243060ustar00rootroot00000000000000import base64 import json import platform import re import sys import unittest from tempfile import TemporaryFile from unittest.mock import ANY, MagicMock import aiohttp import pytest from aioresponses import CallbackResult, aioresponses from yarl import URL from auth0.asyncify import asyncify from auth0.authentication import GetToken, Users from auth0.management import Clients, Guardian, Jobs clients = re.compile(r"^https://example\.com/api/v2/clients.*") token = re.compile(r"^https://example\.com/oauth/token.*") user_info = re.compile(r"^https://example\.com/userinfo.*") factors = re.compile(r"^https://example\.com/api/v2/guardian/factors.*") users_imports = re.compile(r"^https://example\.com/api/v2/jobs/users-imports.*") payload = {"foo": "bar"} telemetry = base64.b64encode( json.dumps( { "name": "auth0-python", "version": sys.modules["auth0"].__version__, "env": { "python": platform.python_version(), }, } ).encode("utf-8") ).decode() headers = { "User-Agent": f"Python/{platform.python_version()}", "Authorization": "Bearer jwt", "Content-Type": "application/json", "Auth0-Client": telemetry, } def get_callback(status=200, response=None): mock = MagicMock( return_value=CallbackResult(status=status, payload=response or payload) ) def callback(url, **kwargs): return mock(url, **kwargs) return callback, mock class TestAsyncify(unittest.IsolatedAsyncioTestCase): @pytest.mark.asyncio @aioresponses() async def test_get(self, mocked): callback, mock = get_callback() mocked.get(clients, callback=callback) c = asyncify(Clients)(domain="example.com", token="jwt") self.assertEqual(await c.all_async(), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients?include_fields=true"), allow_redirects=True, params={"include_fields": "true"}, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_post(self, mocked): callback, mock = get_callback() mocked.post(clients, callback=callback) c = asyncify(Clients)(domain="example.com", token="jwt") data = {"client": 1} self.assertEqual(await c.create_async(data), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients"), allow_redirects=True, json=data, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_post_auth(self, mocked): callback, mock = get_callback() mocked.post(token, callback=callback) c = asyncify(GetToken)("example.com", "cid", client_secret="clsec") self.assertEqual( await c.login_async(username="usrnm", password="pswd"), payload ) mock.assert_called_with( URL("https://example.com/oauth/token"), allow_redirects=True, json={ "client_id": "cid", "username": "usrnm", "password": "pswd", "realm": None, "scope": None, "audience": None, "grant_type": "http://auth0.com/oauth/grant-type/password-realm", "client_secret": "clsec", }, headers={i: headers[i] for i in headers if i != "Authorization"}, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_user_info(self, mocked): callback, mock = get_callback() mocked.get(user_info, callback=callback) c = asyncify(Users)(domain="example.com") self.assertEqual( await c.userinfo_async(access_token="access-token-example"), payload ) mock.assert_called_with( URL("https://example.com/userinfo"), headers={**headers, "Authorization": "Bearer access-token-example"}, timeout=ANY, allow_redirects=True, params=None, ) @pytest.mark.asyncio @aioresponses() async def test_file_post(self, mocked): callback, mock = get_callback() mocked.post(users_imports, callback=callback) j = asyncify(Jobs)(domain="example.com", token="jwt") users = TemporaryFile() self.assertEqual(await j.import_users_async("connection-1", users), payload) file_port_headers = headers.copy() file_port_headers.pop("Content-Type") mock.assert_called_with( URL("https://example.com/api/v2/jobs/users-imports"), allow_redirects=True, data={ "connection_id": "connection-1", "upsert": "false", "send_completion_email": "true", "external_id": None, "users": users, }, headers=file_port_headers, timeout=ANY, ) users.close() @pytest.mark.asyncio @aioresponses() async def test_patch(self, mocked): callback, mock = get_callback() mocked.patch(clients, callback=callback) c = asyncify(Clients)(domain="example.com", token="jwt") data = {"client": 1} self.assertEqual(await c.update_async("client-1", data), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients/client-1"), allow_redirects=True, json=data, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_put(self, mocked): callback, mock = get_callback() mocked.put(factors, callback=callback) g = asyncify(Guardian)(domain="example.com", token="jwt") data = {"factor": 1} self.assertEqual(await g.update_factor_async("factor-1", data), payload) mock.assert_called_with( URL("https://example.com/api/v2/guardian/factors/factor-1"), allow_redirects=True, json=data, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_delete(self, mocked): callback, mock = get_callback() mocked.delete(clients, callback=callback) c = asyncify(Clients)(domain="example.com", token="jwt") self.assertEqual(await c.delete_async("client-1"), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients/client-1"), allow_redirects=True, params={}, json=None, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_shared_session(self, mocked): callback, mock = get_callback() mocked.get(clients, callback=callback) async with asyncify(Clients)(domain="example.com", token="jwt") as c: self.assertEqual(await c.all_async(), payload) mock.assert_called_with( URL("https://example.com/api/v2/clients?include_fields=true"), allow_redirects=True, params={"include_fields": "true"}, headers=headers, timeout=ANY, ) @pytest.mark.asyncio @aioresponses() async def test_rate_limit(self, mocked): callback, mock = get_callback(status=429) mocked.get(clients, callback=callback) mocked.get(clients, callback=callback) mocked.get(clients, callback=callback) mocked.get(clients, payload=payload) c = asyncify(Clients)(domain="example.com", token="jwt") rest_client = c._async_client.client rest_client._skip_sleep = True self.assertEqual(await c.all_async(), payload) self.assertEqual(3, mock.call_count) (a, b, c) = rest_client._metrics["backoff"] self.assertTrue(100 <= a < b < c <= 1000) @pytest.mark.asyncio @aioresponses() async def test_rate_limit_post(self, mocked): callback, mock = get_callback(status=429) mocked.post(clients, callback=callback) mocked.post(clients, callback=callback) mocked.post(clients, callback=callback) mocked.post(clients, payload=payload) c = asyncify(Clients)(domain="example.com", token="jwt") rest_client = c._async_client.client rest_client._skip_sleep = True self.assertEqual(await c.create_async({}), payload) self.assertEqual(3, mock.call_count) @pytest.mark.asyncio @aioresponses() async def test_timeout(self, mocked): callback, mock = get_callback() mocked.get(clients, callback=callback) c = asyncify(Clients)(domain="example.com", token="jwt", timeout=(8.8, 9.9)) self.assertEqual(await c.all_async(), payload) mock.assert_called_with( ANY, allow_redirects=ANY, params=ANY, headers=ANY, timeout=aiohttp.ClientTimeout(sock_connect=8.8, sock_read=9.9), ) auth0-auth0-python-0f6dbea/auth0/types.py000066400000000000000000000002221506260514000203740ustar00rootroot00000000000000from typing import Any, Dict, List, Tuple, Union TimeoutType = Union[float, Tuple[float, float]] RequestData = Union[Dict[str, Any], List[Any]] auth0-auth0-python-0f6dbea/auth0/utils.py000066400000000000000000000002721506260514000203750ustar00rootroot00000000000000def is_async_available() -> bool: try: import asyncio import aiohttp return True except ImportError: # pragma: no cover pass return False auth0-auth0-python-0f6dbea/dockerfile000066400000000000000000000002111506260514000176650ustar00rootroot00000000000000FROM python:3 WORKDIR /home/app COPY . /home/app COPY ./.pypirc ~/ RUN cp /home/app/.pypirc ~ CMD python setup.py sdist bdist upload auth0-auth0-python-0f6dbea/docs/000077500000000000000000000000001506260514000165715ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/docs/Makefile000066400000000000000000000012211506260514000202250ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= --keep-going -n -a SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) auth0-auth0-python-0f6dbea/docs/make.bat000066400000000000000000000013771506260514000202060ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=../auth0 if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd auth0-auth0-python-0f6dbea/docs/source/000077500000000000000000000000001506260514000200715ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/docs/source/authentication.rst000066400000000000000000000033301506260514000236410ustar00rootroot00000000000000authentication package ========================= authentication.base module ----------------------------- .. automodule:: auth0.authentication.base :members: :undoc-members: :show-inheritance: authentication.database module --------------------------------- .. automodule:: auth0.authentication.database :members: :undoc-members: :show-inheritance: authentication.delegated module ---------------------------------- .. automodule:: auth0.authentication.delegated :members: :undoc-members: :show-inheritance: authentication.enterprise module ----------------------------------- .. automodule:: auth0.authentication.enterprise :members: :undoc-members: :show-inheritance: authentication.get\_token module ----------------------------------- .. automodule:: auth0.authentication.get_token :members: :undoc-members: :show-inheritance: authentication.passwordless module ------------------------------------- .. automodule:: auth0.authentication.passwordless :members: :undoc-members: :show-inheritance: authentication.revoke\_token module -------------------------------------- .. automodule:: auth0.authentication.revoke_token :members: :undoc-members: :show-inheritance: authentication.social module ------------------------------- .. automodule:: auth0.authentication.social :members: :undoc-members: :show-inheritance: authentication.token\_verifier module ---------------------------------------- .. automodule:: auth0.authentication.token_verifier :members: :undoc-members: :show-inheritance: authentication.users module ------------------------------ .. automodule:: auth0.authentication.users :members: :undoc-members: :show-inheritance: auth0-auth0-python-0f6dbea/docs/source/conf.py000066400000000000000000000063531506260514000213770ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import re import sys sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.abspath("../..")) # -- helper function to read a file without importing it def read(*names, **kwargs): with open( os.path.join(os.path.dirname(__file__)[:-7], *names), encoding=kwargs.get("encoding", "utf8"), ) as fp: return fp.read() # -- helper function to get the __version__ from a file def find_version(*file_paths): version_file = read(*file_paths) version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") # -- regenerate autodoc definitions # sphinx-apidoc -o ./source ../auth0/ # -- Project information ----------------------------------------------------- project = "auth0-python" copyright = "2021, Auth0" author = "Auth0" # The full version, including alpha/beta/rc tags release = find_version("..", "auth0", "__init__.py") # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.githubpages", "sphinx_mdinclude", "sphinx_autodoc_typehints", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = [".rst", ".md"] # The master toctree document. master_doc = "index" # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Sphinx somehow can't find this one nitpick_ignore = [ ("py:class", "cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey"), ("py:class", "RSAPublicKey"), ("py:data", "typing.Any"), ("py:data", "typing.ClassVar"), ] auth0-auth0-python-0f6dbea/docs/source/exceptions.rst000066400000000000000000000002411506260514000230010ustar00rootroot00000000000000exceptions module ===================== Module contents --------------- .. automodule:: auth0.exceptions :members: :undoc-members: :show-inheritance: auth0-auth0-python-0f6dbea/docs/source/index.rst000066400000000000000000000004261506260514000217340ustar00rootroot00000000000000Auth0-Python documentation ======================================== .. mdInclude:: ../../README.md .. toctree:: :hidden: :caption: Learn the basics readme_content .. toctree:: :hidden: :caption: API Documentation authentication management exceptions auth0-auth0-python-0f6dbea/docs/source/management.rst000066400000000000000000000114061506260514000227410ustar00rootroot00000000000000management package ===================== management.auth0 module -------------------------- .. automodule:: auth0.management.auth0 :members: :undoc-members: :show-inheritance: management.blacklists module ------------------------------- .. automodule:: auth0.management.blacklists :members: :undoc-members: :show-inheritance: management.branding module ------------------------------- .. automodule:: auth0.management.branding :members: :undoc-members: :show-inheritance: management.client\_grants module ----------------------------------- .. automodule:: auth0.management.client_grants :members: :undoc-members: :show-inheritance: management.clients module ---------------------------- .. automodule:: auth0.management.clients :members: :undoc-members: :show-inheritance: management.connections module -------------------------------- .. automodule:: auth0.management.connections :members: :undoc-members: :show-inheritance: management.custom\_domains module ------------------------------------ .. automodule:: auth0.management.custom_domains :members: :undoc-members: :show-inheritance: management.device\_credentials module ---------------------------------------- .. automodule:: auth0.management.device_credentials :members: :undoc-members: :show-inheritance: management.email\_templates module ------------------------------------- .. automodule:: auth0.management.email_templates :members: :undoc-members: :show-inheritance: management.emails module --------------------------- .. automodule:: auth0.management.emails :members: :undoc-members: :show-inheritance: management.grants module --------------------------- .. automodule:: auth0.management.grants :members: :undoc-members: :show-inheritance: management.guardian module ----------------------------- .. automodule:: auth0.management.guardian :members: :undoc-members: :show-inheritance: management.hooks module -------------------------- .. automodule:: auth0.management.hooks :members: :undoc-members: :show-inheritance: management.jobs module ------------------------- .. automodule:: auth0.management.jobs :members: :undoc-members: :show-inheritance: management.log\_streams module --------------------------------- .. automodule:: auth0.management.log_streams :members: :undoc-members: :show-inheritance: management.logs module ------------------------- .. automodule:: auth0.management.logs :members: :undoc-members: :show-inheritance: management.network\_acls module ----------------------------------------- .. automodule:: auth0.management.network_acls :members: :undoc-members: :show-inheritance: management.organizations module ---------------------------------- .. automodule:: auth0.management.organizations :members: :undoc-members: :show-inheritance: management.prompts module ---------------------------------- .. automodule:: auth0.management.prompts :members: :undoc-members: :show-inheritance: management.resource\_servers module -------------------------------------- .. automodule:: auth0.management.resource_servers :members: :undoc-members: :show-inheritance: management.roles module -------------------------- .. automodule:: auth0.management.roles :members: :undoc-members: :show-inheritance: management.rules\_configs module ----------------------------------- .. automodule:: auth0.management.rules_configs :members: :undoc-members: :show-inheritance: management.rules module -------------------------- .. automodule:: auth0.management.rules :members: :undoc-members: :show-inheritance: management.self\_service\_profiles module ----------------------------------------- .. automodule:: auth0.management.self_service_profiles :members: :undoc-members: :show-inheritance: management.stats module -------------------------- .. automodule:: auth0.management.stats :members: :undoc-members: :show-inheritance: management.tenants module ---------------------------- .. automodule:: auth0.management.tenants :members: :undoc-members: :show-inheritance: management.tickets module ---------------------------- .. automodule:: auth0.management.tickets :members: :undoc-members: :show-inheritance: management.user\_blocks module --------------------------------- .. automodule:: auth0.management.user_blocks :members: :undoc-members: :show-inheritance: management.users\_by\_email module ------------------------------------- .. automodule:: auth0.management.users_by_email :members: :undoc-members: :show-inheritance: management.users module -------------------------- .. automodule:: auth0.management.users :members: :undoc-members: :show-inheritance: auth0-auth0-python-0f6dbea/docs/source/readme_content.rst000066400000000000000000000000371506260514000236120ustar00rootroot00000000000000.. mdinclude:: ../../README.md auth0-auth0-python-0f6dbea/examples/000077500000000000000000000000001506260514000174575ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/examples/flask-api/000077500000000000000000000000001506260514000213265ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/examples/flask-api/README.md000066400000000000000000000002431506260514000226040ustar00rootroot00000000000000# Deprecation Notice These samples have been deprecated. Please see the new Python API samples [here](https://github.com/auth0-samples/auth0-python-api-samples). auth0-auth0-python-0f6dbea/examples/flask-webapp/000077500000000000000000000000001506260514000220335ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/examples/flask-webapp/README.md000066400000000000000000000002371506260514000233140ustar00rootroot00000000000000# Deprecation Notice These samples have been deprecated. Please see the new Python API samples [here](https://github.com/auth0-samples/auth0-python-web-app). auth0-auth0-python-0f6dbea/examples/webapi2/000077500000000000000000000000001506260514000210105ustar00rootroot00000000000000auth0-auth0-python-0f6dbea/examples/webapi2/README.md000066400000000000000000000002431506260514000222660ustar00rootroot00000000000000# Deprecation Notice These samples have been deprecated. Please see the new Python API samples [here](https://github.com/auth0-samples/auth0-python-api-samples). auth0-auth0-python-0f6dbea/mypy.ini000066400000000000000000000005021506260514000173350ustar00rootroot00000000000000[mypy] python_version = 3.7 [mypy-auth0.test.*,auth0.test_async.*] ignore_errors = True [mypy-auth0.management.*] ignore_errors = False disable_error_code=var-annotated [mypy-auth0.rest_async] disable_error_code=override [mypy-auth0.authentication.async_token_verifier] disable_error_code=override, misc, attr-defined auth0-auth0-python-0f6dbea/opslevel.yml000066400000000000000000000000741506260514000202160ustar00rootroot00000000000000--- version: 1 repository: owner: dx_sdks tier: tags: auth0-auth0-python-0f6dbea/poetry.lock000066400000000000000000004414321506260514000200450ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, ] [[package]] name = "aiohttp" version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, ] [package.dependencies] aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aioresponses" version = "0.7.8" description = "Mock out requests made by ClientSession from aiohttp package" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "aioresponses-0.7.8-py2.py3-none-any.whl", hash = "sha256:b73bd4400d978855e55004b23a3a84cb0f018183bcf066a85ad392800b5b9a94"}, {file = "aioresponses-0.7.8.tar.gz", hash = "sha256:b861cdfe5dc58f3b8afac7b0a6973d5d7b2cb608dd0f6253d16b8ee8eaf6df11"}, ] [package.dependencies] aiohttp = ">=3.3.0,<4.0.0" packaging = ">=22.0" [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "argcomplete" version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, ] [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "async-timeout" version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] name = "attrs" version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {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"] 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 = "certifi" version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "exceptiongroup" version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] [package.dependencies] typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] [[package]] name = "frozenlist" version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" groups = ["main", "dev"] 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"}, ] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "mock" version = "5.2.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, ] [package.extras] build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] [[package]] name = "multidict" version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" groups = ["main", "dev"] 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"}, ] [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "packaging" version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pipx" version = "1.7.1" description = "Install and Run Python Applications in Isolated Environments" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pipx-1.7.1-py3-none-any.whl", hash = "sha256:3933c43bb344e649cb28e10d357e0967ce8572f1c19caf90cf39ae95c2a0afaf"}, {file = "pipx-1.7.1.tar.gz", hash = "sha256:762de134e16a462be92645166d225ecef446afaef534917f5f70008d63584360"}, ] [package.dependencies] argcomplete = ">=1.9.4" colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""} packaging = ">=20" platformdirs = ">=2.1" tomli = {version = "*", markers = "python_version < \"3.11\""} userpath = ">=1.6,<1.9 || >1.9" [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" 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"}, ] [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)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "propcache" version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, ] [[package]] name = "pycparser" version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] name = "pyjwt" version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-aiohttp" version = "1.0.5" description = "Pytest plugin for aiohttp support" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, ] [package.dependencies] aiohttp = ">=3.8.1" pytest = ">=6.1.0" pytest-asyncio = ">=0.17.2" [package.extras] testing = ["coverage (==6.2)", "mypy (==0.931)"] [[package]] name = "pytest-asyncio" version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, ] [package.dependencies] pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "responses" version = "0.25.8" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c"}, {file = "responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4"}, ] [package.dependencies] pyyaml = "*" requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "typing-extensions" version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "userpath" version = "1.9.2" description = "Cross-platform tool for adding locations to the user PATH" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d"}, {file = "userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815"}, ] [package.dependencies] click = "*" [[package]] name = "yarl" version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.0" [metadata] lock-version = "2.1" python-versions = ">=3.8" content-hash = "f2b4849cb247bdefbbc9b155d28c5e1bcf9b4ca3c2c92defd30b21206018fa3f" auth0-auth0-python-0f6dbea/publish.sh000066400000000000000000000003501506260514000176410ustar00rootroot00000000000000# Deploy to https://pypi.org/project/auth0-python/ # Requires a ~/.pypirc file in the developer machine with proper credentials #!/usr/bin/env bash docker build -t auth0-python-sdk-publish . docker run -it auth0-python-sdk-publish auth0-auth0-python-0f6dbea/pyproject.toml000066400000000000000000000023671506260514000205650ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry] name = "auth0-python" version = "0.0.0" # This is replaced by dynamic versioning description = "" authors = ["Auth0 "] license = "MIT" repository = "https://github.com/auth0/auth0-python" homepage = "https://auth0.com" readme = "README.md" packages = [{ include = "auth0" }] [tool.poetry-dynamic-versioning] strict = true enable = true vcs = "git" style = "semver" format-jinja = "{% if distance == 0 %}{{ base }}{% else %}{{ base }}{% endif %}" pattern = "default-unprefixed" [tool.poetry-dynamic-versioning.substitution] files = ["*/__init__.py"] folders = [{ path = "auth0" }] [tool.poetry.dependencies] python = ">=3.8" aiohttp = ">=3.10.11" cryptography = ">=43.0.1" # pyjwt has a weak dependency on cryptography pyjwt = ">=2.8.0" requests = ">=2.32.3" urllib3 = ">=2.2.3" # requests has a weak dependency on urllib3 [tool.poetry.group.dev.dependencies] aioresponses = "^0.7.8" mock = "^5.1.0" pipx = "^1.7.1" pytest = "^7.4.0" pytest-aiohttp = "^1.0.4" pytest-asyncio = ">=0.21.1,<0.24.0" pytest-cov = "^4.1.0" responses = ">=0.23.3,<0.26.0" auth0-auth0-python-0f6dbea/requirements.txt000066400000000000000000000057021506260514000211310ustar00rootroot00000000000000aiohttp==3.8.6 ; python_version >= "3.7" and python_version < "4.0" aioresponses==0.7.4 ; python_version >= "3.7" and python_version < "4.0" aiosignal==1.4.0 ; python_version >= "3.7" and python_version < "4.0" argcomplete==3.5.3 ; python_version >= "3.7" and python_version < "4.0" async-timeout==4.0.3 ; python_version >= "3.7" and python_version < "4.0" asynctest==0.13.0 ; python_version >= "3.7" and python_version < "3.8" attrs==23.1.0 ; python_version >= "3.7" and python_version < "4.0" certifi==2025.8.3 ; python_version >= "3.7" and python_version < "4.0" cffi==1.17.1 ; python_version >= "3.7" and python_version < "4.0" charset-normalizer==3.4.2 ; python_version >= "3.7" and python_version < "4.0" click==8.1.8 ; python_version >= "3.7" and python_version < "4.0" colorama==0.4.6 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" coverage[toml]==7.2.7 ; python_version >= "3.7" and python_version < "4.0" cryptography==44.0.1 ; python_version >= "3.7" and python_version < "4.0" exceptiongroup==1.1.3 ; python_version >= "3.7" and python_version < "3.11" frozenlist==1.7.0 ; python_version >= "3.7" and python_version < "4.0" idna==3.10 ; python_version >= "3.7" and python_version < "4.0" importlib-metadata==6.7.0 ; python_version >= "3.7" and python_version < "3.8" iniconfig==2.1.0 ; python_version >= "3.7" and python_version < "4.0" mock==5.2.0 ; python_version >= "3.7" and python_version < "4.0" multidict==6.0.4 ; python_version >= "3.7" and python_version < "4.0" packaging==23.2 ; python_version >= "3.7" and python_version < "4.0" pipx==1.2.0 ; python_version >= "3.7" and python_version < "4.0" pluggy==1.2.0 ; python_version >= "3.7" and python_version < "4.0" pycparser==2.23 ; python_version >= "3.7" and python_version < "4.0" pyjwt==2.9.0 ; python_version >= "3.7" and python_version < "4.0" pyopenssl==25.2.0 ; python_version >= "3.7" and python_version < "4.0" pytest-aiohttp==1.0.5 ; python_version >= "3.7" and python_version < "4.0" pytest-asyncio==0.23.8 ; python_version >= "3.7" and python_version < "4.0" pytest-cov==4.1.0 ; python_version >= "3.7" and python_version < "4.0" pytest==7.4.0 ; python_version >= "3.7" and python_version < "4.0" pyyaml==6.0.2 ; python_version >= "3.7" and python_version < "4.0" requests==2.32.4 ; python_version >= "3.7" and python_version < "4.0" responses==0.23.3 ; python_version >= "3.7" and python_version < "4.0" tomli==2.2.1 ; python_version >= "3.7" and python_full_version <= "3.11.0a6" types-pyyaml==6.0.12.11 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.7.1 ; python_version >= "3.7" and python_version < "3.8" urllib3==2.5.0 ; python_version >= "3.7" and python_version < "4.0" userpath==1.9.2 ; python_version >= "3.7" and python_version < "4.0" yarl==1.20.0 ; python_version >= "3.7" and python_version < "4.0" zipp==3.19.1 ; python_version >= "3.7" and python_version < "3.8" auth0-auth0-python-0f6dbea/setup.py000066400000000000000000000000461506260514000173530ustar00rootroot00000000000000from setuptools import setup setup()