| import json |
| from json import JSONDecodeError |
| from typing import List, Optional, Union |
|
|
| from .base64url_to_bytes import base64url_to_bytes |
| from .exceptions import InvalidJSONStructure, InvalidAuthenticationOptions |
| from .structs import ( |
| AuthenticatorTransport, |
| PublicKeyCredentialDescriptor, |
| PublicKeyCredentialRequestOptions, |
| UserVerificationRequirement, |
| ) |
|
|
|
|
| def parse_authentication_options_json( |
| json_val: Union[str, dict] |
| ) -> PublicKeyCredentialRequestOptions: |
| """ |
| Parse a JSON form of authentication options, as either stringified JSON or a plain dict, into an |
| instance of `PublicKeyCredentialRequestOptions`. Typically useful in mapping output from |
| `generate_authentication_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 challenge |
| """ |
| options_challenge = json_val.get("challenge") |
| if not isinstance(options_challenge, str): |
| raise InvalidJSONStructure("Options missing required challenge") |
|
|
| """ |
| Check timeout |
| """ |
| options_timeout = json_val.get("timeout") |
| mapped_timeout = None |
| if isinstance(options_timeout, int): |
| mapped_timeout = options_timeout |
|
|
| """ |
| Check rpId |
| """ |
| options_rp_id = json_val.get("rpId") |
| mapped_rp_id = None |
| if isinstance(options_rp_id, str): |
| mapped_rp_id = options_rp_id |
|
|
| """ |
| Check userVerification |
| """ |
| options_user_verification = json_val.get("userVerification") |
| if not isinstance(options_user_verification, str): |
| raise InvalidJSONStructure("Options missing required userVerification") |
|
|
| try: |
| mapped_user_verification = UserVerificationRequirement(options_user_verification) |
| except ValueError as exc: |
| raise InvalidJSONStructure("Options userVerification was invalid value") from exc |
|
|
| """ |
| Check allowCredentials |
| """ |
| options_allow_credentials = json_val.get("allowCredentials") |
| mapped_allow_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None |
| if isinstance(options_allow_credentials, list): |
| mapped_allow_credentials = [] |
| for cred in options_allow_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_allow_credentials.append(_mapped) |
|
|
| try: |
| authentication_options = PublicKeyCredentialRequestOptions( |
| challenge=base64url_to_bytes(options_challenge), |
| timeout=mapped_timeout, |
| rp_id=mapped_rp_id, |
| user_verification=mapped_user_verification, |
| allow_credentials=mapped_allow_credentials, |
| ) |
| except Exception as exc: |
| raise InvalidAuthenticationOptions( |
| "Could not parse authentication options from JSON data" |
| ) from exc |
|
|
| return authentication_options |
|
|