Integrate with the API for adding and getting webauthn creds

This links up the `get_webauthn_credentials_for_user` and
`create_webauthn_credential_for_user` methods of the user api client to
notifications-api.

To send data to the API we need strings to be unicode, so we call
decode('utf-8') on base64 objects.

Co-authored-by: Leo Hemsted <leo.hemsted@digital.cabinet-office.gov.uk>
This commit is contained in:
Katie Smith
2021-05-13 15:54:05 +01:00
parent 362189d562
commit bafcc02b7d
7 changed files with 60 additions and 24 deletions

View File

@@ -19,8 +19,10 @@ def test_should_show_overview_page(
def test_overview_page_shows_disable_for_platform_admin(
client_request,
platform_admin_user
platform_admin_user,
mocker
):
mocker.patch('app.user_api_client.get_webauthn_credentials_for_user')
client_request.login(platform_admin_user)
page = client_request.get('main.user_profile')
assert page.select_one('h1').text.strip() == 'Your profile'

View File

@@ -27,6 +27,7 @@ def test_begin_register_returns_encoded_options(
values={'ADMIN_BASE_URL': 'http://localhost:6012'}
)
webauthn_server.init_app(app_)
mocker.patch('app.user_api_client.get_webauthn_credentials_for_user', return_value=[])
response = platform_admin_client.get(
url_for('main.webauthn_begin_register')
@@ -71,11 +72,18 @@ def test_begin_register_includes_existing_credentials(
def test_begin_register_stores_state_in_session(
platform_admin_client,
mocker,
):
platform_admin_client.get(
mocker.patch(
'app.user_api_client.get_webauthn_credentials_for_user',
return_value=[])
response = platform_admin_client.get(
url_for('main.webauthn_begin_register')
)
assert response.status_code == 200
with platform_admin_client.session_transaction() as session:
assert session['webauthn_registration_state'] is not None

View File

@@ -1,5 +1,6 @@
import base64
import pytest
from fido2 import cbor
from fido2.cose import ES256
@@ -14,7 +15,8 @@ CLIENT_DATA_JSON = b'{"type": "webauthn.create", "clientExtensions": {}, "challe
ATTESTATION_OBJECT = base64.b64decode(b'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAI1qbvWibQos/t3zsTU05IXw1Ek3SDApATok09uc4UBwAiEAv0fB/lgb5Ot3zJ691Vje6iQLAtLhJDiA8zDxaGjcE3hjeDVjgVkCUzCCAk8wggE3oAMCAQICBDxoKU0wDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMDExLzAtBgNVBAMMJll1YmljbyBVMkYgRUUgU2VyaWFsIDIzOTI1NzM0ODExMTE3OTAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvd9nk9t3lMNQMXHtLE1FStlzZnUaSLql2fm1ajoggXlrTt8rzXuSehSTEPvEaEdv/FeSqX22L6Aoa8ajIAIOY6M7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAKrADVEJfuwVpIazebzEg0D4Z9OXLs5qZ/ukcONgxkRZ8K04QtP/CB5x6olTlxsj+SXArQDCRzEYUgbws6kZKfuRt2a1P+EzUiqDWLjRILSr+3/o7yR7ZP/GpiFKwdm+czb94POoGD+TS1IYdfXj94mAr5cKWx4EKjh210uovu/pLdLjc8xkQciUrXzZpPR9rT2k/q9HkZhHU+NaCJzky+PTyDbq0KKnzqVhWtfkSBCGw3ezZkTS+5lrvOKbIa24lfeTgu7FST5OwTPCFn8HcfWZMXMSD/KNU+iBqJdAwTLPPDRoLLvPTl29weCAIh+HUpmBQd0UltcPOrA/LFvAf61oYXV0aERhdGFYwnSm6pITyZwvdLIkkrMgz0AmKpTBqVCgOX8pJQtghB7wQQAAAAAAAAAAAAAAAAAAAAAAAAAAAECKU1ppjl9gmhHWyDkgHsUvZmhr6oF3/lD3llzLE2SaOSgOGIsIuAQqgp8JQSUu3r/oOaP8RS44dlQjrH+ALfYtpAECAyYhWCAxnqAfESXOYjKUc2WACuXZ3ch0JHxV0VFrrTyjyjIHXCJYIFnx8H87L4bApR4M+hPcV+fHehEOeW+KCyd0H+WGY8s6') # noqa
def test_from_registration_verifies_response(app_, mocker):
@pytest.fixture
def disable_webauthn_origin_verification(app_, mocker):
mocker.patch.dict(
app_.config, values={
'NOTIFY_ENVIRONMENT': 'development',
@@ -25,6 +27,8 @@ def test_from_registration_verifies_response(app_, mocker):
# disable origin verification for non-HTTPS test
webauthn_server.init_app(app_)
def test_from_registration_verifies_response(disable_webauthn_origin_verification):
registration_response = {
'clientDataJSON': CLIENT_DATA_JSON,
'attestationObject': ATTESTATION_OBJECT,
@@ -32,9 +36,23 @@ def test_from_registration_verifies_response(app_, mocker):
credential = WebAuthnCredential.from_registration(SESSION_STATE, registration_response)
assert credential.name == 'Unnamed key'
assert credential.registration_response == base64.b64encode(cbor.encode(registration_response))
assert credential.registration_response == base64.b64encode(cbor.encode(registration_response)).decode('utf-8')
credential_data = credential.to_credential_data()
assert type(credential_data.credential_id) is bytes
assert type(credential_data.aaguid) is bytes
assert credential_data.public_key[3] == ES256.ALGORITHM
def test_from_registration_encodes_as_unicode(disable_webauthn_origin_verification):
registration_response = {
'clientDataJSON': CLIENT_DATA_JSON,
'attestationObject': ATTESTATION_OBJECT,
}
credential = WebAuthnCredential.from_registration(SESSION_STATE, registration_response)
serialized_credential = credential.serialize()
assert type(serialized_credential['credential_data']) == str
assert type(serialized_credential['registration_response']) == str

View File

@@ -241,12 +241,25 @@ def test_add_user_to_service_calls_correct_endpoint_and_deletes_keys_from_cache(
]
def test_get_webauthn_credentials_for_user_returns_stubbed_data():
credentials = user_api_client.get_webauthn_credentials_for_user('id')
assert len(credentials) == 0
def test_get_webauthn_credentials_for_user(mocker, webauthn_credential, fake_uuid):
mock_get = mocker.patch(
'app.notify_client.user_api_client.UserApiClient.get',
return_value={'data': [webauthn_credential]}
)
credentials = user_api_client.get_webauthn_credentials_for_user(fake_uuid)
mock_get.assert_called_once_with(f'/user/{fake_uuid}/webauthn')
assert len(credentials) == 1
assert credentials[0]['name'] == 'Test credential'
def test_create_webauthn_credential_for_user_stores_stubbed_data(webauthn_credential):
def test_create_webauthn_credential_for_user(mocker, webauthn_credential, fake_uuid):
credential = WebAuthnCredential(webauthn_credential)
user_api_client.create_webauthn_credential_for_user('id', credential)
assert len(user_api_client.credentials) == 1
mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post')
expected_url = f'/user/{fake_uuid}/webauthn'
user_api_client.create_webauthn_credential_for_user(fake_uuid, credential)
mock_post.assert_called_once_with(expected_url, data=credential.serialize())

View File

@@ -4485,7 +4485,7 @@ def mock_get_invited_org_user_by_id(mocker, sample_org_invite):
def webauthn_credential():
return {
'name': 'Test credential',
'credential_data': b'WJ0AAAAAAAAAAAAAAAAAAAAAAECKU1ppjl9gmhHWyDkgHsUvZmhr6oF3/lD3llzLE2SaOSgOGIsIuAQqgp8JQSUu3r/oOaP8RS44dlQjrH+ALfYtpAECAyYhWCAxnqAfESXOYjKUc2WACuXZ3ch0JHxV0VFrrTyjyjIHXCJYIFnx8H87L4bApR4M+hPcV+fHehEOeW+KCyd0H+WGY8s6', # noqa
'credential_data': 'WJ0AAAAAAAAAAAAAAAAAAAAAAECKU1ppjl9gmhHWyDkgHsUvZmhr6oF3/lD3llzLE2SaOSgOGIsIuAQqgp8JQSUu3r/oOaP8RS44dlQjrH+ALfYtpAECAyYhWCAxnqAfESXOYjKUc2WACuXZ3ch0JHxV0VFrrTyjyjIHXCJYIFnx8H87L4bApR4M+hPcV+fHehEOeW+KCyd0H+WGY8s6', # noqa
'registration_response': 'anything',
'created_at': '2017-10-18T16:57:14.154185Z',
}