pax_global_header00006660000000000000000000000064146075655300014525gustar00rootroot0000000000000052 comment=096c8d42545d3b68ea21a4f890fb2b2d8979c0bd itsdangerous-2.2.0/000077500000000000000000000000001460756553000142355ustar00rootroot00000000000000itsdangerous-2.2.0/.devcontainer/000077500000000000000000000000001460756553000167745ustar00rootroot00000000000000itsdangerous-2.2.0/.devcontainer/devcontainer.json000066400000000000000000000006711460756553000223540ustar00rootroot00000000000000{ "name": "pallets/itsdangerous", "image": "mcr.microsoft.com/devcontainers/python:3", "customizations": { "vscode": { "settings": { "python.defaultInterpreterPath": "${workspaceFolder}/.venv", "python.terminal.activateEnvInCurrentTerminal": true, "python.terminal.launchArgs": [ "-X", "dev" ] } } }, "onCreateCommand": ".devcontainer/on-create-command.sh" } itsdangerous-2.2.0/.devcontainer/on-create-command.sh000077500000000000000000000002451460756553000226250ustar00rootroot00000000000000#!/bin/bash set -e python3 -m venv --upgrade-deps .venv . .venv/bin/activate pip install -r requirements/dev.txt pip install -e . pre-commit install --install-hooks itsdangerous-2.2.0/.editorconfig000066400000000000000000000003511460756553000167110ustar00rootroot00000000000000root = true [*] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf charset = utf-8 max_line_length = 88 [*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] indent_size = 2 itsdangerous-2.2.0/.github/000077500000000000000000000000001460756553000155755ustar00rootroot00000000000000itsdangerous-2.2.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001460756553000177605ustar00rootroot00000000000000itsdangerous-2.2.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000012031460756553000223640ustar00rootroot00000000000000--- name: Bug report about: Report a bug in ItsDangerous (not other projects which depend on ItsDangerous) --- Environment: - Python version: - ItsDangerous version: itsdangerous-2.2.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005231460756553000217500ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions on Discussions url: https://github.com/pallets/itsdangerous/discussions/ about: Ask questions about your own code on the Discussions tab. - name: Questions on Chat url: https://discord.gg/pallets about: Ask questions about your own code on our Discord chat. itsdangerous-2.2.0/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000006561460756553000234320ustar00rootroot00000000000000--- name: Feature request about: Suggest a new feature for ItsDangerous --- itsdangerous-2.2.0/.github/dependabot.yml000066400000000000000000000010521460756553000204230ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: monthly ignore: # slsa depends on upload/download v3 - dependency-name: actions/upload-artifact versions: '>= 4' - dependency-name: actions/download-artifact versions: '>= 4' groups: github-actions: patterns: - '*' - package-ecosystem: pip directory: /requirements/ schedule: interval: monthly groups: python-requirements: patterns: - '*' itsdangerous-2.2.0/.github/pull_request_template.md000066400000000000000000000014661460756553000225450ustar00rootroot00000000000000 itsdangerous-2.2.0/.github/workflows/000077500000000000000000000000001460756553000176325ustar00rootroot00000000000000itsdangerous-2.2.0/.github/workflows/lock.yaml000066400000000000000000000012251460756553000214460ustar00rootroot00000000000000name: Lock inactive closed issues # Lock closed issues that have not received any further activity for two weeks. # This does not close open issues, only humans may do that. It is easier to # respond to new issues with fresh examples rather than continuing discussions # on old issues. on: schedule: - cron: '0 0 * * *' permissions: issues: write pull-requests: write concurrency: group: lock jobs: lock: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: issue-inactive-days: 14 pr-inactive-days: 14 discussion-inactive-days: 14 itsdangerous-2.2.0/.github/workflows/publish.yaml000066400000000000000000000051541460756553000221710ustar00rootroot00000000000000name: Publish on: push: tags: - '*' jobs: build: runs-on: ubuntu-latest outputs: hash: ${{ steps.hash.outputs.hash }} steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.x' cache: pip cache-dependency-path: requirements*/*.txt - run: pip install -r requirements/build.txt # Use the commit date instead of the current date during the build. - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV - run: python -m build # Generate hashes used for provenance. - name: generate hash id: hash run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: path: ./dist provenance: needs: [build] permissions: actions: read id-token: write contents: write # Can't pin with hash due to how this workflow works. uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0 with: base64-subjects: ${{ needs.build.outputs.hash }} create-release: # Upload the sdist, wheels, and provenance to a GitHub release. They remain # available as build artifacts for a while as well. needs: [provenance] runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - name: create release run: > gh release create --draft --repo ${{ github.repository }} ${{ github.ref_name }} *.intoto.jsonl/* artifact/* env: GH_TOKEN: ${{ github.token }} publish-pypi: needs: [provenance] # Wait for approval before attempting to upload to PyPI. This allows reviewing the # files in the draft release. environment: name: publish url: https://pypi.org/project/itsdangerous/${{ github.ref_name }} runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 with: repository-url: https://test.pypi.org/legacy/ packages-dir: artifact/ - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 with: packages-dir: artifact/ itsdangerous-2.2.0/.github/workflows/tests.yaml000066400000000000000000000033451460756553000216650ustar00rootroot00000000000000name: Tests on: push: branches: - main - '*.x' paths-ignore: - 'docs/**' - '*.md' - '*.rst' pull_request: paths-ignore: - 'docs/**' - '*.md' - '*.rst' jobs: tests: name: ${{ matrix.name || matrix.python }} runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: include: - {python: '3.12'} - {name: Windows, python: '3.12', os: windows-latest} - {name: Mac, python: '3.12', os: macos-latest} - {python: '3.11'} - {python: '3.10'} - {python: '3.9'} - {python: '3.8'} - {name: PyPy, python: 'pypy-3.10', tox: pypy310} steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: ${{ matrix.python }} allow-prereleases: true cache: pip cache-dependency-path: requirements*/*.txt - run: pip install tox - run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} typing: runs-on: ubuntu-latest steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.x' cache: pip cache-dependency-path: requirements*/*.txt - name: cache mypy uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: ./.mypy_cache key: mypy|${{ hashFiles('pyproject.toml') }} - run: pip install tox - run: tox run -e typing itsdangerous-2.2.0/.gitignore000066400000000000000000000001311460756553000162200ustar00rootroot00000000000000.idea/ .vscode/ .venv*/ venv*/ __pycache__/ dist/ .coverage* htmlcov/ .tox/ docs/_build/ itsdangerous-2.2.0/.pre-commit-config.yaml000066400000000000000000000006271460756553000205230ustar00rootroot00000000000000ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.3.7 hooks: - id: ruff - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: check-merge-conflict - id: debug-statements - id: fix-byte-order-marker - id: trailing-whitespace - id: end-of-file-fixer itsdangerous-2.2.0/.readthedocs.yaml000066400000000000000000000003211460756553000174600ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: '3.12' python: install: - requirements: requirements/docs.txt - method: pip path: . sphinx: builder: dirhtml fail_on_warning: true itsdangerous-2.2.0/CHANGES.rst000066400000000000000000000174121460756553000160440ustar00rootroot00000000000000Version 2.2.0 ------------- Released 2024-04-16 - Drop support for Python 3.7. :pr:`372` - Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. :pr:`326` - Use ``flit_core`` instead of ``setuptools`` as build backend. - Deprecate the ``__version__`` attribute. Use feature detection, or ``importlib.metadata.version("itsdangerous")``, instead. :issue:`371` - ``Serializer`` and the return type of ``dumps`` is generic for type checking. By default it is ``Serializer[str]`` and ``dumps`` returns a ``str``. If a different ``serializer`` argument is given, it will try to infer the return type of its ``dumps`` method. :issue:`347` - The default ``hashlib.sha1`` may not be available in FIPS builds. Don't access it at import time so the developer has time to change the default. :issue:`375` Version 2.1.2 ------------- Released 2022-03-24 - Handle date overflow in timed unsign on 32-bit systems. :pr:`299` Version 2.1.1 ------------- Released 2022-03-09 - Handle date overflow in timed unsign. :pr:`296` Version 2.1.0 ------------- Released 2022-02-17 - Drop support for Python 3.6. :pr:`272` - Remove previously deprecated code. :pr:`273` - JWS functionality: Use a dedicated library such as Authlib instead. - ``import itsdangerous.json``: Import ``json`` from the standard library instead. Version 2.0.1 ------------- Released 2021-05-18 - Mark top-level names as exported so type checking understands imports in user projects. :pr:`240` - The ``salt`` argument to ``Serializer`` and ``Signer`` can be ``None`` again. :issue:`237` Version 2.0.0 ------------- Released 2021-05-11 - Drop support for Python 2 and 3.5. - JWS support (``JSONWebSignatureSerializer``, ``TimedJSONWebSignatureSerializer``) is deprecated. Use a dedicated JWS/JWT library such as authlib instead. :issue:`129` - Importing ``itsdangerous.json`` is deprecated. Import Python's ``json`` module instead. :pr:`152` - Simplejson is no longer used if it is installed. To use a different library, pass it as ``Serializer(serializer=...)``. :issue:`146` - ``datetime`` values are timezone-aware with ``timezone.utc``. Code using ``TimestampSigner.unsign(return_timestamp=True)`` or ``BadTimeSignature.date_signed`` may need to change. :issue:`150` - If a signature has an age less than 0, it will raise ``SignatureExpired`` rather than appearing valid. This can happen if the timestamp offset is changed. :issue:`126` - ``BadTimeSignature.date_signed`` is always a ``datetime`` object rather than an ``int`` in some cases. :issue:`124` - Added support for key rotation. A list of keys can be passed as ``secret_key``, oldest to newest. The newest key is used for signing, all keys are tried for unsigning. :pr:`141` - Removed the default SHA-512 fallback signer from ``default_fallback_signers``. :issue:`155` - Add type information for static typing tools. :pr:`186` Version 1.1.0 ------------- Released 2018-10-26 - Change default signing algorithm back to SHA-1. :pr:`113` - Added a default SHA-512 fallback for users who used the yanked 1.0.0 release which defaulted to SHA-512. :pr:`114` - Add support for fallback algorithms during deserialization to support changing the default in the future without breaking existing signatures. :pr:`113` - Changed capitalization of packages back to lowercase as the change in capitalization broke some tooling. :pr:`113` Version 1.0.0 ------------- Released 2018-10-18 YANKED *Note*: This release was yanked from PyPI because it changed the default algorithm to SHA-512. This decision was reverted in 1.1.0 and it remains at SHA1. - Drop support for Python 2.6 and 3.3. - Refactor code from a single module to a package. Any object in the API docs is still importable from the top-level ``itsdangerous`` name, but other imports will need to be changed. A future release will remove many of these compatibility imports. :pr:`107` - Optimize how timestamps are serialized and deserialized. :pr:`13` - ``base64_decode`` raises ``BadData`` when it is passed invalid data. :pr:`27` - Ensure value is bytes when signing to avoid a ``TypeError`` on Python 3. :issue:`29` - Add a ``serializer_kwargs`` argument to ``Serializer``, which is passed to ``dumps`` during ``dump_payload``. :pr:`36` - More compact JSON dumps for unicode strings. :issue:`38` - Use the full timestamp rather than an offset, allowing dates before 2011. :issue:`46` To retain compatibility with signers from previous versions, consider using `this shim `_ when unsigning. - Detect a ``sep`` character that may show up in the signature itself and raise a ``ValueError``. :issue:`62` - Use a consistent signature for keyword arguments for ``Serializer.load_payload`` in subclasses. :issue:`74`, :pr:`75` - Change default intermediate hash from SHA-1 to SHA-512. :pr:`80` - Convert JWS exp header to an int when loading. :pr:`99` Version 0.24 ------------ Released 2014-03-28 - Added a ``BadHeader`` exception that is used for bad headers that replaces the old ``BadPayload`` exception that was reused in those cases. Version 0.23 ------------ Released 2013-08-08 - Fixed a packaging mistake that caused the tests and license files to not be included. Version 0.22 ------------ Released 2013-07-03 - Added support for ``TimedJSONWebSignatureSerializer``. - Made it possible to override the signature verification function to allow implementing asymmetrical algorithms. Version 0.21 ------------ Released 2013-05-26 - Fixed an issue on Python 3 which caused invalid errors to be generated. Version 0.20 ------------ Released 2013-05-23 - Fixed an incorrect call into ``want_bytes`` that broke some uses of ItsDangerous on Python 2.6. Version 0.19 ------------ Released 2013-05-21 - Dropped support for 2.5 and added support for 3.3. Version 0.18 ------------ Released 2013-05-03 - Added support for JSON Web Signatures (JWS). Version 0.17 ------------ Released 2012-08-10 - Fixed a name error when overriding the digest method. Version 0.16 ------------ Released 2012-07-11 - Made it possible to pass unicode values to ``load_payload`` to make it easier to debug certain things. Version 0.15 ------------ Released 2012-07-11 - Made standalone ``load_payload`` more robust by raising one specific error if something goes wrong. - Refactored exceptions to catch more cases individually, added more attributes. - Fixed an issue that caused ``load_payload`` not work in some situations with timestamp based serializers - Added an ``loads_unsafe`` method. Version 0.14 ------------ Released 2012-06-29 - API refactoring to support different key derivations. - Added attributes to exceptions so that you can inspect the data even if the signature check failed. Version 0.13 ------------ Released 2012-06-10 - Small API change that enables customization of the digest module. Version 0.12 ------------ Released 2012-02-22 - Fixed a problem with the local timezone being used for the epoch calculation. This might invalidate some of your signatures if you were not running in UTC timezone. You can revert to the old behavior by monkey patching ``itsdangerous.EPOCH``. Version 0.11 ------------ Released 2011-07-07 - Fixed an uncaught value error. Version 0.10 ------------ Released 2011-06-25 - Refactored interface that the underlying serializers can be swapped by passing in a module instead of having to override the payload loaders and dumpers. This makes the interface more compatible with Django's recent changes. itsdangerous-2.2.0/CONTRIBUTING.rst000066400000000000000000000146531460756553000167070ustar00rootroot00000000000000How to contribute to ItsDangerous ================================= Thank you for considering contributing to ItsDangerous! Support questions ----------------- Please don't use the issue tracker for this. The issue tracker is a tool to address bugs and feature requests in ItsDangerous itself. Use one of the following resources for questions about using ItsDangerous or issues with your own code: - The ``#get-help`` channel on our Discord chat: https://discord.gg/pallets - The mailing list flask@python.org for long term discussion or larger issues. - Ask on `Stack Overflow`_. Search with Google first using: ``site:stackoverflow.com itsdangerous {search term, exception message, etc.}`` .. _Stack Overflow: https://stackoverflow.com/questions/tagged/itsdangerous?tab=Frequent Reporting issues ---------------- Include the following information in your post: - Describe what you expected to happen. - If possible, include a `minimal reproducible example`_ to help us identify the issue. This also helps check that the issue is not with your own code. - Describe what actually happened. Include the full traceback if there was an exception. - List your Python and ItsDangerous versions. If possible, check if this issue is already fixed in the latest releases or the latest code in the repository. .. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example Submitting patches ------------------ If there is not an open issue for what you want to submit, prefer opening one for discussion before working on a PR. You can work on any issue that doesn't have an open PR linked to it or a maintainer assigned to it. These show up in the sidebar. No need to ask if you can work on an issue that interests you. Include the following in your patch: - Use `Black`_ to format your code. This and other tools will run automatically if you install `pre-commit`_ using the instructions below. - Include tests if your patch adds or changes code. Make sure the test fails without your patch. - Update any relevant docs pages and docstrings. Docs pages and docstrings should be wrapped at 72 characters. - Add an entry in ``CHANGES.rst``. Use the same style as other entries. Also include ``.. versionchanged::`` inline changelogs in relevant docstrings. .. _Black: https://black.readthedocs.io .. _pre-commit: https://pre-commit.com First time setup ~~~~~~~~~~~~~~~~ - Download and install the `latest version of git`_. - Configure git with your `username`_ and `email`_. .. code-block:: text $ git config --global user.name 'your name' $ git config --global user.email 'your email' - Make sure you have a `GitHub account`_. - Fork ItsDangerous to your GitHub account by clicking the `Fork`_ button. - `Clone`_ the main repository locally. .. code-block:: text $ git clone https://github.com/pallets/itsdangerous $ cd itsdangerous - Add your fork as a remote to push your work to. Replace ``{username}`` with your username. This names the remote "fork", the default Pallets remote is "origin". .. code-block:: text $ git remote add fork https://github.com/{username}/itsdangerous - Create a virtualenv. .. code-block:: text $ python3 -m venv env $ . env/bin/activate On Windows, activating is different. .. code-block:: text > env\Scripts\activate - Install the development dependencies, then install ItsDangerous in editable mode. .. code-block:: text $ pip install -r requirements/dev.txt && pip install -e . - Install the pre-commit hooks. .. code-block:: text $ pre-commit install .. _latest version of git: https://git-scm.com/downloads .. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git .. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address .. _GitHub account: https://github.com/join .. _Fork: https://github.com/pallets/itsdangerous/fork .. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork Start coding ~~~~~~~~~~~~ - Create a branch to identify the issue you would like to work on. If you're submitting a bug or documentation fix, branch off of the latest ".x" branch. .. code-block:: text $ git fetch origin $ git checkout -b your-branch-name origin/2.0.x If you're submitting a feature addition or change, branch off of the "main" branch. .. code-block:: text $ git fetch origin $ git checkout -b your-branch-name origin/main - Using your favorite editor, make your changes, `committing as you go`_. - Include tests that cover any code changes you make. Make sure the test fails without your patch. Run the tests as described below. - Push your commits to your fork on GitHub and `create a pull request`_. Link to the issue being addressed with ``fixes #123`` in the pull request. .. code-block:: text $ git push --set-upstream fork your-branch-name .. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes .. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request Running the tests ~~~~~~~~~~~~~~~~~ Run the basic test suite with pytest. .. code-block:: text $ pytest This runs the tests for the current environment, which is usually sufficient. CI will run the full suite when you submit your pull request. You can run the full test suite with tox if you don't want to wait. .. code-block:: text $ tox Running test coverage ~~~~~~~~~~~~~~~~~~~~~ Generating a report of lines that do not have test coverage can indicate where to start contributing. Run ``pytest`` using ``coverage`` and generate a report. .. code-block:: text $ pip install coverage $ coverage run -m pytest $ coverage html Open ``htmlcov/index.html`` in your browser to explore the report. Read more about `coverage `__. Building the docs ~~~~~~~~~~~~~~~~~ Build the docs in the ``docs`` directory using Sphinx. .. code-block:: text $ cd docs $ make html Open ``_build/html/index.html`` in your browser to view the docs. Read more about `Sphinx `__. itsdangerous-2.2.0/LICENSE.txt000066400000000000000000000027031460756553000160620ustar00rootroot00000000000000Copyright 2011 Pallets Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. itsdangerous-2.2.0/README.md000066400000000000000000000021261460756553000155150ustar00rootroot00000000000000# ItsDangerous ... so better sign this Various helpers to pass data to untrusted environments and to get it back safe and sound. Data is cryptographically signed to ensure that a token has not been tampered with. It's possible to customize how data is serialized. Data is compressed as needed. A timestamp can be added and verified automatically while loading a token. ## A Simple Example Here's how you could generate a token for transmitting a user's id and name between web requests. ```python from itsdangerous import URLSafeSerializer auth_s = URLSafeSerializer("secret key", "auth") token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) print(token) # eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg data = auth_s.loads(token) print(data["name"]) # itsdangerous ``` ## Donate The Pallets organization develops and supports ItsDangerous and other popular packages. In order to grow the community of contributors and users, and allow the maintainers to devote more time to the projects, [please donate today][]. [please donate today]: https://palletsprojects.com/donate itsdangerous-2.2.0/docs/000077500000000000000000000000001460756553000151655ustar00rootroot00000000000000itsdangerous-2.2.0/docs/Makefile000066400000000000000000000011051460756553000166220ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . 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) itsdangerous-2.2.0/docs/_static/000077500000000000000000000000001460756553000166135ustar00rootroot00000000000000itsdangerous-2.2.0/docs/_static/itsdangerous-logo-sidebar.png000066400000000000000000000147451460756553000244100ustar00rootroot00000000000000PNG  IHDR&zsBIT|dIDATxyeDF`ldR6 (SZjhZbp  f#wJSۼ'p"; !<=&dL;` :b+ z|l < Fx_%HY^@+7bϿ |܎]ӀAC/n7Ptޟ{Oˀ@a`k7Ph<؋@G+9;f@)\9p2p0%pv6 ȉ'@%ÀO1h4^Ȳwr@Hʀ}hYc0iPjxqN@XUۛ ((#v8 ĮEEJ"͆HpdE[TǤqtCM]W$R xb.B_؂8$Z}ђƖƃxWEce1l#9|*+ *amv0Z(a?%S)%`G/`d8OH(>Z% b '[0k@.B]k(=pR#xľ~|KS+M2-aZ+v꒥L۩8gZPIuB3a#_nAR+.LtǑFan?#;6ż}@NUҁ*=5X8qZe##s>nA]&8"V1":cswcM 8h1)2 5U)ZEp$wǒx؞[~(ҁaub!og1:q.!7>xw>ZFLH%!^ V/8[Ļ}Ņ~@b"X?S*p^ʘHq:Hro-wDj^vHvJfQ:!7#V#񦢂m?INox48y mr2Mwx ׆J:06 DFcD{$4b&oW 4F|n6`l,"J$ޯQXl_?Qk6ٞAuɎG d }ᨭE@_lJ:czKo_} }GzA6_P*mǠo 팧*/7 7#ᅅ~A],&"%O1E2S-"o>( I~x7I׻ud|BBCOވ2`'PK_Mx-ޟ0xqu_@pE@џ[{\qvJGrcc 뀢G*QEC7Xxs(Ϻuc{oA,F4j I5S&6G=Bid2[" ~c@ Q>ݶ؊x`}Z [r1)0L2w0| Fĩa{$0Q !^oL;p"vC^"!^R* #bb8C[h~o CT18|?6^[nC+yc WJdX \/k{)7cBvd,^@F€ xp$ٍxkjn{҇2ڮӥrx=(zb# E@Fk=ěyP~ g6F02nxy$Ļy?U((Q mGaR@18Kb;稼GFK4D2}(a:3'bOظܚ"~TCLd}POwO?8`%Q>݁HQlJ%8~ z|hJb: C? Ƕh=b"!t@@5ī~>T+pr-\|LHUNr@)=1R= sV4nDwLm-[+ {bA$p8ᱻKi(>@eQ!aFUs]x#nul^KF+]M܊)Я|ah' /H13sU!8( >=WF&4K{ݒnjhW B??Jܻc1rxkFv@a(ꈖ쳽[_(@脳NƠO'Xe qRU-^m-Ges ^@Ftc7aZsk1g0½eQ AhI^ ^@Ntȳ +0z#*'! '7)P%PG @尖XGUB"ގEKa$[xc ȉޘ2i>]w +`6G'c@yvO kH2AH +1$_ ꔀ{v²Xw<^F]m;t|ʱ֫{w I žuE!qKHފo+6lT,lěq Q/r^ {~msu: >Fc:,MAk/fXg^K܇O3.GP$ī.Y'b}Rpӱl2ՉF?z.Gߠ^&c$IJl"}k@hiO~DBO+CPD8cx5ZheR$; &ѣՏː`-u[_}pVˑ3J' p.fcp 1ka0 O~/ر?6oUapw d$Lލ[n>,>Fm9#IeH8W+aI'$CG d$8L/b'8"E2q&DkCc̿@uC[ۘpkPWp:GbJRy[#! hdprA1Fg"1vXtqwǣu/ @),,h+$Bb42b$=|/z﷘l.~wz0hZpV8p 1xxr/BFO` &7nr HDR'#Ibfim90x@Q&B. 2`"r֘*YJBǣ70"% 02?Bh$+aOEkFͮ h\[6ĄۂA>DYQҸb̓ bũXĄۊ =MIryt &~Ֆa͹P|h]j.DM E닖o FzoSvITsޏďxl%&ӻZX&cJ@@F,2T%@`+6nQW zc> ):Rny{z$XT%s@ Nu0Ut$,q '#tIPg{j[nc6Fg`$ܓ$Lxuz44X@E?7x">Lhb gk_P'oyqyHtS-Bj:Q O߀|bcsE;r8|?*iřq;P |H[k59^S@@T_h3j2XTU e$fSrD;ʲa$tƘXF";Ou݉I&͌_M-DP”n-/8pSuLm@>T-NN8Γ}0nc ,[!f({ш$wػweÕ`5'j@πo_%a,F#l9-uTM{ae`XدkTbP*Ez%gy5D.^/e= ڹ{-{`6h:jkkZ ʿ(9O 08uCl+&yYm@%F0pLRNt>.}S-ͪ/Ps<=uQG-gn Ua .LE' [ Ttb@)~tI1_wPh]do`8sw' IO]ֻ  uͬed:62AALO/kffO~ol ifY.kAD`Xk30p?;AAt&=OlN'$ٶv!fG4 N2[90*=3Y 6K@VW9lVtkoP3AAIxX }_6vkml]V?f>ѝ p;a(  X̶kwX   Jj`{`< f@.1k~l AA pGYQDc@/4pG3۬v ;a wA l_`2QU`07& w۠B} ~YC{M0%rM&GǺO\wkz[f#9CWAA*:6`G`f6.֠afY9IBб3 κ6;e/@ HlF3"3or`I ppmaY%vrF/.U|W ߞh =_}00#zxc`O܎*=`5`S3;Ef=}sߟ^A Gi s# 91>~^>XhfYMQdڛދx =p3AA}镟oR0+o‚yq`U`gZ3+wA f-zRXh'ŀ ?dQllr`g`Z`xt5f6h4f6zyË́G_{]!hNN^_gj<"haOz3ݟlSȿ Atb^8f9-Y}_a;%nkY-f6+p$Du̾B-JU I~#〟Ț+& ||G `u{kmn93ݿgirt~|zKz ۙ X]-gi  Ϲ+e 'rf.cV3YAꎶc~F`g>9 ,?p[fעlxzg>Á*f;:k|\^rlPdqMSE+'"nrc)tπ.瀵gTxl7`Ywc9;61bfrE{(UJNFo4df?"ߖa-HNnVKZ<,/ְcf EuG18T:㑸յnlÀݽh:lcLZDB/P`̬w_ m/~;|7iy#&}Ebk9Moyۢ33W/{krۜs !0=ieXw\uKsn-ah}t,l ~Dsݽ*맙Nk1mro}wK ~_<zςjJ|~`;d^)Q#QD6,NEoτ n8܅2W2anMw餿ޅ鼭:4]e4 SZ秴%L?5u>uthc"7:als,0cSv^4hzea[G9.F?ˠ_e(r̍vs HL/ lTks{2B*Ǜu>3}ÁπʹҹY7 Ŷo o_^5;K^V-e߁ݝr7t,RKZHA=#_YQ'IW3زum2כ[ۙBjo2;8Ӈ+F9s7r |v.rCqdUX~v@p2evvEPY[jT8!}l?e7 =by[$p^>O˃ N*Z Y}}X.qާz BeY^p"~O@c3#1"ꅜ7Do dJ%= X*ep Ա?Eqw8" EQl>Q+~,H~/?-梭ɞU<0A?_Pm7oj锻?!9weĭ&ZEtQ7Je73;̖@2uQմE֭OqJ)vjW{ۻ !̡WƫIڲot`g@ ߠSJ[!0+rF7dPn?&xv]˯zy+HTQPtuusu?ϣYޢZ5 (AB:o=V'z}VtSN/07"4f|}_+ߺ'Ne!ݠAp? OzCsY>s@2/795+X /Sw WzǹKr7wt5~B7lzf6gQ#i=*]6"x)rZ@cZY\ſyR UN$^sVtDCBzf̦ANVCuHlQB;aM 3ukS)npe:X6@H2-.5q=D^W27 9:hpCҿ7~{蚉x;ȴkho_9Ί\])7S!!fQn(|;jvgЭ,3A۪3ںSu_nTȉ8-v LOdxfg 'J|4UVFE Jk%O3+V f$3EfW^4f0,Kټef+✔7LEUIڹ5moǚaTGvg~+v+ BJuH+/̷NB LӔdu*7ɭ\.)aOdǺ'R W$[Ud}dIᆉwz3?T*լtME-UE;̍ -殕6Ywf_>|9?sWH3:/A3{&OȞxsPiZ CJl ")??VH~r (>xݟ*@r̺67%;w3h"YnTE%a"Ӻ/rYӺ4u^ۺJVc" 0)3{1lgff][ch>OS 0+Vz ;E;&ٱf6{61lX笧ױRut$x!Kى̋ЛfG$v3Pm聿AVOfbme*tmBwTz`lCcӟ{'Tl"oM+Xw {za+7mCͪ"=B߲&Gr*qR,Vq.!9iol3zS˧ɾ/YXkO9}#M~$Za*U(ROKyV,Flgb| do''Q~=wo}h@HyA !JHTz`Pf6& ELt#zwF[fWZ#UpFOC]@H]vH_DR%loMۙ!uH?\@pwORS:@wχ~d@>Kaf7ERZA+*"Nܘ^ ;Z`#]wS. +\jh2HCgBVt/`rgQqovJ49n \B cfk{"'jC.:<3wP f` 7 AnBI/=UJ?DzaKPv7Us5 [mjtZ-9+){Gٗ6Y T#CD_aYd{Q-U\"Vj g.՚bV쩐]=@ȺwL5/Cȕ"y. Ѓ=Tfd}cdK?cӸkw"/ ?K~̳iy}ʒy1(OX%2-NGŞopVH&-J{ezt/(F\i&ż'Ծ&R|U/p>F>4u;3;e"mmVRׄx\~;wj1Kw= }0VX,lf4Ӷ?sNC07AB2,2.Jgfk1bmbo3=ݽ /D3^ |; F plUT>6r(v&mYcк3*T:zZ޺:bf+LVB7uwgf7:,H$ T(FZW؞>k!Ш6qNWX+n 3;O07}P%v f0z.<6zxqH@2 Ѻ]$?CXn')LgC'qf6Sg|yS|zY?@e(OB1k#oݟ?¢49,PMvZ~(wg>CUs ',s!5WjZv&^ j!B5ßZ`p;3r|D>儉t3[Jm(Q\SB=( t (9h8bl7&?Bo0Uu؏oʂPoL2ڦb,~Nx6eVe+^$@&!UloZI$lM[ za( =V49+pT]-jt@ȟrU +{1rجfV&%bj$ٜ+\^1:[#i%1 w~ONn 2<4IJ ZV}̷rGڟ*iMr-s *֘ϛW gW%i+GPYYdm<17y2 5J3 1}pP,A1+.m?[-Y'aaSj rŔ~ɪX i n4=^BY̿:(UzM6Tï4R4r0Wr4҃vzV-Pby]ASr|OPjhX/CkN"aKfv}gڹn3DJy!\mhZ1+i%/;Sy|2Ϣ%1R5əxC䕑\)#8_f5XUN &;{S}M`?8=PDjx4zC>%ڜ:7=ElkX n IDAT'm? KIak]xk}n.3g_ʹ18_aE W[}=6ͭaGo$dۚ6FÚhc43,SU)h3Ԩ͵iWGlsbm4LOzTΪ(qQoϠiJ ͬ{/Psl):XuLV}90SuEJ}Qgi@?[Pp$6Bq;=wޕۗ+u:o&g,zPUDn _ۼ cz̮mאIc?=5Y%@ouǸ:Z3]3?Cg9Z'AT9Bz:4C`WM-p+ :k6YȷUw6YPG<8rKR:W9,n_=gG8#3:/|cPhꏩ}h|5 QbCbiwFtp 0ĕpT;==cOd\ju$FgQZ K=ኺ;f Ikh80}F ˧a.Ăl+Z_UaT;Tt~[f*!sz>zKy2k E 0#Rw I\Lc{ ڧhqN^X~1޹MW(vОJ2 2@5GF/Y);[VwiO>`ǡku8ƹx+lW|3#Uvz͞+7~k9r.:c4]w}R=Ѝ{rwk_Aڝ M ܄?ߡ7\QId=~ -3 e]ݳ-ms9{2-p :דg>?^aZ$l@B{^6gd;vrg&7"zᕍW~ȶhwALW%Ahv^oinCl,R #ճ(z~lYB$̌szBuާE? sǵ v&fկg k8y#ޠaf\YTFV `W"$3LHMy+K|Twȿe$Ɔ#kYfCi=p_=;Ȋ~ +9.DH$<ǻ62- Lz)AF 3;)Tyj 42͏?HU: Dӄ9v:GM&G!X+dʺ8puPGSF#Aݍn!(ȓx*Z~HH F0D(vҔr3 @pP#uQ7y. In/ ?+VtZPz+PmիO5l> B (S r_XHx 7if[5t hDpT9a4$VF"a< !08Enk٦ ݁  h&*aȒ0WPcA$,Gi*umAA0 W>'#T^dIF ̀-]$J 7D…f^#! N /"Ȓʁp-"(!Ҫ(eqWulFCAt%@_DhTiTtv47d=Dª(OB}X$@YW!x)(V /"awpYޘ3tcl>3{]ܥ0 w:@΅,$,|@WY6.ᆠꆌ¾L2ٔfgTj2fs$$ l _Dj EfE*`+ow.Y "aB0 }O$\4̦ wYLjGטr-h^&cPTH$n!kKאHb"|hqI_kc3A-} IJ  `$HShUZ|nTIvK ad`4Y5`^{fe -r:Y fDF CQ"Hu$2%!2~fV҉ܝѧ_vK7f60w?ke ;p=0=/\$>A~ %}JcfoTINi(f@5N :aӿtu_IV nh&{ "'c\u!fvbvAG, nTrg}K,wt:E=Pe7QR+Q4VE#Nݓ'EBDwYnz#KyH4,<-͏$ffGhw4{I螘 fvdf=3;u&3]B3{n7l*Q+fv]iff62C1[3if7قzI3CZ7܊fvofa*3;̞F03fl `}:3̆ٹ_Z6/qi1uY~t6lgo32l[31mffkf]jfTӠ[IG{V40]c3۷͞ 3kf1ׁ1f6>6ͬ%t1wPP?4BNK ³IezSE| oC{"YGb"g0]*(`lfE֝ۯv_04m̲z%Z r}2YV`\CPJ , OFVp"'GQ´;R;䖝e,l"5w<~H*0eZo2gg?#~ӹ?U㛖?+-y:qpm Hm2' Gƍi]ێO.@3}H;,D"a( 7qF}>`%Dd])Z8ЮM_ =M_X 7`ZV؏-27GU҃j2>3iri=͑2ӼJŴ:X?^t3!HTH{ }CH(\]-zn?sn} L7Tm|7g_UZ˝P`- AE B"뮎yYf4y=+~h }=tʲx7]An~~])\mnt_sG-7 i̍u yZg$02힝CznheCJkhZ)p`}BI}XGNGEg3_nkfYow/vs \Ee3i|?k*mGPE/H+uH6̱mhq}Gl-} Eȉx9h2-|R=N@VS,R65IFKo٩627>&_FrPm}ȴXX̞+!?N0E ,efo&(dxqwO ~N'W5tb nffu'mg'O@!)E+$G{Rod;k1ʰ7FcT A} I$DBu2p",ތ^Go'6cmf6sjw4W>*yPߌ砇VMYtwFS̎ȵjf_13lNڝ ;w? - NgCI dKk'L[ >,SV ܽQ-RLVlb2_[Xݯw. 2y/M޳DHa ȋJe3F8}ӐOԒ;VU wF!^hB +lavCn G"!K p ;gH3>2=ef/Y}S̬̀E;`p}S0Gfhy*v6EhO=YhW(AFY0݋v R,ҹcRd90w{f}SEͫ3-os[PHac6+;?Ǻ.hXd E@WsE5B͎zKJ 6Z swC@+G񳶳iL#,N"67-$N-LXZ!WHǼ0dLA(aهiv-9g#A-=ӧWZ wS%F͂5ݱhXw2 wlk$OA# ЛȁgM#0# :+N*13~%!-8#cgt %BlݍnBM\)}֟|F/{$+!("ovf炠@>=8<* BzˠbpY̶Ao(lŌH8-DBt)7R83{=̞F ˺S] #PGlAx8p *$%} .BRWCaqSS'GOBt )t(mN[60ċT L:7=]| C_=}Mviz ӈ!:'}J8st6%?YYB M7S~E}l ݐOBT J/g!]PnQzQRfwZCw,d= pFPR[l]lf 9 yNkq-fD굽 I u< Q3QճP(ЃHVB> Y$NJfI^6-$& U&\mAON.Aowb.["h`WvgpqǙRI$l"! !kG(Lw<|a;AA7 B'"N!Ks(Tq(rJ| _*1[H2_ifGC-Uvo^7虦) pWFAMɸ9(' x0j3L7!.7" `f6 UI M},}U;At+"0ߣo{ȁyȯkp 7\U)-F!ƛٔ(Hw̾l7Y6[*4\ALĄpѐÖ(({O1R8E7"6N`v (>C5(qS ~f&M I t1f6Jt p*+xlYL_Y6r f"df # 3.rhgs Q2 4rf6J}fQ{ACXw \ Cy $VN5Ɂ8̊JE2Nb$0Q%`$EC* qA0&6@(9"Waq G7,( pnf롰ϷK#c3!m:f$d I 4 ~'r<Op,Yq0Y ~Ű:po I 4)1(EhT^hu$"By."441-/G8EQ] &-‚d =C;ρ CP QmgIl<|0w=?D96ȉŀCALI15Q(|+$ CQ cbˎ. IDATT2Rpiw4NH/ͅJGoF!EQ~|2;m #f,n,OQhW(( hazTqw Eɕ6 A&1MHiKsf0_M IX D`<0-0m  6m}H+ hN‚D$À3[| ^z y:f, If6u>+l]T B ` BafW#rB|ؚ"?Ƹ? q\0Qp~}eig$Dsj {IH#U݊,-9XYEf6uqgEKM?hxw= l}jHdi8 3~ ̉ʊw<'.XFǡ0S, 讄 0;'`ifPP;P(4(Rgȧ`lb#]7(ZfvT$qU_\8{rAjǁPDOTˀl 'E  t1f OY'p%pWvE.F_Er1?#*OT4jA4aAzV+ŁMofBJ[ĵji;_aQeY6s`9`+$  4f6'mzYoyn{ p7kx 3^r}lc }38MAI&zkq(sM~mx L8)auv`w G3[ xzX8]sX'8:M0< hϣ"Q%$mT[Qρ P6ȵQeAЍ,P)SlJ?K~@ΕAA7CO`G٪`JD/3g40 U|4 9F At3!(AQ3 ǡaatZn%bw,JUNͭńEAФ@%EV eM\?1_"B?|`Z$ f[!(y]AAՄ@JJ< e^<Ux| fkvA7 :>AI 3(3~ `fS] "!( w19%|gfmAi(Ym[ b|ERAwvl;Tmr.w 'BP6f,ugzuȪ-rTx8A5>A%, @E eFdK{] v‚TDNUL 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% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd itsdangerous-2.2.0/docs/serializer.rst000066400000000000000000000066351460756553000201020ustar00rootroot00000000000000.. module:: itsdangerous.serializer Serialization Interface ======================= The :doc:`/signer` only signs bytes. To sign other types, the :class:`Serializer` class provides a ``dumps``/``loads`` interface similar to Python's :mod:`json` module, which serializes the object to a string then signs that. Use :meth:`~Serializer.dumps` to serialize and sign the data: .. code-block:: python from itsdangerous.serializer import Serializer s = Serializer("secret-key") s.dumps([1, 2, 3, 4]) b'[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo' Use :meth:`~Serializer.loads` to verify the signature and deserialize the data. .. code-block:: python s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo') [1, 2, 3, 4] By default, data is serialized to JSON with the built-in :mod:`json` module. This internal serializer can be changed by subclassing. To record and validate the age of the signature, see :doc:`/timed`. To serialize to a format that is safe to use in URLs, see :doc:`/url_safe`. Responding to Failure --------------------- Exceptions have helpful attributes which allow you to inspect the payload if the signature check failed. This has to be done with extra care because at that point you know that someone tampered with your data but it might be useful for debugging purposes. .. code-block:: python from itsdangerous.serializer import Serializer from itsdangerous.exc import BadSignature, BadData s = URLSafeSerializer("secret-key") decoded_payload = None try: decoded_payload = s.loads(data) # This payload is decoded and safe except BadSignature as e: if e.payload is not None: try: decoded_payload = s.load_payload(e.payload) except BadData: pass # This payload is decoded but unsafe because someone # tampered with the signature. The decode (load_payload) # step is explicit because it might be unsafe to unserialize # the payload (think pickle instead of json!) If you don't want to inspect attributes to figure out what exactly went wrong you can also use :meth:`~Serializer.loads_unsafe`: .. code-block:: python sig_okay, payload = s.loads_unsafe(data) The first item in the returned tuple is a boolean that indicates if the signature was correct. Fallback Signers ---------------- You may want to upgrade the signing parameters without invalidating existing signatures immediately. For example, you may decide that you want to use a different digest method. New signatures should use the new method, but old signatures should still validate. A list of ``fallback_signers`` can be given that will be tried if unsigning with the current signer fails. Each item in the list can be: - A dict of ``signer_kwargs`` to instantiate the ``signer`` class passed to the serializer. - A :class:`~itsdangerous.signer.Signer` class to instantiated with the ``secret_key``, ``salt``, and ``signer_kwargs`` passed to the serializer. - A tuple of ``(signer_class, signer_kwargs)`` to instantiate the given class with the given args. For example, this is a serializer that signs using SHA-512, but will unsign using either SHA-512 or SHA-1: .. code-block:: python s = Serializer( signer_kwargs={"digest_method": hashlib.sha512}, fallback_signers=[{"digest_method": hashlib.sha1}] ) API --- .. autoclass:: Serializer :members: itsdangerous-2.2.0/docs/signer.rst000066400000000000000000000023571460756553000172150ustar00rootroot00000000000000.. module:: itsdangerous.signer Signing Interface ================= The most basic interface is the signing interface. The :class:`Signer` class can be used to attach a signature to a specific string: .. code-block:: python from itsdangerous import Signer s = Signer("secret-key") s.sign("my string") b'my string.wh6tMHxLgJqB6oY1uT73iMlyrOA' The signature is appended to the string, separated by a dot. To validate the string, use the :meth:`~Signer.unsign` method: .. code-block:: python s.unsign(b"my string.wh6tMHxLgJqB6oY1uT73iMlyrOA") b'my string' If unicode strings are provided, an implicit encoding to UTF-8 happens. However after unsigning you won't be able to tell if it was unicode or a bytestring. If the value is changed, the signature will no longer match, and unsigning will raise a :exc:`~itsdangerous.exc.BadSignature` exception: .. code-block:: python s.unsign(b"different string.wh6tMHxLgJqB6oY1uT73iMlyrOA") Traceback (most recent call last): ... BadSignature: Signature does not match To record and validate the age of a signature, see :doc:`/timed`. .. autoclass:: Signer :members: Signing Algorithms ------------------ .. autoclass:: NoneAlgorithm .. autoclass:: HMACAlgorithm itsdangerous-2.2.0/docs/timed.rst000066400000000000000000000012611460756553000170210ustar00rootroot00000000000000.. module:: itsdangerous.timed Signing With Timestamps ======================= If you want to expire signatures you can use the :class:`TimestampSigner` class which adds timestamp information and signs it. On unsigning you can validate that the timestamp is not older than a given age. .. code-block:: python from itsdangerous import TimestampSigner s = TimestampSigner('secret-key') string = s.sign('foo') .. code-block:: python s.unsign(string, max_age=5) Traceback (most recent call last): ... itsdangerous.exc.SignatureExpired: Signature age 15 > 5 seconds .. autoclass:: TimestampSigner :members: .. autoclass:: TimedSerializer :members: itsdangerous-2.2.0/docs/url_safe.rst000066400000000000000000000011471460756553000175220ustar00rootroot00000000000000.. module:: itsdangerous.url_safe URL Safe Serialization ====================== Often it is helpful if you can pass these trusted strings in places where you only have a limited set of characters available. Because of this, ItsDangerous also provides URL safe serializers: .. code-block:: python from itsdangerous.url_safe import URLSafeSerializer s = URLSafeSerializer("secret-key") s.dumps([1, 2, 3, 4]) 'WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo' s.loads("WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo") [1, 2, 3, 4] .. autoclass:: URLSafeSerializer .. autoclass:: URLSafeTimedSerializer itsdangerous-2.2.0/pyproject.toml000066400000000000000000000035141460756553000171540ustar00rootroot00000000000000[project] name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." readme = "README.md" license = { file = "LICENSE.txt" } maintainers = [{ name = "Pallets", email = "contact@palletsprojects.com" }] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Typing :: Typed", ] requires-python = ">=3.8" [project.urls] Donate = "https://palletsprojects.com/donate" Documentation = "https://itsdangerous.palletsprojects.com/" Changes = "https://itsdangerous.palletsprojects.com/changes/" Source = "https://github.com/pallets/itsdangerous/" Chat = "https://discord.gg/pallets" [build-system] requires = ["flit_core<4"] build-backend = "flit_core.buildapi" [tool.flit.module] name = "itsdangerous" [tool.flit.sdist] include = [ "docs/", "requirements/", "tests/", "CHANGES.rst", "tox.ini", ] exclude = [ "docs/_build/", ] [tool.pytest.ini_options] testpaths = ["tests"] filterwarnings = [ "error", ] [tool.coverage.run] branch = true source = ["itsdangerous", "tests"] [tool.coverage.paths] source = ["src", "*/site-packages"] [tool.mypy] python_version = "3.8" files = ["src/itsdangerous"] show_error_codes = true pretty = true strict = true [tool.pyright] pythonVersion = "3.8" include = ["src/itsdangerous"] typeCheckingMode = "basic" [tool.ruff] src = ["src"] fix = true show-fixes = true output-format = "full" [tool.ruff.lint] select = [ "B", # flake8-bugbear "E", # pycodestyle error "F", # pyflakes "I", # isort "UP", # pyupgrade "W", # pycodestyle warning ] ignore-init-module-imports = true [tool.ruff.lint.isort] force-single-line = true order-by-type = false itsdangerous-2.2.0/requirements/000077500000000000000000000000001460756553000167605ustar00rootroot00000000000000itsdangerous-2.2.0/requirements/build.in000066400000000000000000000000061460756553000204030ustar00rootroot00000000000000build itsdangerous-2.2.0/requirements/build.txt000066400000000000000000000003431460756553000206200ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile build.in # build==1.2.1 # via -r build.in packaging==24.0 # via build pyproject-hooks==1.0.0 # via build itsdangerous-2.2.0/requirements/dev.in000066400000000000000000000000661460756553000200700ustar00rootroot00000000000000-r docs.txt -r tests.txt -r typing.txt pre-commit tox itsdangerous-2.2.0/requirements/dev.txt000066400000000000000000000060651460756553000203060ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile dev.in # alabaster==0.7.16 # via # -r docs.txt # sphinx babel==2.14.0 # via # -r docs.txt # sphinx cachetools==5.3.3 # via tox certifi==2024.2.2 # via # -r docs.txt # requests cfgv==3.4.0 # via pre-commit chardet==5.2.0 # via tox charset-normalizer==3.3.2 # via # -r docs.txt # requests colorama==0.4.6 # via tox distlib==0.3.8 # via virtualenv docutils==0.20.1 # via # -r docs.txt # sphinx filelock==3.13.4 # via # tox # virtualenv freezegun==1.4.0 # via -r tests.txt identify==2.5.35 # via pre-commit idna==3.7 # via # -r docs.txt # requests imagesize==1.4.1 # via # -r docs.txt # sphinx iniconfig==2.0.0 # via # -r tests.txt # -r typing.txt # pytest jinja2==3.1.3 # via # -r docs.txt # sphinx markupsafe==2.1.5 # via # -r docs.txt # jinja2 mypy==1.9.0 # via -r typing.txt mypy-extensions==1.0.0 # via # -r typing.txt # mypy nodeenv==1.8.0 # via # -r typing.txt # pre-commit # pyright packaging==24.0 # via # -r docs.txt # -r tests.txt # -r typing.txt # pallets-sphinx-themes # pyproject-api # pytest # sphinx # tox pallets-sphinx-themes==2.1.1 # via -r docs.txt platformdirs==4.2.0 # via # tox # virtualenv pluggy==1.4.0 # via # -r tests.txt # -r typing.txt # pytest # tox pre-commit==3.7.0 # via -r dev.in pygments==2.17.2 # via # -r docs.txt # sphinx pyproject-api==1.6.1 # via tox pyright==1.1.358 # via -r typing.txt pytest==8.1.1 # via # -r tests.txt # -r typing.txt python-dateutil==2.9.0.post0 # via # -r tests.txt # freezegun pyyaml==6.0.1 # via pre-commit requests==2.31.0 # via # -r docs.txt # sphinx six==1.16.0 # via # -r tests.txt # python-dateutil snowballstemmer==2.2.0 # via # -r docs.txt # sphinx sphinx==7.2.6 # via # -r docs.txt # pallets-sphinx-themes # sphinxcontrib-log-cabinet sphinxcontrib-applehelp==1.0.8 # via # -r docs.txt # sphinx sphinxcontrib-devhelp==1.0.6 # via # -r docs.txt # sphinx sphinxcontrib-htmlhelp==2.0.5 # via # -r docs.txt # sphinx sphinxcontrib-jsmath==1.0.1 # via # -r docs.txt # sphinx sphinxcontrib-log-cabinet==1.0.1 # via -r docs.txt sphinxcontrib-qthelp==1.0.7 # via # -r docs.txt # sphinx sphinxcontrib-serializinghtml==1.1.10 # via # -r docs.txt # sphinx tox==4.14.2 # via -r dev.in typing-extensions==4.11.0 # via # -r typing.txt # mypy urllib3==2.2.1 # via # -r docs.txt # requests virtualenv==20.25.1 # via # pre-commit # tox # The following packages are considered to be unsafe in a requirements file: # setuptools itsdangerous-2.2.0/requirements/docs.in000066400000000000000000000000671460756553000202430ustar00rootroot00000000000000pallets-sphinx-themes sphinx sphinxcontrib-log-cabinet itsdangerous-2.2.0/requirements/docs.txt000066400000000000000000000021651460756553000204550ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile docs.in # alabaster==0.7.16 # via sphinx babel==2.14.0 # via sphinx certifi==2024.2.2 # via requests charset-normalizer==3.3.2 # via requests docutils==0.20.1 # via sphinx idna==3.7 # via requests imagesize==1.4.1 # via sphinx jinja2==3.1.3 # via sphinx markupsafe==2.1.5 # via jinja2 packaging==24.0 # via # pallets-sphinx-themes # sphinx pallets-sphinx-themes==2.1.1 # via -r docs.in pygments==2.17.2 # via sphinx requests==2.31.0 # via sphinx snowballstemmer==2.2.0 # via sphinx sphinx==7.2.6 # via # -r docs.in # pallets-sphinx-themes # sphinxcontrib-log-cabinet sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 # via sphinx sphinxcontrib-htmlhelp==2.0.5 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-log-cabinet==1.0.1 # via -r docs.in sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx urllib3==2.2.1 # via requests itsdangerous-2.2.0/requirements/tests.in000066400000000000000000000000211460756553000204430ustar00rootroot00000000000000pytest freezegun itsdangerous-2.2.0/requirements/tests.txt000066400000000000000000000005751460756553000206720ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile tests.in # freezegun==1.4.0 # via -r tests.in iniconfig==2.0.0 # via pytest packaging==24.0 # via pytest pluggy==1.4.0 # via pytest pytest==8.1.1 # via -r tests.in python-dateutil==2.9.0.post0 # via freezegun six==1.16.0 # via python-dateutil itsdangerous-2.2.0/requirements/typing.in000066400000000000000000000000241460756553000206160ustar00rootroot00000000000000mypy pyright pytest itsdangerous-2.2.0/requirements/typing.txt000066400000000000000000000010271460756553000210330ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile typing.in # iniconfig==2.0.0 # via pytest mypy==1.9.0 # via -r typing.in mypy-extensions==1.0.0 # via mypy nodeenv==1.8.0 # via pyright packaging==24.0 # via pytest pluggy==1.4.0 # via pytest pyright==1.1.358 # via -r typing.in pytest==8.1.1 # via -r typing.in typing-extensions==4.11.0 # via mypy # The following packages are considered to be unsafe in a requirements file: # setuptools itsdangerous-2.2.0/src/000077500000000000000000000000001460756553000150245ustar00rootroot00000000000000itsdangerous-2.2.0/src/itsdangerous/000077500000000000000000000000001460756553000175335ustar00rootroot00000000000000itsdangerous-2.2.0/src/itsdangerous/__init__.py000066400000000000000000000026231460756553000216470ustar00rootroot00000000000000from __future__ import annotations import typing as t from .encoding import base64_decode as base64_decode from .encoding import base64_encode as base64_encode from .encoding import want_bytes as want_bytes from .exc import BadData as BadData from .exc import BadHeader as BadHeader from .exc import BadPayload as BadPayload from .exc import BadSignature as BadSignature from .exc import BadTimeSignature as BadTimeSignature from .exc import SignatureExpired as SignatureExpired from .serializer import Serializer as Serializer from .signer import HMACAlgorithm as HMACAlgorithm from .signer import NoneAlgorithm as NoneAlgorithm from .signer import Signer as Signer from .timed import TimedSerializer as TimedSerializer from .timed import TimestampSigner as TimestampSigner from .url_safe import URLSafeSerializer as URLSafeSerializer from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer def __getattr__(name: str) -> t.Any: if name == "__version__": import importlib.metadata import warnings warnings.warn( "The '__version__' attribute is deprecated and will be removed in" " ItsDangerous 2.3. Use feature detection or" " 'importlib.metadata.version(\"itsdangerous\")' instead.", DeprecationWarning, stacklevel=2, ) return importlib.metadata.version("itsdangerous") raise AttributeError(name) itsdangerous-2.2.0/src/itsdangerous/_json.py000066400000000000000000000007311460756553000212160ustar00rootroot00000000000000from __future__ import annotations import json as _json import typing as t class _CompactJSON: """Wrapper around json module that strips whitespace.""" @staticmethod def loads(payload: str | bytes) -> t.Any: return _json.loads(payload) @staticmethod def dumps(obj: t.Any, **kwargs: t.Any) -> str: kwargs.setdefault("ensure_ascii", False) kwargs.setdefault("separators", (",", ":")) return _json.dumps(obj, **kwargs) itsdangerous-2.2.0/src/itsdangerous/encoding.py000066400000000000000000000026011460756553000216720ustar00rootroot00000000000000from __future__ import annotations import base64 import string import struct import typing as t from .exc import BadData def want_bytes( s: str | bytes, encoding: str = "utf-8", errors: str = "strict" ) -> bytes: if isinstance(s, str): s = s.encode(encoding, errors) return s def base64_encode(string: str | bytes) -> bytes: """Base64 encode a string of bytes or text. The resulting bytes are safe to use in URLs. """ string = want_bytes(string) return base64.urlsafe_b64encode(string).rstrip(b"=") def base64_decode(string: str | bytes) -> bytes: """Base64 decode a URL-safe string of bytes or text. The result is bytes. """ string = want_bytes(string, encoding="ascii", errors="ignore") string += b"=" * (-len(string) % 4) try: return base64.urlsafe_b64decode(string) except (TypeError, ValueError) as e: raise BadData("Invalid base64-encoded data") from e # The alphabet used by base64.urlsafe_* _base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") _int64_struct = struct.Struct(">Q") _int_to_bytes = _int64_struct.pack _bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) def int_to_bytes(num: int) -> bytes: return _int_to_bytes(num).lstrip(b"\x00") def bytes_to_int(bytestr: bytes) -> int: return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0] itsdangerous-2.2.0/src/itsdangerous/exc.py000066400000000000000000000062011460756553000206630ustar00rootroot00000000000000from __future__ import annotations import typing as t from datetime import datetime class BadData(Exception): """Raised if bad data of any sort was encountered. This is the base for all exceptions that ItsDangerous defines. .. versionadded:: 0.15 """ def __init__(self, message: str): super().__init__(message) self.message = message def __str__(self) -> str: return self.message class BadSignature(BadData): """Raised if a signature does not match.""" def __init__(self, message: str, payload: t.Any | None = None): super().__init__(message) #: The payload that failed the signature test. In some #: situations you might still want to inspect this, even if #: you know it was tampered with. #: #: .. versionadded:: 0.14 self.payload: t.Any | None = payload class BadTimeSignature(BadSignature): """Raised if a time-based signature is invalid. This is a subclass of :class:`BadSignature`. """ def __init__( self, message: str, payload: t.Any | None = None, date_signed: datetime | None = None, ): super().__init__(message, payload) #: If the signature expired this exposes the date of when the #: signature was created. This can be helpful in order to #: tell the user how long a link has been gone stale. #: #: .. versionchanged:: 2.0 #: The datetime value is timezone-aware rather than naive. #: #: .. versionadded:: 0.14 self.date_signed = date_signed class SignatureExpired(BadTimeSignature): """Raised if a signature timestamp is older than ``max_age``. This is a subclass of :exc:`BadTimeSignature`. """ class BadHeader(BadSignature): """Raised if a signed header is invalid in some form. This only happens for serializers that have a header that goes with the signature. .. versionadded:: 0.24 """ def __init__( self, message: str, payload: t.Any | None = None, header: t.Any | None = None, original_error: Exception | None = None, ): super().__init__(message, payload) #: If the header is actually available but just malformed it #: might be stored here. self.header: t.Any | None = header #: If available, the error that indicates why the payload was #: not valid. This might be ``None``. self.original_error: Exception | None = original_error class BadPayload(BadData): """Raised if a payload is invalid. This could happen if the payload is loaded despite an invalid signature, or if there is a mismatch between the serializer and deserializer. The original exception that occurred during loading is stored on as :attr:`original_error`. .. versionadded:: 0.15 """ def __init__(self, message: str, original_error: Exception | None = None): super().__init__(message) #: If available, the error that indicates why the payload was #: not valid. This might be ``None``. self.original_error: Exception | None = original_error itsdangerous-2.2.0/src/itsdangerous/py.typed000066400000000000000000000000001460756553000212200ustar00rootroot00000000000000itsdangerous-2.2.0/src/itsdangerous/serializer.py000066400000000000000000000363611460756553000222670ustar00rootroot00000000000000from __future__ import annotations import collections.abc as cabc import json import typing as t from .encoding import want_bytes from .exc import BadPayload from .exc import BadSignature from .signer import _make_keys_list from .signer import Signer if t.TYPE_CHECKING: import typing_extensions as te # This should be either be str or bytes. To avoid having to specify the # bound type, it falls back to a union if structural matching fails. _TSerialized = te.TypeVar( "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] ) else: # Still available at runtime on Python < 3.13, but without the default. _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) class _PDataSerializer(t.Protocol[_TSerialized]): def loads(self, payload: _TSerialized, /) -> t.Any: ... # A signature with additional arguments is not handled correctly by type # checkers right now, so an overload is used below for serializers that # don't match this strict protocol. def dumps(self, obj: t.Any, /) -> _TSerialized: ... # Use TypeIs once it's available in typing_extensions or 3.13. def is_text_serializer( serializer: _PDataSerializer[t.Any], ) -> te.TypeGuard[_PDataSerializer[str]]: """Checks whether a serializer generates text or binary.""" return isinstance(serializer.dumps({}), str) class Serializer(t.Generic[_TSerialized]): """A serializer wraps a :class:`~itsdangerous.signer.Signer` to enable serializing and securely signing data other than bytes. It can unsign to verify that the data hasn't been changed. The serializer provides :meth:`dumps` and :meth:`loads`, similar to :mod:`json`, and by default uses :mod:`json` internally to serialize the data to bytes. The secret key should be a random string of ``bytes`` and should not be saved to code or version control. Different salts should be used to distinguish signing in different contexts. See :doc:`/concepts` for information about the security of the secret key and salt. :param secret_key: The secret key to sign and verify with. Can be a list of keys, oldest to newest, to support key rotation. :param salt: Extra key to combine with ``secret_key`` to distinguish signatures in different contexts. :param serializer: An object that provides ``dumps`` and ``loads`` methods for serializing data to a string. Defaults to :attr:`default_serializer`, which defaults to :mod:`json`. :param serializer_kwargs: Keyword arguments to pass when calling ``serializer.dumps``. :param signer: A ``Signer`` class to instantiate when signing data. Defaults to :attr:`default_signer`, which defaults to :class:`~itsdangerous.signer.Signer`. :param signer_kwargs: Keyword arguments to pass when instantiating the ``Signer`` class. :param fallback_signers: List of signer parameters to try when unsigning with the default signer fails. Each item can be a dict of ``signer_kwargs``, a ``Signer`` class, or a tuple of ``(signer, signer_kwargs)``. Defaults to :attr:`default_fallback_signers`. .. versionchanged:: 2.0 Added support for key rotation by passing a list to ``secret_key``. .. versionchanged:: 2.0 Removed the default SHA-512 fallback signer from ``default_fallback_signers``. .. versionchanged:: 1.1 Added support for ``fallback_signers`` and configured a default SHA-512 fallback. This fallback is for users who used the yanked 1.0.0 release which defaulted to SHA-512. .. versionchanged:: 0.14 The ``signer`` and ``signer_kwargs`` parameters were added to the constructor. """ #: The default serialization module to use to serialize data to a #: string internally. The default is :mod:`json`, but can be changed #: to any object that provides ``dumps`` and ``loads`` methods. default_serializer: _PDataSerializer[t.Any] = json #: The default ``Signer`` class to instantiate when signing data. #: The default is :class:`itsdangerous.signer.Signer`. default_signer: type[Signer] = Signer #: The default fallback signers to try when unsigning fails. default_fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] = [] # Serializer[str] if no data serializer is provided, or if it returns str. @t.overload def __init__( self: Serializer[str], secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None = b"itsdangerous", serializer: None | _PDataSerializer[str] = None, serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): ... # Serializer[bytes] with a bytes data serializer positional argument. @t.overload def __init__( self: Serializer[bytes], secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None, serializer: _PDataSerializer[bytes], serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): ... # Serializer[bytes] with a bytes data serializer keyword argument. @t.overload def __init__( self: Serializer[bytes], secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None = b"itsdangerous", *, serializer: _PDataSerializer[bytes], serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): ... # Fall back with a positional argument. If the strict signature of # _PDataSerializer doesn't match, fall back to a union, requiring the user # to specify the type. @t.overload def __init__( self, secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None, serializer: t.Any, serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): ... # Fall back with a keyword argument. @t.overload def __init__( self, secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None = b"itsdangerous", *, serializer: t.Any, serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): ... def __init__( self, secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None = b"itsdangerous", serializer: t.Any | None = None, serializer_kwargs: dict[str, t.Any] | None = None, signer: type[Signer] | None = None, signer_kwargs: dict[str, t.Any] | None = None, fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] | None = None, ): #: The list of secret keys to try for verifying signatures, from #: oldest to newest. The newest (last) key is used for signing. #: #: This allows a key rotation system to keep a list of allowed #: keys and remove expired ones. self.secret_keys: list[bytes] = _make_keys_list(secret_key) if salt is not None: salt = want_bytes(salt) # if salt is None then the signer's default is used self.salt = salt if serializer is None: serializer = self.default_serializer self.serializer: _PDataSerializer[_TSerialized] = serializer self.is_text_serializer: bool = is_text_serializer(serializer) if signer is None: signer = self.default_signer self.signer: type[Signer] = signer self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} if fallback_signers is None: fallback_signers = list(self.default_fallback_signers) self.fallback_signers: list[ dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] ] = fallback_signers self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} @property def secret_key(self) -> bytes: """The newest (last) entry in the :attr:`secret_keys` list. This is for compatibility from before key rotation support was added. """ return self.secret_keys[-1] def load_payload( self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None ) -> t.Any: """Loads the encoded object. This function raises :class:`.BadPayload` if the payload is not valid. The ``serializer`` parameter can be used to override the serializer stored on the class. The encoded ``payload`` should always be bytes. """ if serializer is None: use_serializer = self.serializer is_text = self.is_text_serializer else: use_serializer = serializer is_text = is_text_serializer(serializer) try: if is_text: return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] return use_serializer.loads(payload) # type: ignore[arg-type] except Exception as e: raise BadPayload( "Could not load the payload because an exception" " occurred on unserializing the data.", original_error=e, ) from e def dump_payload(self, obj: t.Any) -> bytes: """Dumps the encoded object. The return value is always bytes. If the internal serializer returns text, the value will be encoded as UTF-8. """ return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) def make_signer(self, salt: str | bytes | None = None) -> Signer: """Creates a new instance of the signer to be used. The default implementation uses the :class:`.Signer` base class. """ if salt is None: salt = self.salt return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: """Iterates over all signers to be tried for unsigning. Starts with the configured signer, then constructs each signer specified in ``fallback_signers``. """ if salt is None: salt = self.salt yield self.make_signer(salt) for fallback in self.fallback_signers: if isinstance(fallback, dict): kwargs = fallback fallback = self.signer elif isinstance(fallback, tuple): fallback, kwargs = fallback else: kwargs = self.signer_kwargs for secret_key in self.secret_keys: yield fallback(secret_key, salt=salt, **kwargs) def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: """Returns a signed string serialized with the internal serializer. The return value can be either a byte or unicode string depending on the format of the internal serializer. """ payload = want_bytes(self.dump_payload(obj)) rv = self.make_signer(salt).sign(payload) if self.is_text_serializer: return rv.decode("utf-8") # type: ignore[return-value] return rv # type: ignore[return-value] def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: """Like :meth:`dumps` but dumps into a file. The file handle has to be compatible with what the internal serializer expects. """ f.write(self.dumps(obj, salt)) def loads( self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any ) -> t.Any: """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the signature validation fails. """ s = want_bytes(s) last_exception = None for signer in self.iter_unsigners(salt): try: return self.load_payload(signer.unsign(s)) except BadSignature as err: last_exception = err raise t.cast(BadSignature, last_exception) def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: """Like :meth:`loads` but loads from a file.""" return self.loads(f.read(), salt) def loads_unsafe( self, s: str | bytes, salt: str | bytes | None = None ) -> tuple[bool, t.Any]: """Like :meth:`loads` but without verifying the signature. This is potentially very dangerous to use depending on how your serializer works. The return value is ``(signature_valid, payload)`` instead of just the payload. The first item will be a boolean that indicates if the signature is valid. This function never fails. Use it for debugging only and if you know that your serializer module is not exploitable (for example, do not use it with a pickle serializer). .. versionadded:: 0.15 """ return self._loads_unsafe_impl(s, salt) def _loads_unsafe_impl( self, s: str | bytes, salt: str | bytes | None, load_kwargs: dict[str, t.Any] | None = None, load_payload_kwargs: dict[str, t.Any] | None = None, ) -> tuple[bool, t.Any]: """Low level helper function to implement :meth:`loads_unsafe` in serializer subclasses. """ if load_kwargs is None: load_kwargs = {} try: return True, self.loads(s, salt=salt, **load_kwargs) except BadSignature as e: if e.payload is None: return False, None if load_payload_kwargs is None: load_payload_kwargs = {} try: return ( False, self.load_payload(e.payload, **load_payload_kwargs), ) except BadPayload: return False, None def load_unsafe( self, f: t.IO[t.Any], salt: str | bytes | None = None ) -> tuple[bool, t.Any]: """Like :meth:`loads_unsafe` but loads from a file. .. versionadded:: 0.15 """ return self.loads_unsafe(f.read(), salt=salt) itsdangerous-2.2.0/src/itsdangerous/signer.py000066400000000000000000000226571460756553000214100ustar00rootroot00000000000000from __future__ import annotations import collections.abc as cabc import hashlib import hmac import typing as t from .encoding import _base64_alphabet from .encoding import base64_decode from .encoding import base64_encode from .encoding import want_bytes from .exc import BadSignature class SigningAlgorithm: """Subclasses must implement :meth:`get_signature` to provide signature generation functionality. """ def get_signature(self, key: bytes, value: bytes) -> bytes: """Returns the signature for the given key and value.""" raise NotImplementedError() def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: """Verifies the given signature matches the expected signature. """ return hmac.compare_digest(sig, self.get_signature(key, value)) class NoneAlgorithm(SigningAlgorithm): """Provides an algorithm that does not perform any signing and returns an empty signature. """ def get_signature(self, key: bytes, value: bytes) -> bytes: return b"" def _lazy_sha1(string: bytes = b"") -> t.Any: """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include SHA-1, in which case the import and use as a default would fail before the developer can configure something else. """ return hashlib.sha1(string) class HMACAlgorithm(SigningAlgorithm): """Provides signature generation using HMACs.""" #: The digest method to use with the MAC algorithm. This defaults to #: SHA1, but can be changed to any other function in the hashlib #: module. default_digest_method: t.Any = staticmethod(_lazy_sha1) def __init__(self, digest_method: t.Any = None): if digest_method is None: digest_method = self.default_digest_method self.digest_method: t.Any = digest_method def get_signature(self, key: bytes, value: bytes) -> bytes: mac = hmac.new(key, msg=value, digestmod=self.digest_method) return mac.digest() def _make_keys_list( secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], ) -> list[bytes]: if isinstance(secret_key, (str, bytes)): return [want_bytes(secret_key)] return [want_bytes(s) for s in secret_key] # pyright: ignore class Signer: """A signer securely signs bytes, then unsigns them to verify that the value hasn't been changed. The secret key should be a random string of ``bytes`` and should not be saved to code or version control. Different salts should be used to distinguish signing in different contexts. See :doc:`/concepts` for information about the security of the secret key and salt. :param secret_key: The secret key to sign and verify with. Can be a list of keys, oldest to newest, to support key rotation. :param salt: Extra key to combine with ``secret_key`` to distinguish signatures in different contexts. :param sep: Separator between the signature and value. :param key_derivation: How to derive the signing key from the secret key and salt. Possible values are ``concat``, ``django-concat``, or ``hmac``. Defaults to :attr:`default_key_derivation`, which defaults to ``django-concat``. :param digest_method: Hash function to use when generating the HMAC signature. Defaults to :attr:`default_digest_method`, which defaults to :func:`hashlib.sha1`. Note that the security of the hash alone doesn't apply when used intermediately in HMAC. :param algorithm: A :class:`SigningAlgorithm` instance to use instead of building a default :class:`HMACAlgorithm` with the ``digest_method``. .. versionchanged:: 2.0 Added support for key rotation by passing a list to ``secret_key``. .. versionchanged:: 0.18 ``algorithm`` was added as an argument to the class constructor. .. versionchanged:: 0.14 ``key_derivation`` and ``digest_method`` were added as arguments to the class constructor. """ #: The default digest method to use for the signer. The default is #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or #: compatible object. Note that the security of the hash alone #: doesn't apply when used intermediately in HMAC. #: #: .. versionadded:: 0.14 default_digest_method: t.Any = staticmethod(_lazy_sha1) #: The default scheme to use to derive the signing key from the #: secret key and salt. The default is ``django-concat``. Possible #: values are ``concat``, ``django-concat``, and ``hmac``. #: #: .. versionadded:: 0.14 default_key_derivation: str = "django-concat" def __init__( self, secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], salt: str | bytes | None = b"itsdangerous.Signer", sep: str | bytes = b".", key_derivation: str | None = None, digest_method: t.Any | None = None, algorithm: SigningAlgorithm | None = None, ): #: The list of secret keys to try for verifying signatures, from #: oldest to newest. The newest (last) key is used for signing. #: #: This allows a key rotation system to keep a list of allowed #: keys and remove expired ones. self.secret_keys: list[bytes] = _make_keys_list(secret_key) self.sep: bytes = want_bytes(sep) if self.sep in _base64_alphabet: raise ValueError( "The given separator cannot be used because it may be" " contained in the signature itself. ASCII letters," " digits, and '-_=' must not be used." ) if salt is not None: salt = want_bytes(salt) else: salt = b"itsdangerous.Signer" self.salt = salt if key_derivation is None: key_derivation = self.default_key_derivation self.key_derivation: str = key_derivation if digest_method is None: digest_method = self.default_digest_method self.digest_method: t.Any = digest_method if algorithm is None: algorithm = HMACAlgorithm(self.digest_method) self.algorithm: SigningAlgorithm = algorithm @property def secret_key(self) -> bytes: """The newest (last) entry in the :attr:`secret_keys` list. This is for compatibility from before key rotation support was added. """ return self.secret_keys[-1] def derive_key(self, secret_key: str | bytes | None = None) -> bytes: """This method is called to derive the key. The default key derivation choices can be overridden here. Key derivation is not intended to be used as a security method to make a complex key out of a short password. Instead you should use large random secret keys. :param secret_key: A specific secret key to derive from. Defaults to the last item in :attr:`secret_keys`. .. versionchanged:: 2.0 Added the ``secret_key`` parameter. """ if secret_key is None: secret_key = self.secret_keys[-1] else: secret_key = want_bytes(secret_key) if self.key_derivation == "concat": return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) elif self.key_derivation == "django-concat": return t.cast( bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() ) elif self.key_derivation == "hmac": mac = hmac.new(secret_key, digestmod=self.digest_method) mac.update(self.salt) return mac.digest() elif self.key_derivation == "none": return secret_key else: raise TypeError("Unknown key derivation method") def get_signature(self, value: str | bytes) -> bytes: """Returns the signature for the given value.""" value = want_bytes(value) key = self.derive_key() sig = self.algorithm.get_signature(key, value) return base64_encode(sig) def sign(self, value: str | bytes) -> bytes: """Signs the given string.""" value = want_bytes(value) return value + self.sep + self.get_signature(value) def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: """Verifies the signature for the given value.""" try: sig = base64_decode(sig) except Exception: return False value = want_bytes(value) for secret_key in reversed(self.secret_keys): key = self.derive_key(secret_key) if self.algorithm.verify_signature(key, value, sig): return True return False def unsign(self, signed_value: str | bytes) -> bytes: """Unsigns the given string.""" signed_value = want_bytes(signed_value) if self.sep not in signed_value: raise BadSignature(f"No {self.sep!r} found in value") value, sig = signed_value.rsplit(self.sep, 1) if self.verify_signature(value, sig): return value raise BadSignature(f"Signature {sig!r} does not match", payload=value) def validate(self, signed_value: str | bytes) -> bool: """Only validates the given signed value. Returns ``True`` if the signature exists and is valid. """ try: self.unsign(signed_value) return True except BadSignature: return False itsdangerous-2.2.0/src/itsdangerous/timed.py000066400000000000000000000176231460756553000212200ustar00rootroot00000000000000from __future__ import annotations import collections.abc as cabc import time import typing as t from datetime import datetime from datetime import timezone from .encoding import base64_decode from .encoding import base64_encode from .encoding import bytes_to_int from .encoding import int_to_bytes from .encoding import want_bytes from .exc import BadSignature from .exc import BadTimeSignature from .exc import SignatureExpired from .serializer import _TSerialized from .serializer import Serializer from .signer import Signer class TimestampSigner(Signer): """Works like the regular :class:`.Signer` but also records the time of the signing and can be used to expire signatures. The :meth:`unsign` method can raise :exc:`.SignatureExpired` if the unsigning failed because the signature is expired. """ def get_timestamp(self) -> int: """Returns the current timestamp. The function must return an integer. """ return int(time.time()) def timestamp_to_datetime(self, ts: int) -> datetime: """Convert the timestamp from :meth:`get_timestamp` into an aware :class`datetime.datetime` in UTC. .. versionchanged:: 2.0 The timestamp is returned as a timezone-aware ``datetime`` in UTC rather than a naive ``datetime`` assumed to be UTC. """ return datetime.fromtimestamp(ts, tz=timezone.utc) def sign(self, value: str | bytes) -> bytes: """Signs the given string and also attaches time information.""" value = want_bytes(value) timestamp = base64_encode(int_to_bytes(self.get_timestamp())) sep = want_bytes(self.sep) value = value + sep + timestamp return value + sep + self.get_signature(value) # Ignore overlapping signatures check, return_timestamp is the only # parameter that affects the return type. @t.overload def unsign( # type: ignore[overload-overlap] self, signed_value: str | bytes, max_age: int | None = None, return_timestamp: t.Literal[False] = False, ) -> bytes: ... @t.overload def unsign( self, signed_value: str | bytes, max_age: int | None = None, return_timestamp: t.Literal[True] = True, ) -> tuple[bytes, datetime]: ... def unsign( self, signed_value: str | bytes, max_age: int | None = None, return_timestamp: bool = False, ) -> tuple[bytes, datetime] | bytes: """Works like the regular :meth:`.Signer.unsign` but can also validate the time. See the base docstring of the class for the general behavior. If ``return_timestamp`` is ``True`` the timestamp of the signature will be returned as an aware :class:`datetime.datetime` object in UTC. .. versionchanged:: 2.0 The timestamp is returned as a timezone-aware ``datetime`` in UTC rather than a naive ``datetime`` assumed to be UTC. """ try: result = super().unsign(signed_value) sig_error = None except BadSignature as e: sig_error = e result = e.payload or b"" sep = want_bytes(self.sep) # If there is no timestamp in the result there is something # seriously wrong. In case there was a signature error, we raise # that one directly, otherwise we have a weird situation in # which we shouldn't have come except someone uses a time-based # serializer on non-timestamp data, so catch that. if sep not in result: if sig_error: raise sig_error raise BadTimeSignature("timestamp missing", payload=result) value, ts_bytes = result.rsplit(sep, 1) ts_int: int | None = None ts_dt: datetime | None = None try: ts_int = bytes_to_int(base64_decode(ts_bytes)) except Exception: pass # Signature is *not* okay. Raise a proper error now that we have # split the value and the timestamp. if sig_error is not None: if ts_int is not None: try: ts_dt = self.timestamp_to_datetime(ts_int) except (ValueError, OSError, OverflowError) as exc: # Windows raises OSError # 32-bit raises OverflowError raise BadTimeSignature( "Malformed timestamp", payload=value ) from exc raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt) # Signature was okay but the timestamp is actually not there or # malformed. Should not happen, but we handle it anyway. if ts_int is None: raise BadTimeSignature("Malformed timestamp", payload=value) # Check timestamp is not older than max_age if max_age is not None: age = self.get_timestamp() - ts_int if age > max_age: raise SignatureExpired( f"Signature age {age} > {max_age} seconds", payload=value, date_signed=self.timestamp_to_datetime(ts_int), ) if age < 0: raise SignatureExpired( f"Signature age {age} < 0 seconds", payload=value, date_signed=self.timestamp_to_datetime(ts_int), ) if return_timestamp: return value, self.timestamp_to_datetime(ts_int) return value def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: """Only validates the given signed value. Returns ``True`` if the signature exists and is valid.""" try: self.unsign(signed_value, max_age=max_age) return True except BadSignature: return False class TimedSerializer(Serializer[_TSerialized]): """Uses :class:`TimestampSigner` instead of the default :class:`.Signer`. """ default_signer: type[TimestampSigner] = TimestampSigner def iter_unsigners( self, salt: str | bytes | None = None ) -> cabc.Iterator[TimestampSigner]: return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) # TODO: Signature is incompatible because parameters were added # before salt. def loads( # type: ignore[override] self, s: str | bytes, max_age: int | None = None, return_timestamp: bool = False, salt: str | bytes | None = None, ) -> t.Any: """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the signature validation fails. If a ``max_age`` is provided it will ensure the signature is not older than that time in seconds. In case the signature is outdated, :exc:`.SignatureExpired` is raised. All arguments are forwarded to the signer's :meth:`~TimestampSigner.unsign` method. """ s = want_bytes(s) last_exception = None for signer in self.iter_unsigners(salt): try: base64d, timestamp = signer.unsign( s, max_age=max_age, return_timestamp=True ) payload = self.load_payload(base64d) if return_timestamp: return payload, timestamp return payload except SignatureExpired: # The signature was unsigned successfully but was # expired. Do not try the next signer. raise except BadSignature as err: last_exception = err raise t.cast(BadSignature, last_exception) def loads_unsafe( # type: ignore[override] self, s: str | bytes, max_age: int | None = None, salt: str | bytes | None = None, ) -> tuple[bool, t.Any]: return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) itsdangerous-2.2.0/src/itsdangerous/url_safe.py000066400000000000000000000047111460756553000217100ustar00rootroot00000000000000from __future__ import annotations import typing as t import zlib from ._json import _CompactJSON from .encoding import base64_decode from .encoding import base64_encode from .exc import BadPayload from .serializer import _PDataSerializer from .serializer import Serializer from .timed import TimedSerializer class URLSafeSerializerMixin(Serializer[str]): """Mixed in with a regular serializer it will attempt to zlib compress the string to make it shorter if necessary. It will also base64 encode the string so that it can safely be placed in a URL. """ default_serializer: _PDataSerializer[str] = _CompactJSON def load_payload( self, payload: bytes, *args: t.Any, serializer: t.Any | None = None, **kwargs: t.Any, ) -> t.Any: decompress = False if payload.startswith(b"."): payload = payload[1:] decompress = True try: json = base64_decode(payload) except Exception as e: raise BadPayload( "Could not base64 decode the payload because of an exception", original_error=e, ) from e if decompress: try: json = zlib.decompress(json) except Exception as e: raise BadPayload( "Could not zlib decompress the payload before decoding the payload", original_error=e, ) from e return super().load_payload(json, *args, **kwargs) def dump_payload(self, obj: t.Any) -> bytes: json = super().dump_payload(obj) is_compressed = False compressed = zlib.compress(json) if len(compressed) < (len(json) - 1): json = compressed is_compressed = True base64d = base64_encode(json) if is_compressed: base64d = b"." + base64d return base64d class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): """Works like :class:`.Serializer` but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """ class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): """Works like :class:`.TimedSerializer` but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """ itsdangerous-2.2.0/tests/000077500000000000000000000000001460756553000153775ustar00rootroot00000000000000itsdangerous-2.2.0/tests/test_itsdangerous/000077500000000000000000000000001460756553000211455ustar00rootroot00000000000000itsdangerous-2.2.0/tests/test_itsdangerous/__init__.py000066400000000000000000000000001460756553000232440ustar00rootroot00000000000000itsdangerous-2.2.0/tests/test_itsdangerous/test_encoding.py000066400000000000000000000017541460756553000243530ustar00rootroot00000000000000import pytest from itsdangerous.encoding import base64_decode from itsdangerous.encoding import base64_encode from itsdangerous.encoding import bytes_to_int from itsdangerous.encoding import int_to_bytes from itsdangerous.encoding import want_bytes from itsdangerous.exc import BadData @pytest.mark.parametrize("value", ("mañana", b"tomorrow")) def test_want_bytes(value): out = want_bytes(value) assert isinstance(out, bytes) @pytest.mark.parametrize("value", ("無限", b"infinite")) def test_base64(value): enc = base64_encode(value) assert isinstance(enc, bytes) dec = base64_decode(enc) assert dec == want_bytes(value) def test_base64_bad(): with pytest.raises(BadData): base64_decode("12345") @pytest.mark.parametrize( ("value", "expect"), ((0, b""), (192, b"\xc0"), (18446744073709551615, b"\xff" * 8)) ) def test_int_bytes(value, expect): enc = int_to_bytes(value) assert enc == expect dec = bytes_to_int(enc) assert dec == value itsdangerous-2.2.0/tests/test_itsdangerous/test_serializer.py000066400000000000000000000153171460756553000247360ustar00rootroot00000000000000import hashlib import pickle from functools import partial from io import BytesIO from io import StringIO from typing import Any from typing import cast from typing import IO from typing import overload from typing import Union import pytest from itsdangerous.exc import BadPayload from itsdangerous.exc import BadSignature from itsdangerous.serializer import Serializer from itsdangerous.signer import _lazy_sha1 from itsdangerous.signer import Signer @overload def coerce_str(ref: str, s: str) -> str: ... @overload def coerce_str(ref: bytes, s: str) -> bytes: ... def coerce_str(ref: Union[str, bytes], s: str) -> Union[str, bytes]: if isinstance(ref, bytes): return s.encode("utf8") return s class TestSerializer: @pytest.fixture(params=(Serializer, partial(Serializer, serializer=pickle))) def serializer_factory(self, request): return partial(request.param, secret_key="secret_key") @pytest.fixture() def serializer(self, serializer_factory): return serializer_factory() @pytest.fixture() def value(self): return {"id": 42} @pytest.mark.parametrize( "value", (None, True, "str", "text", [1, 2, 3], {"id": 42}) ) def test_serializer(self, serializer: Serializer, value: Any): assert serializer.loads(serializer.dumps(value)) == value @pytest.mark.parametrize( "transform", ( lambda s: s.upper(), lambda s: s + coerce_str(s, "a"), lambda s: coerce_str(s, "a") + s[1:], lambda s: s.replace(coerce_str(s, "."), coerce_str(s, "")), ), ) def test_changed_value(self, serializer: Serializer, value: Any, transform): signed = serializer.dumps(value) assert serializer.loads(signed) == value changed = transform(signed) with pytest.raises(BadSignature): serializer.loads(changed) def test_bad_signature_exception(self, serializer: Serializer, value: Any): bad_signed = serializer.dumps(value)[:-1] with pytest.raises(BadSignature) as exc_info: serializer.loads(bad_signed) payload = cast(bytes, exc_info.value.payload) assert serializer.load_payload(payload) == value def test_bad_payload_exception(self, serializer: Serializer, value: Any): original = serializer.dumps(value) payload = original.rsplit(coerce_str(original, "."), 1)[0] # type: ignore bad = serializer.make_signer().sign(payload[:-1]) with pytest.raises(BadPayload) as exc_info: serializer.loads(bad) assert exc_info.value.original_error is not None def test_loads_unsafe(self, serializer: Serializer, value: Any): signed = serializer.dumps(value) assert serializer.loads_unsafe(signed) == (True, value) bad_signed = signed[:-1] assert serializer.loads_unsafe(bad_signed) == (False, value) payload = signed.rsplit(coerce_str(signed, "."), 1)[0] # type: ignore bad_payload = serializer.make_signer().sign(payload[:-1])[:-1] assert serializer.loads_unsafe(bad_payload) == (False, None) class BadUnsign(serializer.signer): # type: ignore def unsign(self, signed_value, *args, **kwargs): try: return super().unsign(signed_value, *args, **kwargs) except BadSignature as e: e.payload = None raise serializer.signer = BadUnsign assert serializer.loads_unsafe(bad_signed) == (False, None) def test_file(self, serializer: Serializer, value: Any): f = cast( IO, BytesIO() if isinstance(serializer.dumps(value), bytes) else StringIO() ) serializer.dump(value, f) f.seek(0) assert serializer.load(f) == value f.seek(0) assert serializer.load_unsafe(f) == (True, value) def test_alt_salt(self, serializer: Serializer, value: Any): signed = serializer.dumps(value, salt="other") with pytest.raises(BadSignature): serializer.loads(signed) assert serializer.loads(signed, salt="other") == value def test_signer_cls(self, serializer_factory, serializer: Serializer, value: Any): class Other(serializer.signer): # type: ignore default_key_derivation = "hmac" other = serializer_factory(signer=Other) assert other.loads(other.dumps(value)) == value assert other.dumps(value) != serializer.dumps(value) def test_signer_kwargs( self, serializer_factory, serializer: Serializer, value: Any ): other = serializer_factory(signer_kwargs={"key_derivation": "hmac"}) assert other.loads(other.dumps(value)) == value assert other.dumps("value") != serializer.dumps("value") def test_serializer_kwargs(self, serializer_factory): serializer = serializer_factory(serializer_kwargs={"skipkeys": True}) try: serializer.serializer.dumps(None, skipkeys=True) except TypeError: return assert serializer.loads(serializer.dumps({(): 1})) == {} def test_fallback_signers(self, serializer_factory, value: Any): serializer = serializer_factory(signer_kwargs={"digest_method": hashlib.sha256}) signed = serializer.dumps(value) fallback_serializer = serializer_factory( signer_kwargs={"digest_method": hashlib.sha1}, fallback_signers=[{"digest_method": hashlib.sha256}], ) assert fallback_serializer.loads(signed) == value def test_iter_unsigners(self, serializer: Serializer, serializer_factory): class Signer256(serializer.signer): # type: ignore default_digest_method = hashlib.sha256 serializer = serializer_factory( secret_key="secret_key", fallback_signers=[ {"digest_method": hashlib.sha256}, (Signer, {"digest_method": hashlib.sha256}), Signer256, ], ) unsigners = serializer.iter_unsigners() assert next(unsigners).digest_method == _lazy_sha1 for signer in unsigners: assert signer.digest_method == hashlib.sha256 def test_digests(): factory = partial(Serializer, secret_key="dev key", salt="dev salt") default_value = factory(signer_kwargs={}).dumps([42]) sha1_value = factory(signer_kwargs={"digest_method": hashlib.sha1}).dumps([42]) sha512_value = factory(signer_kwargs={"digest_method": hashlib.sha512}).dumps([42]) assert default_value == sha1_value assert sha1_value == "[42].-9cNi0CxsSB3hZPNCe9a2eEs1ZM" assert sha512_value == ( "[42].MKCz_0nXQqv7wKpfHZcRtJRmpT2T5uvs9YQsJEhJimqxc" "9bCLxG31QzS5uC8OVBI1i6jyOLAFNoKaF5ckO9L5Q" ) itsdangerous-2.2.0/tests/test_itsdangerous/test_signer.py000066400000000000000000000065531460756553000240560ustar00rootroot00000000000000import hashlib from functools import partial import pytest from itsdangerous.exc import BadSignature from itsdangerous.signer import HMACAlgorithm from itsdangerous.signer import NoneAlgorithm from itsdangerous.signer import Signer from itsdangerous.signer import SigningAlgorithm class _ReverseAlgorithm(SigningAlgorithm): def get_signature(self, key, value): return (key + value)[::-1] class TestSigner: @pytest.fixture() def signer_factory(self): return partial(Signer, secret_key="secret-key") @pytest.fixture() def signer(self, signer_factory): return signer_factory() def test_signer(self, signer): signed = signer.sign("my string") assert isinstance(signed, bytes) assert signer.validate(signed) out = signer.unsign(signed) assert out == b"my string" def test_no_separator(self, signer): signed = signer.sign("my string") signed = signed.replace(signer.sep, b"*", 1) assert not signer.validate(signed) with pytest.raises(BadSignature): signer.unsign(signed) def test_broken_signature(self, signer): signed = signer.sign("b") bad_signed = signed[:-1] bad_sig = bad_signed.rsplit(b".", 1)[1] assert not signer.verify_signature(b"b", bad_sig) with pytest.raises(BadSignature) as exc_info: signer.unsign(bad_signed) assert exc_info.value.payload == b"b" def test_changed_value(self, signer): signed = signer.sign("my string") signed = signed.replace(b"my", b"other", 1) assert not signer.validate(signed) with pytest.raises(BadSignature): signer.unsign(signed) def test_invalid_separator(self, signer_factory): with pytest.raises(ValueError) as exc_info: signer_factory(sep="-") assert "separator cannot be used" in str(exc_info.value) @pytest.mark.parametrize( "key_derivation", ("concat", "django-concat", "hmac", "none") ) def test_key_derivation(self, signer_factory, key_derivation): signer = signer_factory(key_derivation=key_derivation) assert signer.unsign(signer.sign("value")) == b"value" def test_invalid_key_derivation(self, signer_factory): signer = signer_factory(key_derivation="invalid") with pytest.raises(TypeError): signer.derive_key() def test_digest_method(self, signer_factory): signer = signer_factory(digest_method=hashlib.md5) assert signer.unsign(signer.sign("value")) == b"value" @pytest.mark.parametrize( "algorithm", (None, NoneAlgorithm(), HMACAlgorithm(), _ReverseAlgorithm()) ) def test_algorithm(self, signer_factory, algorithm): signer = signer_factory(algorithm=algorithm) assert signer.unsign(signer.sign("value")) == b"value" if algorithm is None: assert signer.algorithm.digest_method == signer.digest_method def test_secret_keys(self): signer = Signer("a") signed = signer.sign("my string") assert isinstance(signed, bytes) signer = Signer(["a", "b"]) assert signer.validate(signed) out = signer.unsign(signed) assert out == b"my string" def test_abstract_algorithm(): alg = SigningAlgorithm() with pytest.raises(NotImplementedError): alg.get_signature(b"a", b"b") itsdangerous-2.2.0/tests/test_itsdangerous/test_timed.py000066400000000000000000000073371460756553000236720ustar00rootroot00000000000000from datetime import datetime from datetime import timedelta from datetime import timezone from functools import partial import pytest from freezegun import freeze_time from itsdangerous.exc import BadTimeSignature from itsdangerous.exc import SignatureExpired from itsdangerous.signer import Signer from itsdangerous.timed import TimedSerializer from itsdangerous.timed import TimestampSigner from test_itsdangerous.test_serializer import TestSerializer from test_itsdangerous.test_signer import TestSigner class FreezeMixin: @pytest.fixture() def ts(self): return datetime(2011, 6, 24, 0, 9, 5, tzinfo=timezone.utc) @pytest.fixture(autouse=True) def freeze(self, ts): with freeze_time(ts) as ft: yield ft class TestTimestampSigner(FreezeMixin, TestSigner): @pytest.fixture() def signer_factory(self): return partial(TimestampSigner, secret_key="secret-key") def test_max_age(self, signer, ts, freeze): signed = signer.sign("value") freeze.tick() assert signer.unsign(signed, max_age=10) == b"value" freeze.tick(timedelta(seconds=10)) with pytest.raises(SignatureExpired) as exc_info: signer.unsign(signed, max_age=10) assert exc_info.value.date_signed == ts def test_return_timestamp(self, signer, ts): signed = signer.sign("value") assert signer.unsign(signed, return_timestamp=True) == (b"value", ts) def test_timestamp_missing(self, signer): other = Signer("secret-key") signed = other.sign("value") with pytest.raises(BadTimeSignature) as exc_info: signer.unsign(signed) assert "missing" in str(exc_info.value) assert exc_info.value.date_signed is None def test_malformed_timestamp(self, signer): other = Signer("secret-key") signed = other.sign(b"value.____________") with pytest.raises(BadTimeSignature) as exc_info: signer.unsign(signed) assert "Malformed" in str(exc_info.value) assert exc_info.value.date_signed is None def test_malformed_future_timestamp(self, signer): signed = b"value.TgPVoaGhoQ.AGBfQ6G6cr07byTRt0zAdPljHOY" with pytest.raises(BadTimeSignature) as exc_info: signer.unsign(signed) assert "Malformed" in str(exc_info.value) assert exc_info.value.date_signed is None def test_future_age(self, signer): signed = signer.sign("value") with freeze_time("1971-05-31"): with pytest.raises(SignatureExpired) as exc_info: signer.unsign(signed, max_age=10) assert isinstance(exc_info.value.date_signed, datetime) def test_sig_error_date_signed(self, signer): signed = signer.sign("my string").replace(b"my", b"other", 1) with pytest.raises(BadTimeSignature) as exc_info: signer.unsign(signed) assert isinstance(exc_info.value.date_signed, datetime) class TestTimedSerializer(FreezeMixin, TestSerializer): @pytest.fixture() def serializer_factory(self): return partial(TimedSerializer, secret_key="secret_key") def test_max_age(self, serializer, value, ts, freeze): signed = serializer.dumps(value) freeze.tick() assert serializer.loads(signed, max_age=10) == value freeze.tick(timedelta(seconds=10)) with pytest.raises(SignatureExpired) as exc_info: serializer.loads(signed, max_age=10) assert exc_info.value.date_signed == ts assert serializer.load_payload(exc_info.value.payload) == value def test_return_payload(self, serializer, value, ts): signed = serializer.dumps(value) assert serializer.loads(signed, return_timestamp=True) == (value, ts) itsdangerous-2.2.0/tests/test_itsdangerous/test_url_safe.py000066400000000000000000000014311460756553000243550ustar00rootroot00000000000000from functools import partial import pytest from itsdangerous.url_safe import URLSafeSerializer from itsdangerous.url_safe import URLSafeTimedSerializer from test_itsdangerous.test_serializer import TestSerializer from test_itsdangerous.test_timed import TestTimedSerializer class TestURLSafeSerializer(TestSerializer): @pytest.fixture() def serializer_factory(self): return partial(URLSafeSerializer, secret_key="secret-key") @pytest.fixture(params=({"id": 42}, pytest.param("a" * 1000, id="zlib"))) def value(self, request): return request.param class TestURLSafeTimedSerializer(TestURLSafeSerializer, TestTimedSerializer): @pytest.fixture() def serializer_factory(self): return partial(URLSafeTimedSerializer, secret_key="secret-key") itsdangerous-2.2.0/tox.ini000066400000000000000000000017001460756553000155460ustar00rootroot00000000000000[tox] envlist = py3{12,11,10,9,8} pypy310 style typing docs skip_missing_interpreters = true [testenv] package = wheel wheel_build_env = .pkg constrain_package_deps = true use_frozen_constraints = true deps = -r requirements/tests.txt commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs} [testenv:style] deps = pre-commit skip_install = true commands = pre-commit run --all-files [testenv:typing] deps = -r requirements/typing.txt commands = mypy pyright pyright --verifytypes itsdangerous --ignoreexternal [testenv:docs] deps = -r requirements/docs.txt commands = sphinx-build -E -W -b dirhtml docs docs/_build/dirhtml [testenv:update-requirements] deps = pip-tools pre-commit skip_install = true change_dir = requirements commands = pre-commit autoupdate -j4 pip-compile -U build.in pip-compile -U docs.in pip-compile -U tests.in pip-compile -U typing.in pip-compile -U dev.in