diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 03dc1744..5361f84e 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -28,7 +28,7 @@ jobs: # based on. - name: Checkout head commits # https://github.com/actions/checkout - uses: actions/checkout@v2.3.5 + uses: actions/checkout@v2.4.0 #with: #ref: ${{github.head.sha}} #repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 8c7f54b3..021b9109 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - name: Set up Python 3.9 diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 06c1c027..05e01364 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -16,7 +16,7 @@ jobs: shell: powershell steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - uses: actions/setup-python@v2.2.2 with: python-version: "3.9.7" diff --git a/ChangeLog.md b/ChangeLog.md index b4f5a1f8..7c2b8ad3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,16 @@ produce the Windows executables and installer. --- +Release 5.2.1 +=== + +This release primarily addresses the issue of the program asking for +Frontier authorization much too often. + +* Actually utilise the Frontier Refresh Token when the CAPI response is + "Unauthorized". The re-factoring of this code to make CAPI queries + threaded inadvertently prevented this. + Release 5.2.0 === diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 35d2c58a..24453a47 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -151,6 +151,12 @@ if __name__ == '__main__': # noqa: C901 action='store_true' ) + parser.add_argument( + '--capi-use-debug-access-token', + help='Load a debug Access Token from disk (from config.app_dir_pathapp_dir_path / access_token.txt)', + action='store_true' + ) + parser.add_argument( '--eddn-url', help='Specify an alternate EDDN upload URL', @@ -170,6 +176,11 @@ if __name__ == '__main__': # noqa: C901 logger.info('Pretending CAPI is down') conf_module.capi_pretend_down = True + if args.capi_use_debug_access_token: + import config as conf_module + with open(conf_module.config.app_dir_path / 'access_token.txt', 'r') as at: + conf_module.capi_debug_access_token = at.readline().strip() + level_to_set: Optional[int] = None if args.trace or args.trace_on: level_to_set = logging.TRACE # type: ignore # it exists @@ -1159,6 +1170,17 @@ class AppWindow(object): # LANG: Frontier CAPI server error when fetching data self.status['text'] = _('Frontier CAPI server error') + except companion.CredentialsRequireRefresh: + # We need to 'close' the auth else it'll see STATE_OK and think login() isn't needed + companion.session.close() + # LANG: Frontier CAPI Access Token expired, trying to get a new one + self.status['text'] = _('CAPI: Refreshing access token...') + if companion.session.login(): + logger.debug('Initial query failed, but login() just worked, trying again...') + companion.session.retrying = True + self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event)) + return # early exit to avoid starting cooldown count + except companion.CredentialsError: companion.session.retrying = False companion.session.invalidate() diff --git a/companion.py b/companion.py index 1a811858..2682fd8a 100644 --- a/companion.py +++ b/companion.py @@ -265,6 +265,15 @@ class CredentialsError(Exception): self.args = (_('Error: Invalid Credentials'),) +class CredentialsRequireRefresh(Exception): + """Exception Class for CAPI credentials requiring refresh.""" + + def __init__(self, *args) -> None: + self.args = args + if not args: + self.args = ('CAPI: Requires refresh of Access Token',) + + class CmdrError(Exception): """Exception Class for CAPI Commander error. @@ -750,7 +759,13 @@ class Session(object): if conf_module.capi_pretend_down: raise ServerConnectionError(f'Pretending CAPI down: {capi_endpoint}') + if conf_module.capi_debug_access_token is not None: + self.requests_session.headers['Authorization'] = f'Bearer {conf_module.capi_debug_access_token}' # type: ignore # noqa: E501 + # This is one-shot + conf_module.capi_debug_access_token = None + r = self.requests_session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore + logger.trace_if('capi.worker', '... got result...') r.raise_for_status() # Typically 403 "Forbidden" on token expiry # May also fail here if token expired since response is empty @@ -772,9 +787,10 @@ class Session(object): self.dump(r) if r.status_code == 401: # CAPI doesn't think we're Auth'd + # TODO: This needs to try a REFRESH, not a full re-auth # No need for translation, we'll go straight into trying new Auth # and thus any message would be overwritten. - raise CredentialsError('Frontier CAPI said Auth required') from e + raise CredentialsRequireRefresh('Frontier CAPI said "unauthorized"') from e if r.status_code == 418: # "I'm a teapot" - used to signal maintenance # LANG: Frontier CAPI returned 418, meaning down for maintenance diff --git a/config.py b/config.py index d690da56..d50d5495 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.2.0' +_static_appversion = '5.2.1' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' @@ -46,6 +46,7 @@ debug_senders: List[str] = [] trace_on: List[str] = [] capi_pretend_down: bool = False +capi_debug_access_token: Optional[str] = None # This must be done here in order to avoid an import cycle with EDMCLogging. # Other code should use EDMCLogging.get_main_logger if os.getenv("EDMC_NO_UI"): diff --git a/monitor.py b/monitor.py index debba020..65119f7f 100644 --- a/monitor.py +++ b/monitor.py @@ -1210,6 +1210,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below } except KeyError: + # TODO: Log the exception details too, for some clue about *which* key logger.error(f"LoadoutEquipModule: {entry}") elif event_type == 'loadoutremovemodule': diff --git a/requirements-dev.txt b/requirements-dev.txt index 16d01f5a..4d2c0a0c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 flake8-docstrings==1.6.0 -isort==5.9.3 +isort==5.10.0 flake8-isort==4.1.1 flake8-json==21.7.0 flake8-noqa==1.2.0 @@ -21,7 +21,7 @@ safety==1.10.3 types-requests==2.25.11 # Code formatting tools -autopep8==1.5.7 +autopep8==1.6.0 # HTML changelogs grip==4.5.2 @@ -33,7 +33,7 @@ py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==6.0.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.1.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==302; sys_platform == 'win32'