codekingpro/portable-devtools
114k
1"""2 flask_security.webauthn_util3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~4 5 Utility class providing methods controlling various aspects of webauthn.6 7 :copyright: (c) 2020-2024 by J. Christopher Wagner (jwag).8 :license: MIT, see LICENSE for more details.9 10"""11 12from __future__ import annotations13 14import secrets15import typing as t16 17from flask import current_app, request18 19try:20 # noinspection PyUnresolvedReferences21 from webauthn.helpers.structs import (22 AuthenticatorAttachment,23 AuthenticatorSelectionCriteria,24 ResidentKeyRequirement,25 UserVerificationRequirement,26 )27except ImportError: # pragma: no cover28 pass29 30 31if t.TYPE_CHECKING: # pragma: no cover32 import flask33 from .datastore import User34 35 36class WebauthnUtil:37 """38 Utility class allowing an application to fine-tune various Relying Party39 attributes.40 41 To provide your own implementation, pass in the class as ``webauthn_util_cls``42 at init time. Your class will be instantiated once as part of app initialization.43 44 .. versionadded:: 5.0.045 """46 47 def __init__(self, app: flask.Flask):48 """Instantiate class.49 50 :param app: The Flask application being initialized.51 """52 pass53 54 def generate_challenge(self, nbytes: int | None = None) -> str:55 # Mostly override this for testing, so we can have a 'constant' challenge.56 return secrets.token_urlsafe(nbytes)57 58 def origin(self) -> str:59 # Return the RP origin - normally this is just the URL of the application.60 return request.host_url.rstrip("/")61 62 def registration_options(63 self, user: User, usage: str, existing_options: dict[str, t.Any]64 ) -> dict[str, t.Any]:65 """66 :param user: User object - could be used to configure on a per-user basis.67 :param usage: Either "first" or "secondary" (webauthn is being used as a second68 factor for authentication)69 :param existing_options: Currently filled in registration options.70 71 Return a dict that will be sent in to py-webauthn generate_registration_options72 """73 existing_options["authenticator_selection"] = self.authenticator_selection(74 user, usage75 )76 return existing_options77 78 def authenticator_selection(79 self, user: User, usage: str80 ) -> AuthenticatorSelectionCriteria:81 """82 :param user: User object - could be used to configure on a per-user basis.83 :param usage: Either "first" or "secondary" (webauthn is being used as a second84 factor for authentication85 86 Part of the registration ceremony is providing information about what kind87 of authenticators the app is interested in.88 See: https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#dictionary-authenticatorSelection89 90 The main options are:91 - whether you want a ResidentKey (discoverable)92 - Attachment - platform or cross-platform93 - Does the key have to provide user-verification94 95 :note::96 If the key isn't resident then it isn't discoverable which means that97 the user won't be able to use that key unless they identify themselves98 (use the key as a second factor OR type in their identity). If they are forced99 to type in their identity PRIOR to being authenticated, then there is the100 possibility that the app will leak username information.101 """ # noqa: E501102 103 select_criteria = AuthenticatorSelectionCriteria()104 # TODO: look at #sctn-usecase-new-device-registration to see a reason105 # to allow multiple keys as "first" - only one would need to be cross-platform106 if usage == "first":107 select_criteria.authenticator_attachment = (108 AuthenticatorAttachment.CROSS_PLATFORM109 )110 select_criteria.user_verification = UserVerificationRequirement.PREFERRED111 else:112 # For second factor minimize user-interaction by not asking for UV113 select_criteria.user_verification = UserVerificationRequirement.DISCOURAGED114 115 if not current_app.config.get("SECURITY_WAN_ALLOW_USER_HINTS"):116 select_criteria.resident_key = ResidentKeyRequirement.REQUIRED117 else:118 select_criteria.resident_key = ResidentKeyRequirement.PREFERRED119 return select_criteria120 121 def authentication_options(122 self,123 user: User | None,124 usage: list[str],125 existing_options: dict[str, t.Any],126 ) -> dict[str, t.Any]:127 """128 :param user: User object - could be used to configure on a per-user basis.129 However, this can be null.130 :param usage: Either "first" or "secondary" (webauthn is being used as a second131 factor for authentication)132 :param existing_options: Currently filled in authentication options.133 134 Return a dict that will be sent in to135 py-webauthn generate_authentication_options136 """137 existing_options["user_verification"] = self.user_verification(user, usage)138 return existing_options139 140 def user_verification(141 self, user: User | None, usage: list[str]142 ) -> UserVerificationRequirement:143 """144 As part of signin - do we want/need user verification.145 This is called from /wan-signin and /wan-verify146 147 :param user: User object - could be used to configure on a per-user basis.148 Note that this may not be set on initial wan-signin.149 :param usage: List of "first", "secondary" (webauthn is being used as a second150 factor for authentication). Note that in the ``verify``/``reauthentication``151 case this list is derived from :py:data:`SECURITY_WAN_ALLOW_AS_VERIFY`152 153 """154 if "secondary" in usage:155 return UserVerificationRequirement.DISCOURAGED156 if current_app.config.get("SECURITY_WAN_ALLOW_AS_MULTI_FACTOR"):157 return UserVerificationRequirement.PREFERRED158 return UserVerificationRequirement.PREFERRED159 