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 39 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
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
12 changes: 10 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"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"
}
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
7 changes: 2 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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
Expand All @@ -12,12 +11,10 @@ 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
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
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ def find_version(*file_paths):
'asn1crypto>=1.4.0',
'cbor2>=5.4.6',
'cryptography>=41.0.4',
'pydantic>=1.10.11',
'pyOpenSSL>=23.2.0',
]
)
4 changes: 2 additions & 2 deletions tests/test_bytes_subclass_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_supports_strings_for_bytes(self) -> None:
authenticator_data=bytes(),
client_data_json=bytes(),
signature=bytes(),
user_handle='some_user_handle_string' # type: ignore
user_handle="some_user_handle_string",
)

self.assertEqual(response.user_handle, b'some_user_handle_string')
self.assertEqual(response.user_handle, "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"
)
8 changes: 2 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,9 +78,7 @@ 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,
Expand Down
92 changes: 66 additions & 26 deletions tests/test_options_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
AuthenticatorTransport,
PublicKeyCredentialDescriptor,
ResidentKeyRequirement,
UserVerificationRequirement,
)
from webauthn import generate_registration_options
from webauthn import generate_registration_options, generate_authentication_options


class TestWebAuthnOptionsToJSON(TestCase):
def test_converts_options_to_JSON(self) -> None:
maxDiff = None

def test_converts_registration_options_to_JSON(self) -> None:
options = generate_registration_options(
rp_id="example.com",
rp_name="Example Co",
Expand All @@ -37,25 +40,28 @@ def test_converts_options_to_JSON(self) -> None:

output = options_to_json(options)

assert json.loads(output) == {
"rp": {"name": "Example Co", "id": "example.com"},
"user": {
"id": "QUJBVjZRV1BCRVk5V09UT0ExQTQ",
"name": "lee",
"displayName": "Lee",
},
"challenge": "MTIzNDU2Nzg5MA",
"pubKeyCredParams": [{"type": "public-key", "alg": -36}],
"timeout": 120000,
"excludeCredentials": [{"type": "public-key", "id": "MTIzNDU2Nzg5MA"}],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"residentKey": "required",
"requireResidentKey": True,
"userVerification": "preferred",
self.assertEqual(
json.loads(output),
{
"rp": {"name": "Example Co", "id": "example.com"},
"user": {
"id": "QUJBVjZRV1BCRVk5V09UT0ExQTQ",
"name": "lee",
"displayName": "Lee",
},
"challenge": "MTIzNDU2Nzg5MA",
"pubKeyCredParams": [{"type": "public-key", "alg": -36}],
"timeout": 120000,
"excludeCredentials": [{"type": "public-key", "id": "MTIzNDU2Nzg5MA"}],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"residentKey": "required",
"requireResidentKey": True,
"userVerification": "preferred",
},
"attestation": "direct",
},
"attestation": "direct",
}
)

def test_includes_optional_value_when_set(self) -> None:
options = generate_registration_options(
Expand All @@ -73,10 +79,44 @@ def test_includes_optional_value_when_set(self) -> None:

output = options_to_json(options)

assert json.loads(output)["excludeCredentials"] == [
self.assertEqual(
json.loads(output)["excludeCredentials"],
[
{
"id": "MTIzNDU2Nzg5MA",
"transports": ["usb"],
"type": "public-key",
}
],
)

def test_converts_authentication_options_to_JSON(self) -> None:
options = generate_authentication_options(
rp_id="example.com",
challenge=b"1234567890",
allow_credentials=[
PublicKeyCredentialDescriptor(id=b"1234567890"),
],
timeout=120000,
user_verification=UserVerificationRequirement.DISCOURAGED,
)

output = options_to_json(options)

self.assertEqual(
json.loads(output),
{
"id": "MTIzNDU2Nzg5MA",
"transports": ["usb"],
"type": "public-key",
}
]
"rpId": "example.com",
"challenge": "MTIzNDU2Nzg5MA",
"allowCredentials": [{"type": "public-key", "id": "MTIzNDU2Nzg5MA"}],
"timeout": 120000,
"userVerification": "discouraged",
},
)

def test_raises_on_bad_input(self) -> None:
class FooClass:
pass

with self.assertRaisesRegex(TypeError, "not instance"):
options_to_json(FooClass()) # type: ignore
Loading