Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/remove-pydantic-dependency #195

Merged
merged 47 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4432d9e
Remove Pydantic as dependency
MasterKale Jan 5, 2024
c6d5fa1
Expand upon VS Code settings
MasterKale Jan 5, 2024
cf7a3e9
Refactor structs into dataclasses
MasterKale Jan 5, 2024
cbed385
Remove references to Pydantic in code
MasterKale Jan 5, 2024
b134c58
Run Black over everything
MasterKale Jan 5, 2024
fc60fe8
Rewrite auth credential parsing
MasterKale Jan 5, 2024
b7d7631
Restore typing_extensions for mypy
MasterKale Jan 5, 2024
5b43364
Fix mypy config
MasterKale Jan 5, 2024
f5dd32c
Remove unused type
MasterKale Jan 5, 2024
8bf3c71
Fix incorrect instance check
MasterKale Jan 5, 2024
bd28d8a
Don't initialize an Optional field
MasterKale Jan 5, 2024
9535b9c
Update VS Code settings
MasterKale Jan 5, 2024
2c3357e
Tweak exception messages
MasterKale Jan 5, 2024
5b6af12
Appease mypy with this one weird trick
MasterKale Jan 5, 2024
e9cbfc9
Use InvalidJSONStructure in parse_client_data_json
MasterKale Jan 5, 2024
9dd6d53
Create encode_cbor to abstract cbor2 use
MasterKale Jan 5, 2024
b8ff3b6
Abstract more cbor2 use
MasterKale Jan 5, 2024
61b6044
Add comment
MasterKale Jan 5, 2024
dc3bb68
Update tests
MasterKale Jan 5, 2024
d58f95e
Parse registration responses
MasterKale Jan 5, 2024
ad5be88
Refactor options_to_json
MasterKale Jan 5, 2024
8ef29d3
Create byteslike_to_bytes for bytes shenanigans
MasterKale Jan 5, 2024
0bb1201
Handle things like memoryviews
MasterKale Jan 5, 2024
5c86c3d
Remove print statement
MasterKale Jan 5, 2024
dbb22a9
Remove Pydantic from examples
MasterKale Jan 5, 2024
d403696
Allow user.id and user_handle to be str
MasterKale Jan 5, 2024
48c94b1
Don't assume base64url on user_handle
MasterKale Jan 5, 2024
9db31a6
Update token binding test
MasterKale Jan 5, 2024
05ac887
Refactor check for empty attStmt on None
MasterKale Jan 5, 2024
d5ea72b
Refactor SafetyNet JWS parsing
MasterKale Jan 5, 2024
c458d92
Remove Pydantic checks from CI
MasterKale Jan 5, 2024
3aa5381
Replace fancy single-quote
MasterKale Jan 8, 2024
0f0ea3a
Properly raise on non-dict JSON parse
MasterKale Jan 8, 2024
21433cb
Raise credential type verification up
MasterKale Jan 8, 2024
2a2ac63
Tweak error messages
MasterKale Jan 8, 2024
defb54c
Add registration response parsing tests
MasterKale Jan 8, 2024
c6f30b7
Clean up unused imports
MasterKale Jan 8, 2024
043aa42
Add tests for parsing authentication response
MasterKale Jan 8, 2024
c3c53dd
Add comment about user_handle
MasterKale Jan 8, 2024
4bf9cf5
Remove json_loads_base64url_to_bytes
MasterKale Jan 8, 2024
8f971be
Raise when required string values are empty
MasterKale Jan 8, 2024
963ba2e
Revert PublicKeyCredentialUserEntity id Union
MasterKale Jan 8, 2024
6c0c544
Revert typing on user_handle
MasterKale Jan 8, 2024
f65c30d
Add .git-blame-ignore-revs
MasterKale Jan 8, 2024
64a0579
Run Black on setup.py
MasterKale Jan 8, 2024
6fd8ee6
Update to the latest cryptography and pyOpenSSL
MasterKale Jan 8, 2024
939c486
Ignore another formatting commit
MasterKale Jan 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# "Run Black over everything"
b134c58a394353e02c4f40808bf104f51578e7df
# "Run Black on setup.py"
64a0579be773f2ac783f8b50f1046fccc09389a8
2 changes: 0 additions & 2 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ jobs:
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
pydantic-version: ['>=1.0,<2.0', '>=2.0,<3.0']

