Skip to content

Commit fd95c84

Browse files
committed
Include username in auth errors where possible to help disambiguate
Also handle file not found error when loading service account keys
1 parent 9343592 commit fd95c84

File tree

1 file changed

+33
-21
lines changed

1 file changed

+33
-21
lines changed

emailproxy.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,8 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
766766
try:
767767
client_secret = cryptographer.decrypt(client_secret_encrypted)
768768
except InvalidToken as e: # needed to avoid looping (we don't remove secrets on decryption failure)
769-
Log.error('Invalid password to decrypt', username, 'secret - aborting login:',
770-
Log.error_string(e))
769+
Log.error('Invalid password to decrypt `client_secret_encrypted` for account', username,
770+
'- aborting login:', Log.error_string(e))
771771
return False, '%s: Login failed - the password for account %s is incorrect' % (
772772
APP_NAME, username)
773773
else:
@@ -778,6 +778,7 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
778778
if not access_token or access_token_expiry - current_time < TOKEN_EXPIRY_MARGIN:
779779
if refresh_token:
780780
response = OAuth2Helper.refresh_oauth2_access_token(token_url, client_id, client_secret,
781+
username,
781782
cryptographer.decrypt(refresh_token))
782783

783784
access_token = response['access_token']
@@ -811,11 +812,11 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
811812
redirect_listen_address, username)
812813

813814
if not success:
814-
Log.info('Authorisation result error for', username, '- aborting login.', auth_result)
815+
Log.info('Authorisation result error for account', username, '- aborting login.', auth_result)
815816
return False, '%s: Login failed for account %s: %s' % (APP_NAME, username, auth_result)
816817

817818
if not oauth2_flow:
818-
Log.error('No `oauth2_flow` value specified for', username, '- aborting login')
819+
Log.error('No `oauth2_flow` value specified for account', username, '- aborting login')
819820
return (False, '%s: Incomplete config file entry found for account %s - please make sure an '
820821
'`oauth2_flow` value is specified when using a method that does not require a '
821822
'`permission_url`' % (APP_NAME, username))
@@ -843,8 +844,9 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
843844
if 'refresh_token' in response:
844845
config.set(username, 'refresh_token', cryptographer.encrypt(response['refresh_token']))
845846
elif permission_url: # ignore this situation with CCG/ROPCG/service account flows - it is expected
846-
Log.info('Warning: no refresh token returned for', username, '- you will need to re-authenticate',
847-
'each time the access token expires (does your `oauth2_scope` value allow `offline` use?)')
847+
Log.info('Warning: no refresh token returned for account', username, '- you will need to',
848+
're-authenticate each time the access token expires (does your `oauth2_scope` value allow',
849+
'`offline` use?)')
848850

849851
AppConfig.save()
850852

@@ -867,7 +869,7 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
867869

868870
AppConfig.save()
869871

