# noinspection PyPackageRequirements import wx from logbook import Logger import threading import time import base64 import json import config import webbrowser import eos.db from service.const import EsiLoginMethod, EsiSsoMode from eos.saveddata.ssocharacter import SsoCharacter from service.esiAccess import APIException import gui.globalEvents as GE from gui.ssoLogin import SsoLogin, SsoLoginServer from service.server import StoppableHTTPServer, AuthHandler from service.settings import EsiSettings from service.esiAccess import EsiAccess import gui.mainFrame from requests import Session pyfalog = Logger(__name__) class Esi(EsiAccess): _instance = None @classmethod def getInstance(cls): if cls._instance is None: cls._instance = Esi() return cls._instance def __init__(self): self.settings = EsiSettings.getInstance() super().__init__() # these will be set when needed self.httpd = None self.state = None self.ssoTimer = None self.implicitCharacter = None # until I can get around to making proper caching and modifications to said cache, storee deleted fittings here # so that we can easily hide them in the fitting browser self.fittings_deleted = set() # need these here to post events import gui.mainFrame # put this here to avoid loop self.mainFrame = gui.mainFrame.MainFrame.getInstance() def delSsoCharacter(self, id): char = eos.db.getSsoCharacter(id, config.getClientSecret()) # There is an issue in which the SSO character is not removed from any linked characters - a reference to the # sso character remains even though the SSO character is deleted which should have deleted the link. This is a # work around until we can figure out why. Manually delete SSOCharacter from all of it's characters for x in char.characters: x._Character__ssoCharacters.remove(char) eos.db.remove(char) wx.PostEvent(self.mainFrame, GE.SsoLogout(charID=id)) def getSsoCharacters(self): chars = eos.db.getSsoCharacters(config.getClientSecret()) return chars def getSsoCharacter(self, id): char = eos.db.getSsoCharacter(id, config.getClientSecret()) eos.db.commit() return char def getSkills(self, id): char = self.getSsoCharacter(id) resp = super().getSkills(char) return resp.json() def getSecStatus(self, id): char = self.getSsoCharacter(id) resp = super().getSecStatus(char) return resp.json() def getFittings(self, id): char = self.getSsoCharacter(id) resp = super().getFittings(char) return resp.json() def postFitting(self, id, json_str): # @todo: new fitting ID can be recovered from resp.data, char = self.getSsoCharacter(id) resp = super().postFitting(char, json_str) return resp def delFitting(self, id, fittingID): char = self.getSsoCharacter(id) super().delFitting(char, fittingID) self.fittings_deleted.add(fittingID) def login(self): # always start the local server if user is using client details. Otherwise, start only if they choose to do so. if self.settings.get('ssoMode') == EsiSsoMode.CUSTOM or self.settings.get('loginMode') == EsiLoginMethod.SERVER: with gui.ssoLogin.SsoLoginServer(6461 if self.settings.get('ssoMode') == EsiSsoMode.CUSTOM else 0) as dlg: dlg.ShowModal() else: with gui.ssoLogin.SsoLogin() as dlg: if dlg.ShowModal() == wx.ID_OK: self.handleLogin({'SSOInfo': [dlg.ssoInfoCtrl.Value.strip()]}) def stopServer(self): pyfalog.debug("Stopping Server") if self.httpd: self.httpd.stop() self.httpd = None def startServer(self, port): # todo: break this out into two functions: starting the server, and getting the URI pyfalog.debug("Starting server") # we need this to ensure that the previous get_request finishes, and then the socket will close if self.httpd: self.stopServer() time.sleep(1) self.httpd = StoppableHTTPServer(('localhost', port), AuthHandler) port = self.httpd.socket.getsockname()[1] self.serverThread = threading.Thread(target=self.httpd.serve, args=(self.handleServerLogin,)) self.serverThread.name = "SsoCallbackServer" self.serverThread.daemon = True self.serverThread.start() return 'http://localhost:{}'.format(port) def handleLogin(self, message): # we already have authenticated stuff for the auto mode if self.settings.get('ssoMode') == EsiSsoMode.AUTO: ssoInfo = message['SSOInfo'][0] auth_response = json.loads(base64.b64decode(ssoInfo)) else: # otherwise, we need to fetch the information auth_response = self.auth(message['code'][0]) res = self._session.get( self.oauth_verify, headers=self.get_oauth_header(auth_response['access_token']) ) if res.status_code != 200: raise APIException( self.oauth_verify, res.status_code, res.json() ) cdata = res.json() currentCharacter = self.getSsoCharacter(cdata['CharacterName']) if currentCharacter is None: currentCharacter = SsoCharacter(cdata['CharacterID'], cdata['CharacterName'], config.getClientSecret()) Esi.update_token(currentCharacter, auth_response) eos.db.save(currentCharacter) wx.PostEvent(self.mainFrame, GE.SsoLogin(character=currentCharacter)) # get (endpoint, char, data?) def handleServerLogin(self, message): if not message: raise Exception("Could not parse out querystring parameters.") if message['state'][0] != self.state: pyfalog.warn("OAUTH state mismatch") raise Exception("OAUTH State Mismatch.") pyfalog.debug("Handling SSO login with: {0}", message) self.handleLogin(message)