steps:
- uses: actions/checkout@v3
Expand All @@ -28,7 +27,6 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install 'pydantic${{ matrix.pydantic-version }}'
- name: Test with unittest
run: |
python -m unittest
Expand Down
16 changes: 14 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
{
"black-formatter.args": [
"--line-length", "99"
],
"mypy-type-checker.path": ["venv/bin/mypy"],
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
}
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnPaste": false,
"editor.formatOnSaveMode": "file",
"editor.formatOnSave": true,
},
"python.analysis.typeCheckingMode": "basic",
"gitlens.advanced.blame.customArguments": [
"--ignore-revs-file",
".git-blame-ignore-revs"
]
}
6 changes: 1 addition & 5 deletions examples/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
base64url_to_bytes,
)
from webauthn.helpers.structs import (
PYDANTIC_V2,
PublicKeyCredentialDescriptor,
UserVerificationRequirement,
)
Expand Down Expand Up @@ -66,8 +65,5 @@
require_user_verification=True,
)
print("\n[Authentication Verification]")
if PYDANTIC_V2:
print(authentication_verification.model_dump_json(indent=2))
else:
print(authentication_verification.json(indent=2))
print(authentication_verification)
assert authentication_verification.new_sign_count == 1
10 changes: 3 additions & 7 deletions examples/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
)
from webauthn.helpers.cose import COSEAlgorithmIdentifier
from webauthn.helpers.structs import (
PYDANTIC_V2,
AttestationConveyancePreference,
AuthenticatorAttachment,
AuthenticatorSelectionCriteria,
Expand Down Expand Up @@ -63,11 +62,11 @@
"response": {
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBZ0mWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjRQAAAAAAAAAAAAAAAAAAAAAAAAAAACBmggo_UlC8p2tiPVtNQ8nZ5NSxst4WS_5fnElA2viTq6QBAwM5AQAgWQEA31dtHqc70D_h7XHQ6V_nBs3Tscu91kBL7FOw56_VFiaKYRH6Z4KLr4J0S12hFJ_3fBxpKfxyMfK66ZMeAVbOl_wemY4S5Xs4yHSWy21Xm_dgWhLJjZ9R1tjfV49kDPHB_ssdvP7wo3_NmoUPYMgK-edgZ_ehttp_I6hUUCnVaTvn_m76b2j9yEPReSwl-wlGsabYG6INUhTuhSOqG-UpVVQdNJVV7GmIPHCA2cQpJBDZBohT4MBGme_feUgm4sgqVCWzKk6CzIKIz5AIVnspLbu05SulAVnSTB3NxTwCLNJR_9v9oSkvphiNbmQBVQH1tV_psyi9HM1Jtj9VJVKMeyFDAQAB",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQ2VUV29nbWcwY2NodWlZdUZydjhEWFhkTVpTSVFSVlpKT2dhX3hheVZWRWNCajBDdzN5NzN5aEQ0RmtHU2UtUnJQNmhQSkpBSW0zTFZpZW40aFhFTGciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
"transports": ["internal"]
"transports": ["internal"],
},
"type": "public-key",
"clientExtensionResults": {},
"authenticatorAttachment": "platform"
"authenticatorAttachment": "platform",
},
expected_challenge=base64url_to_bytes(
"CeTWogmg0cchuiYuFrv8DXXdMZSIQRVZJOga_xayVVEcBj0Cw3y73yhD4FkGSe-RrP6hPJJAIm3LVien4hXELg"
Expand All @@ -78,10 +77,7 @@
)

