pax_global_header00006660000000000000000000000064137665041370014526gustar00rootroot0000000000000052 comment=d8aa5e0e152b80cc943e0d639900e5ccefe533dc RomeoDespres-pkce-d8aa5e0/000077500000000000000000000000001376650413700155465ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/.github/000077500000000000000000000000001376650413700171065ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/.github/workflows/000077500000000000000000000000001376650413700211435ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/.github/workflows/python-test.yml000066400000000000000000000016441376650413700241710ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python Unittests on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install mypy pytest pip install . - name: Test with pytest run: | pytest - name: Test with mypy run: | mypy . --ignore-missing-imports --strict RomeoDespres-pkce-d8aa5e0/.gitignore000066400000000000000000000000421376650413700175320ustar00rootroot00000000000000__pycache__ dist build *.egg-info RomeoDespres-pkce-d8aa5e0/LICENSE000066400000000000000000000020601376650413700165510ustar00rootroot00000000000000MIT License Copyright (c) 2020 Roméo Després Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RomeoDespres-pkce-d8aa5e0/README.md000066400000000000000000000010151376650413700170220ustar00rootroot00000000000000# pkce (Proof Key for Code Exchange) Simple Python module to generate PKCE code verifier and code challenge. ## Installation ```bash pip install pkce ``` ## Usage ```python >>> import pkce >>> code_verifier, code_challenge = pkce.generate_pkce_pair() ``` ```python >>> import pkce >>> code_verifier = pkce.generate_code_verifier(length=128) >>> code_challenge = pkce.get_code_challenge(code_verifier) ``` ## Additional information Spec for the PKCE protocol can be found [here](https://tools.ietf.org/html/rfc7636). RomeoDespres-pkce-d8aa5e0/pkce/000077500000000000000000000000001376650413700164705ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/pkce/__init__.py000066400000000000000000000051501376650413700206020ustar00rootroot00000000000000"""Simple module to generate PKCE code verifier and code challenge. Examples -------- >>> import pkce >>> code_verifier, code_challenge = pkce.generate_pkce_pair() >>> import pkce >>> code_verifier = pkce.generate_code_verifier(length=128) >>> code_challenge = pkce.get_code_challenge(code_verifier) """ import secrets import hashlib import base64 from typing import Tuple def generate_code_verifier(length: int = 128) -> str: """Return a random PKCE-compliant code verifier. Parameters ---------- length : int Code verifier length. Must verify `43 <= length <= 128`. Returns ------- code_verifier : str Code verifier. Raises ------ ValueError When `43 <= length <= 128` is not verified. """ if not 43 <= length <= 128: msg = 'Parameter `length` must verify `43 <= length <= 128`.' raise ValueError(msg) code_verifier = secrets.token_urlsafe(96)[:length] return code_verifier def generate_pkce_pair(code_verifier_length: int = 128) -> Tuple[str, str]: """Return random PKCE-compliant code verifier and code challenge. Parameters ---------- code_verifier_length : int Code verifier length. Must verify `43 <= code_verifier_length <= 128`. Returns ------- code_verifier : str code_challenge : str Raises ------ ValueError When `43 <= code_verifier_length <= 128` is not verified. """ if not 43 <= code_verifier_length <= 128: msg = 'Parameter `code_verifier_length` must verify ' msg += '`43 <= code_verifier_length <= 128`.' raise ValueError(msg) code_verifier = generate_code_verifier(code_verifier_length) code_challenge = get_code_challenge(code_verifier) return code_verifier, code_challenge def get_code_challenge(code_verifier: str) -> str: """Return the PKCE-compliant code challenge for a given verifier. Parameters ---------- code_verifier : str Code verifier. Must verify `43 <= len(code_verifier) <= 128`. Returns ------- code_challenge : str Code challenge that corresponds to the input code verifier. Raises ------ ValueError When `43 <= len(code_verifier) <= 128` is not verified. """ if not 43 <= len(code_verifier) <= 128: msg = 'Parameter `code_verifier` must verify ' msg += '`43 <= len(code_verifier) <= 128`.' raise ValueError(msg) hashed = hashlib.sha256(code_verifier.encode('ascii')).digest() encoded = base64.urlsafe_b64encode(hashed) code_challenge = encoded.decode('ascii')[:-1] return code_challenge RomeoDespres-pkce-d8aa5e0/pkce/py.typed000066400000000000000000000000001376650413700201550ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/setup.py000066400000000000000000000013151376650413700172600ustar00rootroot00000000000000import setuptools with open('README.md', encoding='utf-8') as f: long_description = f.read() setuptools.setup( name='pkce', version='1.0.2', author='Roméo Després', author_email='despres.romeo@gmail.com', description='PKCE Pyhton generator.', long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/RomeoDespres/pkce', package_data={'pkce': ['py.typed']}, packages=['pkce'], classifiers=[ 'Programming Language :: Python :: 3', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Typing :: Typed', ], python_requires='>=3', zip_safe=False ) RomeoDespres-pkce-d8aa5e0/tests/000077500000000000000000000000001376650413700167105ustar00rootroot00000000000000RomeoDespres-pkce-d8aa5e0/tests/test_pkce.py000066400000000000000000000024361376650413700212500ustar00rootroot00000000000000import random import string import pkce import pytest CORRECT_LENGTHS = range(43, 129) INCORRECT_LENGTHS = list(range(43)) + list(range(129, 200)) @pytest.mark.parametrize('length', CORRECT_LENGTHS) def test_generate_code_verifier(length): verifier1 = pkce.generate_code_verifier(length) verifier2 = pkce.generate_code_verifier(length) assert len(verifier1) == length assert len(verifier2) == length assert verifier1 != verifier2 @pytest.mark.parametrize('length', INCORRECT_LENGTHS) def test_generate_code_verifier_too_short_or_too_long(length): with pytest.raises(ValueError): pkce.generate_code_verifier(length) @pytest.mark.parametrize('length', INCORRECT_LENGTHS) def test_generate_pkce_pair_too_short_or_too_long(length): with pytest.raises(ValueError): pkce.generate_pkce_pair(length) def test_get_code_challenge(): abc = 'abcdefghijklmnopqrstuvwxyz' challenge = pkce.get_code_challenge(abc + abc.upper()) assert challenge == 'OWQpS2ZGE3mNGkd-uK0CEYtI0MVzjEJ2EyAvLtEjtfE' @pytest.mark.parametrize('length', INCORRECT_LENGTHS) def test_get_code_challenge_too_short_or_too_long(length): verifier = ''.join(random.choices(string.ascii_letters, k=length)) with pytest.raises(ValueError): pkce.get_code_challenge(verifier)