| import json |
| from json.decoder import JSONDecodeError |
| from typing import Union, Optional, List |
|
|
|
|
| from .structs import ( |
| PublicKeyCredentialCreationOptions, |
| PublicKeyCredentialRpEntity, |
| PublicKeyCredentialUserEntity, |
| AttestationConveyancePreference, |
| AuthenticatorSelectionCriteria, |
| AuthenticatorAttachment, |
| ResidentKeyRequirement, |
| UserVerificationRequirement, |
| PublicKeyCredentialParameters, |
| PublicKeyCredentialDescriptor, |
| AuthenticatorTransport, |
| ) |
| from .cose import COSEAlgorithmIdentifier |
| from .exceptions import InvalidJSONStructure, InvalidRegistrationOptions |
| from .base64url_to_bytes import base64url_to_bytes |
|
|
|
|
| def parse_registration_options_json( |
| json_val: Union[str, dict] |
| ) -> PublicKeyCredentialCreationOptions: |
| """ |
| Parse a JSON form of registration options, as either stringified JSON or a plain dict, into an |
| instance of `PublicKeyCredentialCreationOptions`. Typically useful in mapping output from |
| `generate_registration_options()`, that's been persisted as JSON via Redis/etc... back into |
| structured data. |
| """ |
| if isinstance(json_val, str): |
| try: |
| json_val = json.loads(json_val) |
| except JSONDecodeError: |
| raise InvalidJSONStructure("Unable to decode options as JSON") |
|
|
| if not isinstance(json_val, dict): |
| raise InvalidJSONStructure("Options were not a JSON object") |
|
|
| """ |
| Check rp |
| """ |
| options_rp = json_val.get("rp") |
| if not isinstance(options_rp, dict): |
| raise InvalidJSONStructure("Options missing required rp") |
|
|
| options_rp_id = options_rp.get("id") |
| if options_rp_id is not None and not isinstance(options_rp_id, str): |
| raise InvalidJSONStructure("Options rp.id present but not string") |
|
|
| options_rp_name = options_rp.get("name") |
| if not isinstance(options_rp_name, str): |
| raise InvalidJSONStructure("Options rp missing required name") |
|
|
| """ |
| Check user |
| """ |
| options_user = json_val.get("user") |
| if not isinstance(options_user, dict): |
| raise InvalidJSONStructure("Options missing required user") |
|
|
| options_user_id = options_user.get("id") |
| if not isinstance(options_user_id, str): |
| raise InvalidJSONStructure("Options user missing required id") |
|
|
| options_user_name = options_user.get("name") |
| if not isinstance(options_user_name, str): |
| raise InvalidJSONStructure("Options user missing required name") |
|
|
| options_user_display_name = options_user.get("displayName") |
| if not isinstance(options_user_display_name, str): |
| raise InvalidJSONStructure("Options user missing required displayName") |
|
|
| """ |
| Check attestation |
| """ |
| options_attestation = json_val.get("attestation") |
| if not isinstance(options_attestation, str): |
| raise InvalidJSONStructure("Options missing required attestation") |
|
|
| try: |
| mapped_attestation = AttestationConveyancePreference(options_attestation) |
| except ValueError as exc: |
| raise InvalidJSONStructure("Options attestation was invalid value") from exc |
|
|
| """ |
| Check authenticatorSelection |
| """ |
| options_authr_selection = json_val.get("authenticatorSelection") |
| mapped_authenticator_selection: Optional[AuthenticatorSelectionCriteria] = None |
| if isinstance(options_authr_selection, dict): |
| options_authr_selection_attachment = options_authr_selection.get("authenticatorAttachment") |
| mapped_attachment = None |
| if options_authr_selection_attachment is not None: |
| try: |
| mapped_attachment = AuthenticatorAttachment(options_authr_selection_attachment) |
| except ValueError as exc: |
| raise InvalidJSONStructure( |
| "Options authenticatorSelection attachment was invalid value" |
| ) from exc |
|
|
| options_authr_selection_rkey = options_authr_selection.get("residentKey") |
| mapped_rkey = None |
| if options_authr_selection_rkey is not None: |
| try: |
| mapped_rkey = ResidentKeyRequirement(options_authr_selection_rkey) |
| except ValueError as exc: |
| raise InvalidJSONStructure( |
| "Options authenticatorSelection residentKey was invalid value" |
| ) from exc |
|
|
| options_authr_selection_require_rkey = options_authr_selection.get("requireResidentKey") |
| mapped_require_rkey = False |
| if options_authr_selection_require_rkey is not None: |
| if not isinstance(options_authr_selection_require_rkey, bool): |
| raise InvalidJSONStructure( |
| "Options authenticatorSelection requireResidentKey was invalid boolean" |
| ) |
|
|
| mapped_require_rkey = options_authr_selection_require_rkey |
|
|
| options_authr_selection_uv = options_authr_selection.get("userVerification") |
| mapped_user_verification = UserVerificationRequirement.PREFERRED |
| if options_authr_selection_uv is not None: |
| try: |
| mapped_user_verification = UserVerificationRequirement(options_authr_selection_uv) |
| except ValueError as exc: |
| raise InvalidJSONStructure( |
| "Options authenticatorSelection userVerification was invalid value" |
| ) from exc |
|
|
| mapped_authenticator_selection = AuthenticatorSelectionCriteria( |
| authenticator_attachment=mapped_attachment, |
| resident_key=mapped_rkey, |
| require_resident_key=mapped_require_rkey, |
| user_verification=mapped_user_verification, |
| ) |
|
|
| """ |
| Check challenge is present |
| """ |
| options_challenge = json_val.get("challenge") |
| if not isinstance(options_challenge, str): |
| raise InvalidJSONStructure("Options missing required challenge") |
|
|
| """ |
| Check pubKeyCredParams |
| """ |
| options_pub_key_cred_params = json_val.get("pubKeyCredParams") |
| if not isinstance(options_pub_key_cred_params, list): |
| raise InvalidJSONStructure("Options pubKeyCredParams was invalid value") |
|
|
| try: |
| mapped_pub_key_cred_params = [ |
| PublicKeyCredentialParameters( |
| alg=COSEAlgorithmIdentifier(param["alg"]), type="public-key" |
| ) |
| for param in options_pub_key_cred_params |
| ] |
| except ValueError as exc: |
| raise InvalidJSONStructure("Options pubKeyCredParams entry had invalid alg") from exc |
|
|
| """ |
| Check excludeCredentials |
| """ |
| options_exclude_credentials = json_val.get("excludeCredentials") |
| mapped_exclude_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None |
| if isinstance(options_exclude_credentials, list): |
| mapped_exclude_credentials = [] |
| for cred in options_exclude_credentials: |
| _cred_id = cred.get("id") |
| if not isinstance(_cred_id, str): |
| raise InvalidJSONStructure("Options excludeCredentials entry missing required id") |
|
|
| _mapped = PublicKeyCredentialDescriptor(id=base64url_to_bytes(_cred_id)) |
|
|
| _transports = cred.get("transports") |
| if _transports is not None: |
| if not isinstance(_transports, list): |
| raise InvalidJSONStructure( |
| "Options excludeCredentials entry transports was not list" |
| ) |
| try: |
| _mapped.transports = [ |
| AuthenticatorTransport(_transport) for _transport in _transports |
| ] |
| except ValueError as exc: |
| raise InvalidJSONStructure( |
| "Options excludeCredentials entry transports had invalid value" |
| ) from exc |
|
|
| mapped_exclude_credentials.append(_mapped) |
|
|
| """ |
| Check timeout |
| """ |
| options_timeout = json_val.get("timeout") |
| mapped_timeout = None |
| if isinstance(options_timeout, int): |
| mapped_timeout = options_timeout |
|
|
| try: |
| registration_options = PublicKeyCredentialCreationOptions( |
| rp=PublicKeyCredentialRpEntity( |
| id=options_rp_id, |
| name=options_rp_name, |
| ), |
| user=PublicKeyCredentialUserEntity( |
| id=base64url_to_bytes(options_user_id), |
| name=options_user_name, |
| display_name=options_user_display_name, |
| ), |
| attestation=mapped_attestation, |
| authenticator_selection=mapped_authenticator_selection, |
| challenge=base64url_to_bytes(options_challenge), |
| pub_key_cred_params=mapped_pub_key_cred_params, |
| exclude_credentials=mapped_exclude_credentials, |
| timeout=mapped_timeout, |
| ) |
| except Exception as exc: |
| raise InvalidRegistrationOptions( |
| "Could not parse registration options from JSON data" |
| ) from exc |
|
|
| return registration_options |
|
|