print("\n[Registration Verification - None]")
if PYDANTIC_V2:
print(registration_verification.model_dump_json(indent=2))
else:
print(registration_verification.json(indent=2))
print(registration_verification)
assert registration_verification.credential_id == base64url_to_bytes(
"ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s"
)
5 changes: 0 additions & 5 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
[mypy]
plugins = pydantic.mypy

python_version = 3.8

[pydantic-mypy]
init_typed=True

[mypy-asn1crypto.*]
ignore_missing_imports = True

Expand Down
11 changes: 4 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
annotated-types==0.5.0
asn1crypto==1.4.0
black==21.9b0
cbor2==5.4.6
cbor2==5.5.0
cffi==1.15.0
click==8.0.3
cryptography==41.0.4
cryptography==41.0.7
mccabe==0.6.1
mypy==1.4.1
mypy-extensions==1.0.0
pathspec==0.9.0
platformdirs==2.4.0
pycodestyle==2.8.0
pycparser==2.20
pydantic==2.4.2
pydantic_core==2.10.1
pyflakes==2.4.0
pyOpenSSL==23.2.0
pyOpenSSL==23.3.0
regex==2021.10.8
six==1.16.0
toml==0.10.2
tomli==1.2.1
typing_extensions==4.7.1
typing_extensions==4.9.0
48 changes: 23 additions & 25 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,47 @@


def read(*parts):
with codecs.open(os.path.join(HERE, *parts), 'r') as fp:
with codecs.open(os.path.join(HERE, *parts), "r") as fp:
return fp.read()


def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError('Unable to find version string.')
raise RuntimeError("Unable to find version string.")


LONG_DESCRIPTION = read('README.md')
VERSION = find_version('webauthn', '__init__.py')
LONG_DESCRIPTION = read("README.md")
VERSION = find_version("webauthn", "__init__.py")


setup(
name='webauthn',
name="webauthn",
packages=find_packages(exclude=["tests"]),
include_package_data=True,
package_data={"webauthn": ["py.typed"]},
version=VERSION,
description='Pythonic WebAuthn',
description="Pythonic WebAuthn",
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
keywords='webauthn fido2',
author='Duo Labs',
author_email='labs@duo.com',
url='https://github.com/duo-labs/py_webauthn',
download_url='https://github.com/duo-labs/py_webauthn/archive/{}.tar.gz'.format(VERSION),
license='BSD',
long_description_content_type="text/markdown",
keywords="webauthn fido2",
author="Duo Labs",
author_email="labs@duo.com",
url="https://github.com/duo-labs/py_webauthn",
download_url="https://github.com/duo-labs/py_webauthn/archive/{}.tar.gz".format(VERSION),
license="BSD",
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3'
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
],
install_requires=[
'asn1crypto>=1.4.0',
'cbor2>=5.4.6',
'cryptography>=41.0.4',
'pydantic>=1.10.11',
'pyOpenSSL>=23.2.0',
]
"asn1crypto>=1.4.0",
"cbor2>=5.4.6",
"cryptography>=41.0.7",
"pyOpenSSL>=23.3.0",
],
)
13 changes: 0 additions & 13 deletions tests/test_bytes_subclass_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,3 @@ def base64url_to_memoryview(data: str) -> memoryview:
)

assert verification.new_sign_count == 7

def test_supports_strings_for_bytes(self) -> None:
"""
Preserve the ability to pass strings for `bytes` fields
"""
response = AuthenticatorAssertionResponse(
authenticator_data=bytes(),
client_data_json=bytes(),
signature=bytes(),
user_handle='some_user_handle_string' # type: ignore
)

self.assertEqual(response.user_handle, b'some_user_handle_string')
12 changes: 4 additions & 8 deletions tests/test_decode_credential_public_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ def test_decodes_ec2_public_key(self) -> None:
assert decoded.crv == 1
assert (
decoded.x
and bytes_to_base64url(decoded.x)
== "MMcEPFOpY_jJlmcBrnbgvq4-7CGKt5TBEPmxdjpTaDE"
and bytes_to_base64url(decoded.x) == "MMcEPFOpY_jJlmcBrnbgvq4-7CGKt5TBEPmxdjpTaDE"
)
assert (
decoded.y
and bytes_to_base64url(decoded.y)
== "xuwbECbDdNfTTegnc174oYdusZiMmJgct0yI_ulrJGI"
and bytes_to_base64url(decoded.y) == "xuwbECbDdNfTTegnc174oYdusZiMmJgct0yI_ulrJGI"
)

