codekingpro/portable-devtools
114k
1# Copyright 2021 Google LLC2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15""" Challenges for reauthentication.16"""17 18import abc19import base6420import getpass21import sys22 23from google.auth import _helpers24from google.auth import exceptions25 26 27REAUTH_ORIGIN = "https://accounts.google.com"28SAML_CHALLENGE_MESSAGE = (29 "Please run `gcloud auth login` to complete reauthentication with SAML."30)31 32 33def get_user_password(text):34 """Get password from user.35 36 Override this function with a different logic if you are using this library37 outside a CLI.38 39 Args:40 text (str): message for the password prompt.41 42 Returns:43 str: password string.44 """45 return getpass.getpass(text)46 47 48class ReauthChallenge(metaclass=abc.ABCMeta):49 """Base class for reauth challenges."""50 51 @property52 @abc.abstractmethod53 def name(self): # pragma: NO COVER54 """Returns the name of the challenge."""55 raise NotImplementedError("name property must be implemented")56 57 @property58 @abc.abstractmethod59 def is_locally_eligible(self): # pragma: NO COVER60 """Returns true if a challenge is supported locally on this machine."""61 raise NotImplementedError("is_locally_eligible property must be implemented")62 63 @abc.abstractmethod64 def obtain_challenge_input(self, metadata): # pragma: NO COVER65 """Performs logic required to obtain credentials and returns it.66 67 Args:68 metadata (Mapping): challenge metadata returned in the 'challenges' field in69 the initial reauth request. Includes the 'challengeType' field70 and other challenge-specific fields.71 72 Returns:73 response that will be send to the reauth service as the content of74 the 'proposalResponse' field in the request body. Usually a dict75 with the keys specific to the challenge. For example,76 ``{'credential': password}`` for password challenge.77 """78 raise NotImplementedError("obtain_challenge_input method must be implemented")79 80 81class PasswordChallenge(ReauthChallenge):82 """Challenge that asks for user's password."""83 84 @property85 def name(self):86 return "PASSWORD"87 88 @property89 def is_locally_eligible(self):90 return True91 92 @_helpers.copy_docstring(ReauthChallenge)93 def obtain_challenge_input(self, unused_metadata):94 passwd = get_user_password("Please enter your password:")95 if not passwd:96 passwd = " " # avoid the server crashing in case of no password :D97 return {"credential": passwd}98 99 100class SecurityKeyChallenge(ReauthChallenge):101 """Challenge that asks for user's security key touch."""102 103 @property104 def name(self):105 return "SECURITY_KEY"106 107 @property108 def is_locally_eligible(self):109 return True110 111 @_helpers.copy_docstring(ReauthChallenge)112 def obtain_challenge_input(self, metadata):113 try:114 import pyu2f.convenience.authenticator # type: ignore115 import pyu2f.errors # type: ignore116 import pyu2f.model # type: ignore117 except ImportError:118 raise exceptions.ReauthFailError(119 "pyu2f dependency is required to use Security key reauth feature. "120 "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`."121 )122 sk = metadata["securityKey"]123 challenges = sk["challenges"]124 # Read both 'applicationId' and 'relyingPartyId', if they are the same, use125 # applicationId, if they are different, use relyingPartyId first and retry126 # with applicationId127 application_id = sk["applicationId"]128 relying_party_id = sk["relyingPartyId"]129 130 if application_id != relying_party_id:131 application_parameters = [relying_party_id, application_id]132 else:133 application_parameters = [application_id]134 135 challenge_data = []136 for c in challenges:137 kh = c["keyHandle"].encode("ascii")138 key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh)))139 challenge = c["challenge"].encode("ascii")140 challenge = base64.urlsafe_b64decode(challenge)141 challenge_data.append({"key": key, "challenge": challenge})142 143 # Track number of tries to suppress error message until all application_parameters144 # are tried.145 tries = 0146 for app_id in application_parameters:147 try:148 tries += 1149 api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator(150 REAUTH_ORIGIN151 )152 response = api.Authenticate(153 app_id, challenge_data, print_callback=sys.stderr.write154 )155 return {"securityKey": response}156 except pyu2f.errors.U2FError as e:157 if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE:158 # Only show error if all app_ids have been tried159 if tries == len(application_parameters):160 sys.stderr.write("Ineligible security key.\n")161 return None162 continue163 if e.code == pyu2f.errors.U2FError.TIMEOUT:164 sys.stderr.write(165 "Timed out while waiting for security key touch.\n"166 )167 else:168 raise e169 except pyu2f.errors.PluginError as e:170 sys.stderr.write("Plugin error: {}.\n".format(e))171 continue172 except pyu2f.errors.NoDeviceFoundError:173 sys.stderr.write("No security key found.\n")174 return None175 176 177class SamlChallenge(ReauthChallenge):178 """Challenge that asks the users to browse to their ID Providers.179 180 Currently SAML challenge is not supported. When obtaining the challenge181 input, exception will be raised to instruct the users to run182 `gcloud auth login` for reauthentication.183 """184 185 @property186 def name(self):187 return "SAML"188 189 @property190 def is_locally_eligible(self):191 return True192 193 def obtain_challenge_input(self, metadata):194 # Magic Arch has not fully supported returning a proper dedirect URL195 # for programmatic SAML users today. So we error our here and request196 # users to use gcloud to complete a login.197 raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE)198 199 200AVAILABLE_CHALLENGES = {201 challenge.name: challenge202 for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()]203}204 