mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-06-04 09:31:12 +03:00
compnanion.py: Add more logging to better trace auth/CAPI flow
This commit is contained in:
parent
09add21a1a
commit
5c3b8b9927
58
companion.py
58
companion.py
@ -138,25 +138,33 @@ def listify(thing):
|
|||||||
class ServerError(Exception):
|
class ServerError(Exception):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
# Raised when cannot contact the Companion API server
|
# Raised when cannot contact the Companion API server
|
||||||
self.args = args if args else (_('Error: Frontier server is down'),)
|
self.args = args
|
||||||
|
if not args:
|
||||||
|
self.args = (_('Error: Frontier server is down'),)
|
||||||
|
|
||||||
|
|
||||||
class ServerLagging(Exception):
|
class ServerLagging(Exception):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
# Raised when Companion API server is returning old data, e.g. when the servers are too busy
|
# Raised when Companion API server is returning old data, e.g. when the servers are too busy
|
||||||
self.args = args if args else (_('Error: Frontier server is lagging'),)
|
self.args = args
|
||||||
|
if not args:
|
||||||
|
self.args = (_('Error: Frontier server is lagging'),)
|
||||||
|
|
||||||
|
|
||||||
class SKUError(Exception):
|
class SKUError(Exception):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
# Raised when the Companion API server thinks that the user has not purchased E:D
|
# Raised when the Companion API server thinks that the user has not purchased E:D
|
||||||
# i.e. doesn't have the correct 'SKU'
|
# i.e. doesn't have the correct 'SKU'
|
||||||
self.args = args if args else (_('Error: Frontier server SKU problem'),)
|
self.args = args
|
||||||
|
if not args:
|
||||||
|
self.args = (_('Error: Frontier server SKU problem'),)
|
||||||
|
|
||||||
|
|
||||||
class CredentialsError(Exception):
|
class CredentialsError(Exception):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
self.args = args if args else (_('Error: Invalid Credentials'),)
|
self.args = args
|
||||||
|
if not args:
|
||||||
|
self.args = (_('Error: Invalid Credentials'),)
|
||||||
|
|
||||||
|
|
||||||
class CmdrError(Exception):
|
class CmdrError(Exception):
|
||||||
@ -164,7 +172,9 @@ class CmdrError(Exception):
|
|||||||
# Raised when the user has multiple accounts and the username/password setting is not for
|
# Raised when the user has multiple accounts and the username/password setting is not for
|
||||||
# the account they're currently playing OR the user has reset their Cmdr and the Companion API
|
# the account they're currently playing OR the user has reset their Cmdr and the Companion API
|
||||||
# server is still returning data for the old Cmdr
|
# server is still returning data for the old Cmdr
|
||||||
self.args = args if args else (_('Error: Wrong Cmdr'),)
|
self.args = args
|
||||||
|
if not args:
|
||||||
|
self.args = (_('Error: Wrong Cmdr'),)
|
||||||
|
|
||||||
|
|
||||||
class Auth(object):
|
class Auth(object):
|
||||||
@ -176,12 +186,19 @@ class Auth(object):
|
|||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
# Try refresh token. Returns new refresh token if successful, otherwise makes new authorization request.
|
# Try refresh token. Returns new refresh token if successful, otherwise makes new authorization request.
|
||||||
|
logger.debug(f'Trying for "{self.cmdr}"')
|
||||||
|
|
||||||
self.verifier = None
|
self.verifier = None
|
||||||
cmdrs = config.get('cmdrs')
|
cmdrs = config.get('cmdrs')
|
||||||
|
logger.debug(f'Cmdrs: {cmdrs}')
|
||||||
|
|
||||||
idx = cmdrs.index(self.cmdr)
|
idx = cmdrs.index(self.cmdr)
|
||||||
|
logger.debug(f'idx = {idx}')
|
||||||
|
|
||||||
tokens = config.get('fdev_apikeys') or []
|
tokens = config.get('fdev_apikeys') or []
|
||||||
tokens = tokens + [''] * (len(cmdrs) - len(tokens))
|
tokens = tokens + [''] * (len(cmdrs) - len(tokens))
|
||||||
if tokens[idx]:
|
if tokens[idx]:
|
||||||
|
logger.debug('We have a refresh token for that idx')
|
||||||
try:
|
try:
|
||||||
data = {
|
data = {
|
||||||
'grant_type': 'refresh_token',
|
'grant_type': 'refresh_token',
|
||||||
@ -189,6 +206,7 @@ class Auth(object):
|
|||||||
'refresh_token': tokens[idx],
|
'refresh_token': tokens[idx],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug('Attempting refresh with Frontier...')
|
||||||
r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout)
|
r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout)
|
||||||
if r.status_code == requests.codes.ok:
|
if r.status_code == requests.codes.ok:
|
||||||
data = r.json()
|
data = r.json()
|
||||||
@ -214,6 +232,7 @@ class Auth(object):
|
|||||||
s = random.SystemRandom().getrandbits(8 * 32)
|
s = random.SystemRandom().getrandbits(8 * 32)
|
||||||
self.state = self.base64_url_encode(s.to_bytes(32, byteorder='big'))
|
self.state = self.base64_url_encode(s.to_bytes(32, byteorder='big'))
|
||||||
# Won't work under IE: https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
|
# Won't work under IE: https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
|
||||||
|
logger.debug(f'Trying auth from scratch for Commander "{self.cmdr}"')
|
||||||
webbrowser.open(
|
webbrowser.open(
|
||||||
'{server_auth}{url_auth}?response_type=code&audience=frontier&scope=capi&client_id={client_id}&code_challenge={challenge}&code_challenge_method=S256&state={state}&redirect_uri={redirect}'.format( # noqa: E501 # I cant make this any shorter
|
'{server_auth}{url_auth}?response_type=code&audience=frontier&scope=capi&client_id={client_id}&code_challenge={challenge}&code_challenge_method=S256&state={state}&redirect_uri={redirect}'.format( # noqa: E501 # I cant make this any shorter
|
||||||
server_auth=SERVER_AUTH,
|
server_auth=SERVER_AUTH,
|
||||||
@ -226,10 +245,11 @@ class Auth(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def authorize(self, payload):
|
def authorize(self, payload):
|
||||||
|
logger.debug('Checking oAuth authorization callback')
|
||||||
# Handle OAuth authorization code callback.
|
# Handle OAuth authorization code callback.
|
||||||
# Returns access token if successful, otherwise raises CredentialsError
|
# Returns access token if successful, otherwise raises CredentialsError
|
||||||
if '?' not in payload:
|
if '?' not in payload:
|
||||||
logger.error(f'Frontier CAPI Auth: Malformed response\n{payload}\n')
|
logger.error(f'Frontier CAPI Auth: Malformed response (no "?" in payload)\n{payload}\n')
|
||||||
raise CredentialsError('malformed payload') # Not well formed
|
raise CredentialsError('malformed payload') # Not well formed
|
||||||
|
|
||||||
data = urllib.parse.parse_qs(payload[(payload.index('?') + 1):])
|
data = urllib.parse.parse_qs(payload[(payload.index('?') + 1):])
|
||||||
@ -238,13 +258,14 @@ class Auth(object):
|
|||||||
raise CredentialsError('Unexpected response from authorization {!r}'.format(payload)) # Unexpected reply
|
raise CredentialsError('Unexpected response from authorization {!r}'.format(payload)) # Unexpected reply
|
||||||
|
|
||||||
if not data.get('code'):
|
if not data.get('code'):
|
||||||
logger.error(f'Frontier CAPI Auth: Negative response\n{payload}\n')
|
logger.error(f'Frontier CAPI Auth: Negative response (no "code" in returned data)\n{payload}\n')
|
||||||
error = next(
|
error = next(
|
||||||
(data[k] for k in ('error_description', 'error', 'message') if k in data), ('<unknown error>',)
|
(data[k] for k in ('error_description', 'error', 'message') if k in data), ('<unknown error>',)
|
||||||
)
|
)
|
||||||
raise CredentialsError('Error: {!r}'.format(error)[0])
|
raise CredentialsError('Error: {!r}'.format(error)[0])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Got code, posting it back...')
|
||||||
r = None
|
r = None
|
||||||
data = {
|
data = {
|
||||||
'grant_type': 'authorization_code',
|
'grant_type': 'authorization_code',
|
||||||
@ -311,23 +332,29 @@ class Session(object):
|
|||||||
def login(self, cmdr=None, is_beta=None):
|
def login(self, cmdr=None, is_beta=None):
|
||||||
# Returns True if login succeeded, False if re-authorization initiated.
|
# Returns True if login succeeded, False if re-authorization initiated.
|
||||||
if not CLIENT_ID:
|
if not CLIENT_ID:
|
||||||
|
logger.error('CLIENT_ID is None')
|
||||||
raise CredentialsError('cannot login without a valid Client ID')
|
raise CredentialsError('cannot login without a valid Client ID')
|
||||||
|
|
||||||
|
# TODO: WTF is the intent behind this logic ?
|
||||||
if not cmdr or is_beta is None:
|
if not cmdr or is_beta is None:
|
||||||
# Use existing credentials
|
# Use existing credentials
|
||||||
if not self.credentials:
|
if not self.credentials:
|
||||||
|
logger.error('self.credentials is None')
|
||||||
raise CredentialsError('Missing credentials') # Shouldn't happen
|
raise CredentialsError('Missing credentials') # Shouldn't happen
|
||||||
|
|
||||||
elif self.state == Session.STATE_OK:
|
elif self.state == Session.STATE_OK:
|
||||||
|
logger.debug('already logged in')
|
||||||
return True # already logged in
|
return True # already logged in
|
||||||
|
|
||||||
else:
|
else:
|
||||||
credentials = {'cmdr': cmdr, 'beta': is_beta}
|
credentials = {'cmdr': cmdr, 'beta': is_beta}
|
||||||
if self.credentials == credentials and self.state == Session.STATE_OK:
|
if self.credentials == credentials and self.state == Session.STATE_OK:
|
||||||
|
logger.debug('already logged in (is_beta)')
|
||||||
return True # already logged in
|
return True # already logged in
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# changed account or retrying login during auth
|
# changed account or retrying login during auth
|
||||||
|
logger.debug('changed account or retrying login during auth')
|
||||||
self.close()
|
self.close()
|
||||||
self.credentials = credentials
|
self.credentials = credentials
|
||||||
|
|
||||||
@ -337,45 +364,55 @@ class Session(object):
|
|||||||
|
|
||||||
access_token = self.auth.refresh()
|
access_token = self.auth.refresh()
|
||||||
if access_token:
|
if access_token:
|
||||||
|
logger.debug('We have an access_token')
|
||||||
self.auth = None
|
self.auth = None
|
||||||
self.start(access_token)
|
self.start(access_token)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
logger.debug('We do NOT have an access_token')
|
||||||
self.state = Session.STATE_AUTH
|
self.state = Session.STATE_AUTH
|
||||||
return False
|
return False
|
||||||
# Wait for callback
|
# Wait for callback
|
||||||
|
|
||||||
# Callback from protocol handler
|
# Callback from protocol handler
|
||||||
def auth_callback(self):
|
def auth_callback(self):
|
||||||
|
logger.debug('Handling callback from edmc:// handler')
|
||||||
if self.state != Session.STATE_AUTH:
|
if self.state != Session.STATE_AUTH:
|
||||||
# Shouldn't be getting a callback
|
# Shouldn't be getting a callback
|
||||||
|
logger.debug('Got an auth callback while not doing auth')
|
||||||
raise CredentialsError('Got an auth callback while not doing auth')
|
raise CredentialsError('Got an auth callback while not doing auth')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Trying authorize with payload from handler')
|
||||||
self.start(self.auth.authorize(protocolhandler.lastpayload))
|
self.start(self.auth.authorize(protocolhandler.lastpayload))
|
||||||
self.auth = None
|
self.auth = None
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.exception('Failed, will try again next login or query')
|
||||||
self.state = Session.STATE_INIT # Will try to authorize again on next login or query
|
self.state = Session.STATE_INIT # Will try to authorize again on next login or query
|
||||||
self.auth = None
|
self.auth = None
|
||||||
raise # Bad thing happened
|
raise # Bad thing happened
|
||||||
|
|
||||||
def start(self, access_token):
|
def start(self, access_token):
|
||||||
|
logger.debug('Starting session')
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.headers['Authorization'] = 'Bearer {}'.format(access_token)
|
self.session.headers['Authorization'] = 'Bearer {}'.format(access_token)
|
||||||
self.session.headers['User-Agent'] = USER_AGENT
|
self.session.headers['User-Agent'] = USER_AGENT
|
||||||
self.state = Session.STATE_OK
|
self.state = Session.STATE_OK
|
||||||
|
|
||||||
def query(self, endpoint):
|
def query(self, endpoint):
|
||||||
|
logger.debug(f'Performing query for endpoint "{endpoint}"')
|
||||||
if self.state == Session.STATE_INIT:
|
if self.state == Session.STATE_INIT:
|
||||||
if self.login():
|
if self.login():
|
||||||
return self.query(endpoint)
|
return self.query(endpoint)
|
||||||
|
|
||||||
elif self.state == Session.STATE_AUTH:
|
elif self.state == Session.STATE_AUTH:
|
||||||
|
logger.error('cannot make a query when unauthorized')
|
||||||
raise CredentialsError('cannot make a query when unauthorized')
|
raise CredentialsError('cannot make a query when unauthorized')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Trying...')
|
||||||
r = self.session.get(self.server + endpoint, timeout=timeout)
|
r = self.session.get(self.server + endpoint, timeout=timeout)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -383,6 +420,7 @@ class Session(object):
|
|||||||
raise ServerError(f'unable to get endpoint {endpoint}') from e
|
raise ServerError(f'unable to get endpoint {endpoint}') from e
|
||||||
|
|
||||||
if r.url.startswith(SERVER_AUTH):
|
if r.url.startswith(SERVER_AUTH):
|
||||||
|
logger.debug('Redirected back to Auth Server')
|
||||||
# Redirected back to Auth server - force full re-authentication
|
# Redirected back to Auth server - force full re-authentication
|
||||||
self.dump(r)
|
self.dump(r)
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
@ -392,6 +430,7 @@ class Session(object):
|
|||||||
|
|
||||||
elif 500 <= r.status_code < 600:
|
elif 500 <= r.status_code < 600:
|
||||||
# Server error. Typically 500 "Internal Server Error" if server is down
|
# Server error. Typically 500 "Internal Server Error" if server is down
|
||||||
|
logger.debug('500 status back from CAPI')
|
||||||
self.dump(r)
|
self.dump(r)
|
||||||
raise ServerError('Received error {} from server'.format(r.status_code))
|
raise ServerError('Received error {} from server'.format(r.status_code))
|
||||||
|
|
||||||
@ -405,13 +444,14 @@ class Session(object):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication
|
if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication
|
||||||
|
logger.error('Frontier CAPI Auth: query failed after refresh')
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
self.retrying = False
|
self.retrying = False
|
||||||
self.login()
|
self.login()
|
||||||
logger.error('Frontier CAPI Auth: query failed after refresh')
|
|
||||||
raise CredentialsError('query failed after refresh') from e
|
raise CredentialsError('query failed after refresh') from e
|
||||||
|
|
||||||
elif self.login(): # Maybe our token expired. Re-authorize in any case
|
elif self.login(): # Maybe our token expired. Re-authorize in any case
|
||||||
|
logger.debug('Maybe our token expired.')
|
||||||
self.retrying = True
|
self.retrying = True
|
||||||
return self.query(endpoint)
|
return self.query(endpoint)
|
||||||
|
|
||||||
@ -422,6 +462,7 @@ class Session(object):
|
|||||||
|
|
||||||
self.retrying = False
|
self.retrying = False
|
||||||
if 'timestamp' not in data:
|
if 'timestamp' not in data:
|
||||||
|
logger.debug('timestamp not in data, adding from response headers')
|
||||||
data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']))
|
data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@ -467,6 +508,7 @@ class Session(object):
|
|||||||
self.session = None
|
self.session = None
|
||||||
|
|
||||||
def invalidate(self):
|
def invalidate(self):
|
||||||
|
logger.debug('Forcing a full re-authentication')
|
||||||
# Force a full re-authentication
|
# Force a full re-authentication
|
||||||
self.close()
|
self.close()
|
||||||
Auth.invalidate(self.credentials['cmdr'])
|
Auth.invalidate(self.credentials['cmdr'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user