def test_decode_rsa_public_key(self) -> None:
Expand Down Expand Up @@ -62,11 +60,9 @@ def test_decode_uncompressed_ec2_public_key(self) -> None:
assert decoded.crv == 1
assert (
decoded.x
and bytes_to_base64url(decoded.x)
== "FrEpm55XKvkgIN-izKDHBF-VJ09Rw2F5mFOFcJ5MVM0"
and bytes_to_base64url(decoded.x) == "FrEpm55XKvkgIN-izKDHBF-VJ09Rw2F5mFOFcJ5MVM0"
)
assert (
decoded.y
and bytes_to_base64url(decoded.y)
== "o0EM9dj0V-xJ1JwpE2XZ_8NRIt5KVvr71Zl0rB8BWOs"
and bytes_to_base64url(decoded.y) == "o0EM9dj0V-xJ1JwpE2XZ_8NRIt5KVvr71Zl0rB8BWOs"
)
6 changes: 6 additions & 0 deletions tests/test_generate_authentication_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ def test_generates_options_with_custom_values(self) -> None:
PublicKeyCredentialDescriptor(id=b"12345"),
]
assert options.user_verification == UserVerificationRequirement.REQUIRED

def test_raises_on_empty_rp_id(self) -> None:
with self.assertRaisesRegex(ValueError, "rp_id"):
generate_authentication_options(
rp_id="",
)
44 changes: 38 additions & 6 deletions tests/test_generate_registration_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ def test_generates_options_with_custom_values(self) -> None:
timeout=120000,
)

assert options.rp == PublicKeyCredentialRpEntity(
id="example.com", name="Example Co"
)
assert options.rp == PublicKeyCredentialRpEntity(id="example.com", name="Example Co")
assert options.challenge == b"1234567890"
assert options.user == PublicKeyCredentialUserEntity(
id=b"ABAV6QWPBEY9WOTOA1A4",
Expand All @@ -80,12 +78,46 @@ def test_generates_options_with_custom_values(self) -> None:
alg=COSEAlgorithmIdentifier.ECDSA_SHA_512,
)
assert options.timeout == 120000
assert options.exclude_credentials == [
PublicKeyCredentialDescriptor(id=b"1234567890")
]
assert options.exclude_credentials == [PublicKeyCredentialDescriptor(id=b"1234567890")]
assert options.authenticator_selection == AuthenticatorSelectionCriteria(
authenticator_attachment=AuthenticatorAttachment.PLATFORM,
resident_key=ResidentKeyRequirement.REQUIRED,
require_resident_key=True,
)
assert options.attestation == AttestationConveyancePreference.DIRECT

def test_raises_on_empty_rp_id(self) -> None:
with self.assertRaisesRegex(ValueError, "rp_id"):
generate_registration_options(
rp_id="",
rp_name="Example Co",
user_id="blah",
user_name="blah",
)

def test_raises_on_empty_rp_name(self) -> None:
with self.assertRaisesRegex(ValueError, "rp_name"):
generate_registration_options(
rp_id="example.com",
rp_name="",
user_id="blah",
user_name="blah",
)

def test_raises_on_empty_user_id(self) -> None:
with self.assertRaisesRegex(ValueError, "user_id"):
generate_registration_options(
rp_id="example.com",
rp_name="Example Co",
user_id="",
user_name="blah",
)

def test_raises_on_empty_user_name(self) -> None:
with self.assertRaisesRegex(ValueError, "user_name"):
generate_registration_options(
rp_id="example.com",
rp_name="Example Co",
user_id="blah",
user_name="",
)
Loading