870-
Log.info('Retrying login due to exception while refreshing OAuth 2.0 tokens for', username,
872+
Log.info('Retrying login due to exception while refreshing access token for account', username,
871873
'(attempt %d):' % (1 if has_access_token else 2), Log.error_string(e))
872874
return OAuth2Helper.get_oauth2_credentials(username, password, reload_remote_accounts=False)
873875

@@ -884,11 +886,12 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
884886
config.remove_option(username, 'refresh_token')
885887
AppConfig.save()
886888

887-
Log.info('Retrying login due to exception while decrypting OAuth 2.0 credentials for', username,
889+
Log.info('Retrying login due to exception while decrypting OAuth 2.0 credentials for account', username,
888890
'(invalid password):', Log.error_string(e))
889891
return OAuth2Helper.get_oauth2_credentials(username, password, reload_remote_accounts=False)
890892

891-
Log.error('Invalid password to decrypt', username, 'credentials - aborting login:', Log.error_string(e))
893+
Log.error('Invalid password to decrypt credentials for account', username, '- aborting login:',
894+
Log.error_string(e))
892895
return False, '%s: Login failed - the password for account %s is incorrect' % (APP_NAME, username)
893896

894897
except Exception as e:
@@ -897,7 +900,8 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
897900
# errors: URLError(OSError(50, 'Network is down'))) - access token 400 Bad Request HTTPErrors with messages
898901
# such as 'authorisation code was already redeemed' are caused by our support for simultaneous requests,
899902
# and will work from the next request; however, please report an issue if you encounter problems here
900-
Log.info('Caught exception while requesting OAuth 2.0 credentials for %s:' % username, Log.error_string(e))
903+
Log.info('Caught exception while requesting OAuth 2.0 credentials for account %s:' % username,
904+
Log.error_string(e))
901905
return False, '%s: Login failed for account %s - please check your internet connection and retry' % (
902906
APP_NAME, username)
903907

@@ -1014,7 +1018,7 @@ def get_oauth2_authorisation_code(permission_url, redirect_uri, redirect_listen_
10141018
# to improve no-GUI mode we also support the use of a local redirection receiver server or terminal
10151019
# entry to authenticate; this result is a timeout, wsgi request error/failure, or terminal auth ctrl+c
10161020
if 'expired' in data and data['expired']:
1017-
return False, 'No-GUI authorisation request failed or timed out'
1021+
return False, 'No-GUI authorisation request failed or timed out for account %s' % data['username']
10181022

10191023
if 'local_server_auth' in data:
10201024
threading.Thread(target=OAuth2Helper.start_redirection_receiver_server, args=(data,),
@@ -1031,13 +1035,16 @@ def get_oauth2_authorisation_code(permission_url, redirect_uri, redirect_listen_
10311035
authorisation_code = OAuth2Helper.oauth2_url_unescape(response['code'])
10321036
if authorisation_code:
10331037
return True, authorisation_code
1034-
return False, 'No OAuth 2.0 authorisation code returned'
1038+
return False, 'No OAuth 2.0 authorisation code returned for account %s' % data['username']
10351039
if 'error' in response:
1036-
message = 'OAuth 2.0 authorisation error: %s' % response['error']
1040+
message = 'OAuth 2.0 authorisation error for account %s: ' % data['username']
1041+
message += response['error']
10371042
message += '; %s' % response['error_description'] if 'error_description' in response else ''
10381043
return False, message
1039-
return False, 'OAuth 2.0 authorisation response has no code or error message'
1040-
return False, 'OAuth 2.0 authorisation response is missing or does not match `redirect_uri`'
1044+
return (False, 'OAuth 2.0 authorisation response for account %s has neither code nor error '
1045+
'message' % data['username'])
1046+
return (False, 'OAuth 2.0 authorisation response for account %s is missing or does not match'
1047+
'`redirect_uri`' % data['username'])
10411048

10421049
else: # not for this thread - put back into queue
10431050
response_queue_reference.put(data)
@@ -1070,7 +1077,7 @@ def get_oauth2_authorisation_tokens(token_url, redirect_uri, client_id, client_s
10701077
return json.loads(response)
10711078
except urllib.error.HTTPError as e:
10721079
e.message = json.loads(e.read())
1073-
Log.debug('Error requesting access token - received invalid response:', e.message)
1080+
Log.debug('Error requesting access token for account', username, '- received invalid response:', e.message)
10741081
raise e
10751082

10761083
# noinspection PyUnresolvedReferences
@@ -1087,12 +1094,17 @@ def get_service_account_authorisation_token(key_type, key_path_or_contents, oaut
10871094
'`python -m pip install requests google-auth`')
10881095

10891096
if key_type == 'file':
1090-
with open(key_path_or_contents) as key_file:
1091-
service_account = json.load(key_file)
1097+
try:
1098+
with open(key_path_or_contents) as key_file:
1099+
service_account = json.load(key_file)
1100+
except IOError as e:
1101+
raise FileNotFoundError('Unable to open service account key file %s for account %s',
1102+
(key_path_or_contents, username)) from e
10921103
elif key_type == 'key':
10931104
service_account = json.loads(key_path_or_contents)
10941105
else:
1095-
raise Exception('Service account key type not specified - `client_id` must be set to `file` or `key`')
1106+
raise Exception('Service account key type not specified for account %s - `client_id` must be set to '
1107+
'`file` or `key`' % username)
10961108

10971109
credentials = google.oauth2.service_account.Credentials.from_service_account_info(service_account)
10981110
credentials = credentials.with_scopes(oauth2_scope.split(' '))
@@ -1103,7 +1115,7 @@ def get_service_account_authorisation_token(key_type, key_path_or_contents, oaut
11031115
return {'access_token': credentials.token, 'expires_in': int(credentials.expiry.timestamp() - time.time())}
11041116

11051117
@staticmethod
1106-
def refresh_oauth2_access_token(token_url, client_id, client_secret, refresh_token):
1118+
def refresh_oauth2_access_token(token_url, client_id, client_secret, username, refresh_token):
11071119
"""Obtains a new access token from token_url using the given client_id, client_secret and refresh token,
11081120
returning a dict with 'access_token', 'expires_in', and 'refresh_token' on success; exception on failure"""
11091121
params = {'client_id': client_id, 'client_secret': client_secret, 'refresh_token': refresh_token,
@@ -1121,7 +1133,7 @@ def refresh_oauth2_access_token(token_url, client_id, client_secret, refresh_tok
11211133

11221134
except urllib.error.HTTPError as e:
11231135
e.message = json.loads(e.read())
1124-
Log.debug('Error refreshing access token - received invalid response:', e.message)
1136+
Log.debug('Error refreshing access token for account', username, '- received invalid response:', e.message)
11251137
if e.code == 400: # 400 Bad Request typically means re-authentication is required (token expired)
11261138
raise OAuth2Helper.TokenRefreshError from e
11271139
raise e

0 commit comments

Comments
 (0)