Skip to content

Commit

Permalink
Update to latest webauthn (1.11) (#851)
Browse files Browse the repository at this point in the history
* Update py_webauthn (1.11)

Use new 1.11 helpers - parse_registration_credential_json and parse_authentication_credential_json

Note in release notes that for now - must use pydantic 1.x with webauthn until user_handle issue solved.
  • Loading branch information
jwag956 authored Oct 12, 2023
1 parent 8c9c774 commit 8dba976
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default_language_version:
python: python3.9
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand All @@ -14,7 +14,7 @@ repos:
- id: check-merge-conflict
- id: fix-byte-order-marker
- repo: https://github.com/asottile/pyupgrade
rev: v3.14.0
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py38-plus]
Expand Down
8 changes: 6 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ Version 5.3.1

Released October XX, 2023

Please Note: If your application uses webauthn you must pin py_webauthn to 1.9.0
until the issue with user_handle is resolved.
**Please Note:**

- If your application uses webauthn you must use pydantic < 2.0
until the issue with user_handle is resolved.
- If you want to use the latest Flask (3.0.0) you need to have Flask-Login changes -
those aren't currently released - use the 'main' branch.

Fixes
++++++
Expand Down
14 changes: 8 additions & 6 deletions flask_security/webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@
VerifiedAuthentication,
)
from webauthn.registration.verify_registration_response import VerifiedRegistration
from webauthn.helpers import (
parse_registration_credential_json,
parse_authentication_credential_json,
)
from webauthn.helpers.exceptions import (
InvalidAuthenticationResponse,
InvalidRegistrationResponse,
)
from webauthn.helpers.structs import (
AuthenticationCredential,
AuthenticatorTransport,
PublicKeyCredentialDescriptor,
PublicKeyCredentialType,
RegistrationCredential,
UserVerificationRequirement,
)
from webauthn.helpers import bytes_to_base64url
Expand Down Expand Up @@ -168,8 +170,8 @@ def validate(self, **kwargs: t.Any) -> bool:
self.credential.errors.append(msg)
return False
try:
reg_cred = RegistrationCredential.parse_raw(self.credential.data)
except (ValueError, KeyError):
reg_cred = parse_registration_credential_json(self.credential.data)
except (ValueError, KeyError, InvalidRegistrationResponse):
self.credential.errors.append(get_message("API_ERROR")[0])
return False
try:
Expand Down Expand Up @@ -259,8 +261,8 @@ def validate(self, **kwargs: t.Any) -> bool:
if not super().validate(**kwargs):
return False # pragma: no cover
try:
auth_cred = AuthenticationCredential.parse_raw(self.credential.data)
except (ValueError, KeyError):
auth_cred = parse_authentication_credential_json(self.credential.data)
except (ValueError, KeyError, InvalidAuthenticationResponse):
self.credential.errors.append(get_message("API_ERROR")[0])
return False

Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ dependencies = [
babel = ["babel>=2.12.1", "flask_babel>=3.1.0"]
fsqla = ["flask_sqlalchemy>=3.0.3", "sqlalchemy>=2.0.12", "sqlalchemy-utils>=0.41.1"]
common = ["bcrypt>=4.0.1", "flask_mailman>=0.3.0", "bleach>=6.0.0"]
mfa = ["cryptography>=40.0.2", "qrcode>=7.4.2", "phonenumberslite>=8.13.11", "webauthn>=1.9.0"]
mfa = ["cryptography>=40.0.2", "qrcode>=7.4.2", "phonenumberslite>=8.13.11", "webauthn>=1.11.0"]
low = [
# Lowest supported versions
"Flask==2.3.2",
Expand All @@ -78,12 +78,13 @@ low = [
"mongomock==4.1.2",
"pony==0.7.16;python_version<'3.11'",
"phonenumberslite==8.13.11",
"pydantic<2.0",
"qrcode==7.4.2",
# authlib requires requests
"requests",
"sqlalchemy==2.0.12",
"sqlalchemy-utils==0.41.1",
"webauthn==1.9.0",
"webauthn==1.11.0",
"werkzeug==2.3.3",
"zxcvbn==4.4.28"
]
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ filterwarnings =
error
ignore::DeprecationWarning:mongoengine:
ignore::DeprecationWarning:flask_login:0
ignore::DeprecationWarning:pydantic_core:0
ignore:.*passwordless feature.*:DeprecationWarning:flask_security:0
ignore::DeprecationWarning:passlib:0
ignore:.*'sms' was enabled in SECURITY_US_ENABLED_METHODS;.*:UserWarning:flask_security:0
Expand Down
3 changes: 2 additions & 1 deletion requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ qrcode
requests
sqlalchemy
sqlalchemy-utils
webauthn==1.9.0
webauthn
pydantic<2.0
werkzeug
zxcvbn
20 changes: 10 additions & 10 deletions tests/test_webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,20 +1177,20 @@ def test_reset(app, client):


@pytest.mark.settings(webauthn_util_cls=HackWebauthnUtil)
def test_user_handle(app, client, get_message):
def test_user_handle(app, clients, get_message):
"""Test that we fail signin if user_handle doesn't match.
Since we generated the SIGNIN_DATA_OH from view_scaffold - the user_handle
has no way of matching.
"""
authenticate(client)
register_options, response_url = _register_start_json(client, usage="first")
response = client.post(response_url, json=dict(credential=json.dumps(REG_DATA_UH)))
authenticate(clients)
register_options, response_url = _register_start_json(clients, usage="first")
response = clients.post(response_url, json=dict(credential=json.dumps(REG_DATA_UH)))
assert response.status_code == 200

# verify can't sign in
logout(client)
signin_options, response_url, _ = _signin_start_json(client, "matt@lp.com")
response = client.post(
logout(clients)
signin_options, response_url, _ = _signin_start_json(clients, "matt@lp.com")
response = clients.post(
response_url, json=dict(credential=json.dumps(SIGNIN_DATA_UH))
)
assert response.json["response"]["field_errors"]["credential"][0].encode(
Expand All @@ -1210,12 +1210,12 @@ def test_user_handle(app, client, get_message):
)
upd_signin_data = copy.deepcopy(SIGNIN_DATA_UH)
upd_signin_data["response"]["userHandle"] = b64_user_handle
signin_options, response_url, _ = _signin_start_json(client, "matt@lp.com")
response = client.post(
signin_options, response_url, _ = _signin_start_json(clients, "matt@lp.com")
response = clients.post(
response_url, json=dict(credential=json.dumps(upd_signin_data))
)
# verify actually logged in
response = client.get("/profile", headers={"accept": "application/json"})
response = clients.get("/profile", headers={"accept": "application/json"})
assert response.status_code == 200


Expand Down

0 comments on commit 8dba976

Please sign in to comment.