mirror of
https://github.com/GSA/notifications-api.git
synced 2026-05-05 16:48:31 -04:00
add webauthn crud endpoints
added some simple validation to the delete endpoint for sanity, but generally my assumption is that more validation will happen on the admin side. noteably im not checking whether the credentials are duplicated, nor is there a uniqueness constraint in the database - I'm not sure if the credential blob will always reliably be equivalent, and I believe the browser should hopefully take care of dupes.
This commit is contained in:
@@ -74,6 +74,7 @@ from app.models import (
|
||||
Template,
|
||||
TemplateFolder,
|
||||
User,
|
||||
WebauthnCredential,
|
||||
)
|
||||
|
||||
|
||||
@@ -1202,3 +1203,22 @@ def create_broadcast_provider_message(
|
||||
db.session.add(provider_message_number)
|
||||
db.session.commit()
|
||||
return provider_message
|
||||
|
||||
|
||||
def create_webauthn_credential(
|
||||
user,
|
||||
name='my key',
|
||||
*,
|
||||
credential_data='ABC123',
|
||||
registration_response='DEF456',
|
||||
):
|
||||
webauthn_credential = WebauthnCredential(
|
||||
user=user,
|
||||
name=name,
|
||||
credential_data=credential_data,
|
||||
registration_response=registration_response
|
||||
)
|
||||
|
||||
db.session.add(webauthn_credential)
|
||||
db.session.commit()
|
||||
return webauthn_credential
|
||||
|
||||
173
tests/app/webauthn/test_rest.py
Normal file
173
tests/app/webauthn/test_rest.py
Normal file
@@ -0,0 +1,173 @@
|
||||
import uuid
|
||||
from unittest.mock import ANY
|
||||
|
||||
from tests.app.db import create_user, create_webauthn_credential
|
||||
|
||||
|
||||
def test_get_webauthn_credentials_returns_all_credentials_for_user(admin_request, notify_db_session):
|
||||
me = create_user(email='a')
|
||||
other = create_user(email='b')
|
||||
first = create_webauthn_credential(me, '1')
|
||||
create_webauthn_credential(me, '2')
|
||||
create_webauthn_credential(other, '3')
|
||||
|
||||
response = admin_request.get(
|
||||
'webauthn.get_webauthn_credentials',
|
||||
user_id=me.id,
|
||||
)
|
||||
|
||||
creds = sorted(response['data'], key=lambda x: x['name'])
|
||||
assert len(creds) == 2
|
||||
|
||||
assert creds[0] == {
|
||||
'id': str(first.id),
|
||||
'user_id': str(me.id),
|
||||
'name': '1',
|
||||
'credential_data': 'ABC123',
|
||||
'registration_response': 'DEF456',
|
||||
'created_at': ANY,
|
||||
'updated_at': None
|
||||
}
|
||||
|
||||
assert creds[1]['name'] == '2'
|
||||
|
||||
|
||||
def test_get_webauthn_credentials_returns_empty_list_if_no_creds(admin_request, sample_user):
|
||||
response = admin_request.get('webauthn.get_webauthn_credentials', user_id=sample_user.id)
|
||||
assert response == {'data': []}
|
||||
|
||||
|
||||
def test_get_webauthn_credentials_errors_if_user_doesnt_exist(admin_request, sample_user):
|
||||
create_webauthn_credential(sample_user, '1')
|
||||
|
||||
admin_request.get(
|
||||
'webauthn.get_webauthn_credentials',
|
||||
user_id=uuid.uuid4(),
|
||||
_expected_status=404
|
||||
)
|
||||
|
||||
|
||||
def test_create_webauthn_credential_returns_201(admin_request, sample_user):
|
||||
response = admin_request.post(
|
||||
'webauthn.create_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
_data={
|
||||
'name': 'my key',
|
||||
'credential_data': 'ABC123',
|
||||
'registration_response': 'DEF456',
|
||||
},
|
||||
_expected_status=201
|
||||
)
|
||||
assert len(sample_user.webauthn_credentials) == 1
|
||||
|
||||
new_cred = sample_user.webauthn_credentials[0]
|
||||
|
||||
assert new_cred.name == 'my key'
|
||||
assert new_cred.credential_data == 'ABC123'
|
||||
assert new_cred.registration_response == 'DEF456'
|
||||
assert response['data']['id'] == str(new_cred.id)
|
||||
|
||||
|
||||
def test_create_webauthn_credential_errors_if_schema_violation(admin_request, sample_user):
|
||||
response = admin_request.post(
|
||||
'webauthn.create_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
_data={
|
||||
'name': 'my key',
|
||||
'credential_data': 'ABC123',
|
||||
# missing registration_response
|
||||
},
|
||||
_expected_status=400
|
||||
)
|
||||
assert response['errors'][0] == {
|
||||
'error': 'ValidationError',
|
||||
'message': 'registration_response is a required property'
|
||||
}
|
||||
|
||||
|
||||
def test_update_webauthn_credential_returns_200(admin_request, sample_user):
|
||||
cred = create_webauthn_credential(sample_user)
|
||||
assert cred.name != 'new name'
|
||||
|
||||
response = admin_request.post(
|
||||
'webauthn.update_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
webauthn_credential_id=cred.id,
|
||||
_data={
|
||||
'name': 'new name',
|
||||
},
|
||||
)
|
||||
assert response['data']['id'] == str(cred.id)
|
||||
assert response['data']['name'] == 'new name'
|
||||
|
||||
|
||||
def test_update_webauthn_credential_errors_if_schema_violation(admin_request, sample_user):
|
||||
cred = create_webauthn_credential(sample_user)
|
||||
response = admin_request.post(
|
||||
'webauthn.update_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
webauthn_credential_id=cred.id,
|
||||
_data={
|
||||
'name': 'my key',
|
||||
# you can't update credential_data
|
||||
'credential_data': 'NAUGHTY123'
|
||||
},
|
||||
_expected_status=400
|
||||
)
|
||||
assert response['errors'][0] == {
|
||||
'error': 'ValidationError',
|
||||
'message': 'Additional properties are not allowed (credential_data was unexpected)'
|
||||
}
|
||||
|
||||
|
||||
def test_update_webauthn_credential_errors_if_webauthn_credential_doesnt_exist(admin_request, sample_user):
|
||||
admin_request.post(
|
||||
'webauthn.update_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
webauthn_credential_id=uuid.uuid4(),
|
||||
_data={
|
||||
'name': 'my key',
|
||||
},
|
||||
_expected_status=404
|
||||
)
|
||||
|
||||
|
||||
def test_delete_webauthn_credential_returns_204(admin_request, sample_user):
|
||||
cred1 = create_webauthn_credential(sample_user)
|
||||
cred2 = create_webauthn_credential(sample_user)
|
||||
admin_request.delete(
|
||||
'webauthn.update_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
webauthn_credential_id=cred1.id,
|
||||
_expected_status=204
|
||||
)
|
||||
assert sample_user.webauthn_credentials == [cred2]
|
||||
|
||||
|
||||
def test_delete_webauthn_credential_errors_if_last_key(admin_request, sample_user):
|
||||
cred = create_webauthn_credential(sample_user)
|
||||
response = admin_request.delete(
|
||||
'webauthn.delete_webauthn_credential',
|
||||
user_id=sample_user.id,
|
||||
webauthn_credential_id=cred.id,
|
||||
_expected_status=400
|
||||
)
|
||||
assert response['message'] == 'Cannot delete last remaining webauthn credential for user'
|
||||
|
||||
|
||||
def test_delete_webauthn_credential_errors_if_user_id_doesnt_match(admin_request, notify_db_session):
|
||||
user_1 = create_user(email='1')
|
||||
user_2 = create_user(email='2')
|
||||
cred_1a = create_webauthn_credential(user_1) # noqa
|
||||
cred_1b = create_webauthn_credential(user_1) # noqa
|
||||
cred_2a = create_webauthn_credential(user_2)
|
||||
cred_2b = create_webauthn_credential(user_2) # noqa
|
||||
|
||||
response = admin_request.delete(
|
||||
'webauthn.delete_webauthn_credential',
|
||||
user_id=user_1.id,
|
||||
webauthn_credential_id=cred_2a.id,
|
||||
_expected_status=400
|
||||
)
|
||||
|
||||
assert response['message'] == 'Webauthn credential does not belong to this user'
|
||||
Reference in New Issue
Block a user