|
6 | 6 | __author__ = 'Simon Robinson'
|
7 | 7 | __copyright__ = 'Copyright (c) 2023 Simon Robinson'
|
8 | 8 | __license__ = 'Apache 2.0'
|
9 |
| -__version__ = '2023-07-12' # ISO 8601 (YYYY-MM-DD) |
| 9 | +__version__ = '2023-07-13' # ISO 8601 (YYYY-MM-DD) |
10 | 10 |
|
11 | 11 | import abc
|
12 | 12 | import argparse
|
@@ -539,8 +539,11 @@ def _save_cache(cache_store_identifier, output_config_parser):
|
539 | 539 |
|
540 | 540 |
|
541 | 541 | class OAuth2Helper:
|
| 542 | + class TokenRefreshError(Exception): |
| 543 | + pass |
| 544 | + |
542 | 545 | @staticmethod
|
543 |
| - def get_oauth2_credentials(username, password, recurse_retries=True): |
| 546 | + def get_oauth2_credentials(username, password, reload_remote_accounts=True): |
544 | 547 | """Using the given username (i.e., email address) and password, reads account details from AppConfig and
|
545 | 548 | handles OAuth 2.0 token request and renewal, saving the updated details back to AppConfig (or removing them
|
546 | 549 | if invalid). Returns either (True, '[OAuth2 string for authentication]') or (False, '[Error message]')"""
|
@@ -602,9 +605,9 @@ def get_account_with_catch_all_fallback(option):
|
602 | 605 | refresh_token = config.get(username, 'refresh_token', fallback=None)
|
603 | 606 |
|
604 | 607 | # try reloading remotely cached tokens if possible
|
605 |
| - if not access_token and CACHE_STORE != CONFIG_FILE_PATH and recurse_retries: |
| 608 | + if not access_token and CACHE_STORE != CONFIG_FILE_PATH and reload_remote_accounts: |
606 | 609 | AppConfig.reload()
|
607 |
| - return OAuth2Helper.get_oauth2_credentials(username, password, recurse_retries=False) |
| 610 | + return OAuth2Helper.get_oauth2_credentials(username, password, reload_remote_accounts=False) |
608 | 611 |
|
609 | 612 | # we hash locally-stored tokens with the given password
|
610 | 613 | if not token_salt:
|
@@ -697,35 +700,34 @@ def get_account_with_catch_all_fallback(option):
|
697 | 700 | oauth2_string = OAuth2Helper.construct_oauth2_string(username, access_token)
|
698 | 701 | return True, oauth2_string
|
699 | 702 |
|
700 |
| - except InvalidToken as e: |
701 |
| - # we always remove the access token - we can easily request another using the refresh token |
| 703 | + except OAuth2Helper.TokenRefreshError as e: |
| 704 | + # always clear access tokens - can easily request another via the refresh token (with no user interaction) |
702 | 705 | has_access_token = True if config.get(username, 'access_token', fallback=None) else False
|
703 | 706 | config.remove_option(username, 'access_token')
|
704 | 707 | config.remove_option(username, 'access_token_expiry')
|
705 |
| - if has_access_token: |
706 |
| - AppConfig.save() |
707 | 708 |
|
708 |
| - # if invalid details are the reason for failure we remove our cached version and re-authenticate - this can |
709 |
| - # be disabled by a configuration setting, but note that we always remove credentials on 400 Bad Request |
710 |
| - if e.args == (400, APP_PACKAGE) or AppConfig.globals().getboolean('delete_account_token_on_password_error', |
711 |
| - fallback=True): |
712 |
| - # try authentication again with no cached details - note that if we have just removed an invalid access |
713 |
| - # token this will trigger an unnecessary reload from the cache store, but it is worth doing this to |
714 |
| - # avoid an unnecessary re-authentication request |
715 |
| - recurse_retries = True |
716 |
| - |
717 |
| - # if this is already a second attempt, remove the refresh token as well, and force re-authentication |
718 |
| - if not has_access_token: |
719 |
| - config.remove_option(username, 'token_salt') |
720 |
| - config.remove_option(username, 'refresh_token') |
721 |
| - AppConfig.save() |
722 |
| - else: |
723 |
| - recurse_retries = has_access_token # no need to recurse if we are trying the same credentials again |
| 709 | + if not has_access_token: |
| 710 | + # if this is already a second failure, remove the refresh token as well, and force re-authentication |
| 711 | + config.remove_option(username, 'token_salt') |
| 712 | + config.remove_option(username, 'refresh_token') |
| 713 | + |
| 714 | + AppConfig.save() |
| 715 | + |
| 716 | + Log.info('Retrying login due to exception while refreshing OAuth 2.0 tokens for', username, |
| 717 | + '(attempt %d):' % (1 if has_access_token else 2), Log.error_string(e)) |
| 718 | + return OAuth2Helper.get_oauth2_credentials(username, password, reload_remote_accounts=False) |
| 719 | + |
| 720 | + except InvalidToken as e: |
| 721 | + if AppConfig.globals().getboolean('delete_account_token_on_password_error', fallback=True): |
| 722 | + config.remove_option(username, 'access_token') |
| 723 | + config.remove_option(username, 'access_token_expiry') |
| 724 | + config.remove_option(username, 'token_salt') |
| 725 | + config.remove_option(username, 'refresh_token') |
| 726 | + AppConfig.save() |
724 | 727 |
|
725 |
| - if recurse_retries: |
726 |
| - Log.info('Retrying login due to exception while requesting OAuth 2.0 credentials for %s:' % username, |
727 |
| - Log.error_string(e)) |
728 |
| - return OAuth2Helper.get_oauth2_credentials(username, password, recurse_retries=has_access_token) |
| 728 | + Log.info('Retrying login due to exception while decrypting OAuth 2.0 credentials for', username, |
| 729 | + '(invalid password):', Log.error_string(e)) |
| 730 | + return OAuth2Helper.get_oauth2_credentials(username, password, reload_remote_accounts=False) |
729 | 731 |
|
730 | 732 | Log.error('Invalid password to decrypt', username, 'credentials - aborting login:', Log.error_string(e))
|
731 | 733 | return False, '%s: Login failed - the password for account %s is incorrect' % (APP_NAME, username)
|
@@ -930,8 +932,8 @@ def refresh_oauth2_access_token(token_url, client_id, client_secret, refresh_tok
|
930 | 932 | except urllib.error.HTTPError as e:
|
931 | 933 | e.message = json.loads(e.read())
|
932 | 934 | Log.debug('Error refreshing access token - received invalid response:', e.message)
|
933 |
| - if e.code == 400: # 400 Bad Request typically means re-authentication is required (refresh token expired) |
934 |
| - raise InvalidToken(e.code, APP_PACKAGE) from e |
| 935 | + if e.code == 400: # 400 Bad Request typically means re-authentication is required (token expired) |
| 936 | + raise OAuth2Helper.TokenRefreshError from e |
935 | 937 | raise e
|
936 | 938 |
|
937 | 939 | @staticmethod
|
|
0 commit comments