blob: 0baff62e054e9bfd234d1c3f94e7f1be29fb8861 [file] [log] [blame]
arithmetic172882293fe2021-04-14 11:22:13 -07001# Copyright 2021 Google LLC
2#
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 at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# 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 and
13# limitations under the License.
14
15""" Challenges for reauthentication.
16"""
17
18import abc
19import base64
20import getpass
21import sys
22
arithmetic172882293fe2021-04-14 11:22:13 -070023from google.auth import _helpers
24from google.auth import exceptions
25
26
27REAUTH_ORIGIN = "https://accounts.google.com"
arithmetic172813aed5f2021-09-07 16:24:45 -070028SAML_CHALLENGE_MESSAGE = (
29 "Please run `gcloud auth login` to complete reauthentication with SAML."
30)
arithmetic172882293fe2021-04-14 11:22:13 -070031
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 library
37 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
Tres Seaver560cf1e2021-08-03 16:35:54 -040048class ReauthChallenge(object, metaclass=abc.ABCMeta):
arithmetic172882293fe2021-04-14 11:22:13 -070049 """Base class for reauth challenges."""
50
51 @property
52 @abc.abstractmethod
53 def name(self): # pragma: NO COVER
54 """Returns the name of the challenge."""
55 raise NotImplementedError("name property must be implemented")
56
57 @property
58 @abc.abstractmethod
59 def is_locally_eligible(self): # pragma: NO COVER
60 """Returns true if a challenge is supported locally on this machine."""
61 raise NotImplementedError("is_locally_eligible property must be implemented")
62
63 @abc.abstractmethod
64 def obtain_challenge_input(self, metadata): # pragma: NO COVER
65 """Performs logic required to obtain credentials and returns it.
66
67 Args:
68 metadata (Mapping): challenge metadata returned in the 'challenges' field in
69 the initial reauth request. Includes the 'challengeType' field
70 and other challenge-specific fields.
71
72 Returns:
73 response that will be send to the reauth service as the content of
74 the 'proposalResponse' field in the request body. Usually a dict
75 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 @property
85 def name(self):
86 return "PASSWORD"
87
88 @property
89 def is_locally_eligible(self):
90 return True
91
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 :D
97 return {"credential": passwd}
98
99
100class SecurityKeyChallenge(ReauthChallenge):
101 """Challenge that asks for user's security key touch."""
102
103 @property
104 def name(self):
105 return "SECURITY_KEY"
106
107 @property
108 def is_locally_eligible(self):
109 return True
110
111 @_helpers.copy_docstring(ReauthChallenge)
112 def obtain_challenge_input(self, metadata):
113 try:
114 import pyu2f.convenience.authenticator
115 import pyu2f.errors
116 import pyu2f.model
117 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 app_id = sk["applicationId"]
125
126 challenge_data = []
127 for c in challenges:
128 kh = c["keyHandle"].encode("ascii")
129 key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh)))
130 challenge = c["challenge"].encode("ascii")
131 challenge = base64.urlsafe_b64decode(challenge)
132 challenge_data.append({"key": key, "challenge": challenge})
133
134 try:
135 api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator(
136 REAUTH_ORIGIN
137 )
138 response = api.Authenticate(
139 app_id, challenge_data, print_callback=sys.stderr.write
140 )
141 return {"securityKey": response}
142 except pyu2f.errors.U2FError as e:
143 if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE:
144 sys.stderr.write("Ineligible security key.\n")
145 elif e.code == pyu2f.errors.U2FError.TIMEOUT:
146 sys.stderr.write("Timed out while waiting for security key touch.\n")
147 else:
148 raise e
149 except pyu2f.errors.NoDeviceFoundError:
150 sys.stderr.write("No security key found.\n")
151 return None
152
153
arithmetic172813aed5f2021-09-07 16:24:45 -0700154class SamlChallenge(ReauthChallenge):
155 """Challenge that asks the users to browse to their ID Providers.
156
157 Currently SAML challenge is not supported. When obtaining the challenge
158 input, exception will be raised to instruct the users to run
159 `gcloud auth login` for reauthentication.
160 """
161
162 @property
163 def name(self):
164 return "SAML"
165
166 @property
167 def is_locally_eligible(self):
168 return True
169
170 def obtain_challenge_input(self, metadata):
171 # Magic Arch has not fully supported returning a proper dedirect URL
172 # for programmatic SAML users today. So we error our here and request
173 # users to use gcloud to complete a login.
174 raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE)
175
176
arithmetic172882293fe2021-04-14 11:22:13 -0700177AVAILABLE_CHALLENGES = {
178 challenge.name: challenge
arithmetic172813aed5f2021-09-07 16:24:45 -0700179 for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()]
arithmetic172882293fe2021-04-14 11:22:13 -0700180}