From cb392e7e5f2bc5cb7ed089f3764a085e5033046d Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 8 Feb 2018 01:52:38 -0500 Subject: [PATCH] Rename a few things --- .../pyfaCrestPreferences.py | 8 +- gui/crestFittings.py | 22 +- gui/mainFrame.py | 8 +- service/crest.py | 281 ------------------ service/esi.py | 243 ++++++++++++++- service/port.py | 4 +- 6 files changed, 260 insertions(+), 306 deletions(-) delete mode 100644 service/crest.py diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py index f2dbe694d..f3da3dbeb 100644 --- a/gui/builtinPreferenceViews/pyfaCrestPreferences.py +++ b/gui/builtinPreferenceViews/pyfaCrestPreferences.py @@ -11,7 +11,7 @@ from service.settings import CRESTSettings # noinspection PyPackageRequirements from wx.lib.intctrl import IntCtrl -from service.crest import Crest +from service.esi import Esi class PFCrestPref(PreferenceView): @@ -119,16 +119,16 @@ class PFCrestPref(PreferenceView): def OnModeChange(self, event): self.settings.set('mode', event.GetInt()) self.ToggleProxySettings(self.settings.get('mode')) - Crest.restartService() + Esi.restartService() def OnServerChange(self, event): self.settings.set('server', event.GetInt()) - Crest.restartService() + Esi.restartService() def OnBtnApply(self, event): self.settings.set('clientID', self.inputClientID.GetValue().strip()) self.settings.set('clientSecret', self.inputClientSecret.GetValue().strip()) - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() sCrest.delAllCharacters() def ToggleProxySettings(self, mode): diff --git a/gui/crestFittings.py b/gui/crestFittings.py index fdd82312f..69e8b6085 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -16,7 +16,7 @@ import gui.globalEvents as GE from logbook import Logger import calendar -from service.crest import Crest, CrestModes +from service.esi import Esi, CrestModes pyfalog = Logger(__name__) @@ -30,7 +30,7 @@ class CrestFittings(wx.Frame): self.mainFrame = parent mainSizer = wx.BoxSizer(wx.VERTICAL) - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() characterSelectSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -91,7 +91,7 @@ class CrestFittings(wx.Frame): event.Skip() def updateCharList(self): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() chars = sCrest.getSsoCharacters() if len(chars) == 0: @@ -130,7 +130,7 @@ class CrestFittings(wx.Frame): return self.charChoice.GetClientData(selection) if selection is not None else None def fetchFittings(self, event): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() try: waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self) fittings = sCrest.getFittings(self.getActiveCharacter()) @@ -154,7 +154,7 @@ class CrestFittings(wx.Frame): self.mainFrame._openAfterImport(fits) def deleteFitting(self, event): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() selection = self.fitView.fitSelection if not selection: return @@ -181,7 +181,7 @@ class ExportToEve(wx.Frame): self.mainFrame = parent self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() mainSizer = wx.BoxSizer(wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -212,7 +212,7 @@ class ExportToEve(wx.Frame): self.Centre(wx.BOTH) def updateCharList(self): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() chars = sCrest.getSsoCharacters() if len(chars) == 0: @@ -256,7 +256,7 @@ class ExportToEve(wx.Frame): return self.statusbar.SetStatusText("Sending request and awaiting response", 1) - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() try: sFit = Fit.getInstance() @@ -320,7 +320,7 @@ class CrestMgmt(wx.Dialog): event.Skip() def popCharList(self): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() chars = sCrest.getSsoCharacters() self.lcCharacters.DeleteAllItems() @@ -335,7 +335,7 @@ class CrestMgmt(wx.Dialog): @staticmethod def addChar(event): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() uri = sCrest.startServer() webbrowser.open(uri) @@ -343,7 +343,7 @@ class CrestMgmt(wx.Dialog): item = self.lcCharacters.GetFirstSelected() if item > -1: charID = self.lcCharacters.GetItemData(item) - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() sCrest.delCrestCharacter(charID) self.popCharList() diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 46e376976..7cef8ea3f 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -82,8 +82,8 @@ import threading import webbrowser import wx.adv -from service.crest import Crest -from service.crest import CrestModes +from service.esi import Esi +from service.esi import CrestModes from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt disableOverrideEditor = False @@ -614,7 +614,7 @@ class MainFrame(wx.Frame): dlg.Show() def updateTitle(self, event): - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() char = sCrest.implicitCharacter if char: t = time.gmtime(char.eve.expires - time.time()) @@ -649,7 +649,7 @@ class MainFrame(wx.Frame): self.SetTitle(self.title) menu = self.GetMenuBar() - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() if type == CrestModes.IMPLICIT: menu.SetLabel(menu.ssoLoginId, "Login to EVE") diff --git a/service/crest.py b/service/crest.py deleted file mode 100644 index 2f85815b8..000000000 --- a/service/crest.py +++ /dev/null @@ -1,281 +0,0 @@ -# noinspection PyPackageRequirements -import wx -from logbook import Logger -import threading -import copy -import uuid -import time -import config -import base64 -import json - -import eos.db -from eos.enum import Enum -from eos.saveddata.ssocharacter import SsoCharacter -import gui.globalEvents as GE -from service.settings import CRESTSettings -from service.server import StoppableHTTPServer, AuthHandler -from service.pycrest.eve import EVE - -from .esi_security_proxy import EsiSecurityProxy -from esipy import EsiClient, EsiApp -from esipy.cache import FileCache -import os -import logging - - -pyfalog = Logger(__name__) - -server = "https://blitzmann.pythonanywhere.com" -cache_path = os.path.join(config.savePath, config.ESI_CACHE) - -if not os.path.exists(cache_path): - os.mkdir(cache_path) - -file_cache = FileCache(cache_path) - -esiRdy = threading.Event() - - -class Servers(Enum): - TQ = 0 - SISI = 1 - - -class CrestModes(Enum): - IMPLICIT = 0 - USER = 1 - -from utils.timer import Timer - - - -class Crest(object): - clientIDs = { - Servers.TQ : 'f9be379951c046339dc13a00e6be7704', - Servers.SISI: 'af87365240d644f7950af563b8418bad' - } - - # @todo: move this to settings - clientCallback = 'http://localhost:6461' - clientTest = True - - esiapp = None - esi_v1 = None - esi_v4 = None - - _instance = None - - @classmethod - def initEsiApp(cls): - with Timer("Main EsiApp") as t: - cls.esiapp = EsiApp(cache=file_cache) - with Timer('ESI v1') as t: - cls.esi_v1 = cls.esiapp.get_v1_swagger - with Timer('ESI v4') as t: - cls.esi_v4 = cls.esiapp.get_v4_swagger - - # esiRdy.set() - - @classmethod - def genEsiClient(cls, security=None): - return EsiClient( - security=EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) if security is None else security, - cache=file_cache, - headers={'User-Agent': 'pyfa esipy'} - ) - - @classmethod - def getInstance(cls): - if cls._instance is None: - cls._instance = Crest() - - return cls._instance - - @classmethod - def restartService(cls): - # This is here to reseed pycrest values when changing preferences - # We first stop the server n case one is running, as creating a new - # instance doesn't do this. - if cls._instance.httpd: - cls._instance.stopServer() - cls._instance = Crest() - cls._instance.mainFrame.updateCrestMenus(type=cls._instance.settings.get('mode')) - return cls._instance - - def __init__(self): - """ - A note on login/logout events: the character login events happen - whenever a characters is logged into via the SSO, regardless of mod. - However, the mode should be send as an argument. Similarily, - the Logout even happens whenever the character is deleted for either - mode. The mode is sent as an argument, as well as the umber of - characters still in the cache (if USER mode) - """ - Crest.initEsiApp() - - - # prefetch = EsiInitThread() - # prefetch.daemon = True - # prefetch.start() - - self.settings = CRESTSettings.getInstance() - self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] - - # these will be set when needed - self.httpd = None - self.state = None - self.ssoTimer = None - - self.eve_options = { - 'client_id': self.settings.get('clientID') if self.settings.get('mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')), - 'api_key': self.settings.get('clientSecret') if self.settings.get('mode') == CrestModes.USER else None, - 'redirect_uri': self.clientCallback, - 'testing': self.isTestServer - } - - # Base EVE connection that is copied to all characters - self.eve = EVE(**self.eve_options) - - self.implicitCharacter = None - - # The database cache does not seem to be working for some reason. Use - # this as a temporary measure - self.charCache = {} - - # need these here to post events - import gui.mainFrame # put this here to avoid loop - self.mainFrame = gui.mainFrame.MainFrame.getInstance() - - @property - def isTestServer(self): - return self.settings.get('server') == Servers.SISI - - def delCrestCharacter(self, charID): - char = eos.db.getSsoCharacter(charID) - del self.charCache[char.ID] - eos.db.remove(char) - wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=len(self.charCache))) - - def delAllCharacters(self): - chars = eos.db.getSsoCharacters() - for char in chars: - eos.db.remove(char) - self.charCache = {} - wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=0)) - - def getSsoCharacters(self): - chars = eos.db.getSsoCharacters(config.getClientSecret()) - return chars - - def getSsoCharacter(self, charID): - """ - Get character, and modify to include the eve connection - """ - char = eos.db.getSsoCharacter(charID, config.getClientSecret()) - if char.esi_client is None: - char.esi_client = Crest.genEsiClient() - char.esi_client.security.update_token(char.get_sso_data()) - return char - - def getFittings(self, charID): - char = self.getSsoCharacter(charID) - print(repr(char)) - op = Crest.esi_v1.op['get_characters_character_id_fittings']( - character_id=charID - ) - resp = char.esi_client.request(op) - return resp.data - - def postFitting(self, charID, json_str): - # @todo: new fitting ID can be recovered from resp.data, - char = self.getSsoCharacter(charID) - - op = Crest.esi_v1.op['post_characters_character_id_fittings']( - character_id=char.characterID, - fitting=json.loads(json_str) - ) - - resp = char.esi_client.request(op) - - return resp.data - - def delFitting(self, charID, fittingID): - char = self.getSsoCharacter(charID) - print(repr(char)) - op = Crest.esi_v1.op['delete_characters_character_id_fittings_fitting_id']( - character_id=charID, - fitting_id=fittingID - ) - - resp = char.esi_client.request(op) - return resp.data - - - def logout(self): - """Logout of implicit character""" - pyfalog.debug("Character logout") - self.implicitCharacter = None - wx.PostEvent(self.mainFrame, GE.SsoLogout(type=self.settings.get('mode'))) - - def stopServer(self): - pyfalog.debug("Stopping Server") - self.httpd.stop() - self.httpd = None - - def startServer(self): - 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.state = str(uuid.uuid4()) - self.httpd = StoppableHTTPServer(('localhost', 0), AuthHandler) - port = self.httpd.socket.getsockname()[1] - - esisecurity = EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) - - uri = esisecurity.get_auth_uri(state=self.state, redirect='http://localhost:{}'.format(port)) - - self.serverThread = threading.Thread(target=self.httpd.serve, args=(self.handleLogin,)) - self.serverThread.name = "SsoCallbackServer" - self.serverThread.daemon = True - self.serverThread.start() - - return uri - - def handleLogin(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 CREST login with: {0}", message) - - auth_response = json.loads(base64.b64decode(message['SSOInfo'][0])) - - # We need to preload the ESI Security object beforehand with the auth response so that we can use verify to - # get character information - # init the security object - esisecurity = EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) - - esisecurity.update_token(auth_response) - - # we get the character information - cdata = esisecurity.verify() - print(cdata) - - currentCharacter = self.getSsoCharacter(cdata['CharacterID']) - - if currentCharacter is None: - currentCharacter = SsoCharacter(cdata['CharacterID'], cdata['CharacterName'], config.getClientSecret()) - currentCharacter.esi_client = Crest.genEsiClient(esisecurity) - currentCharacter.update_token(auth_response) # this also sets the esi security token - - eos.db.save(currentCharacter) - - wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER)) # todo: remove user / implicit authentication diff --git a/service/esi.py b/service/esi.py index 0f375a591..d065ffb90 100644 --- a/service/esi.py +++ b/service/esi.py @@ -5,17 +5,37 @@ import threading import copy import uuid import time +import config +import base64 +import json import eos.db from eos.enum import Enum -from eos.saveddata.ssocharacter import CrestChar +from eos.saveddata.ssocharacter import SsoCharacter import gui.globalEvents as GE from service.settings import CRESTSettings from service.server import StoppableHTTPServer, AuthHandler from service.pycrest.eve import EVE +from .esi_security_proxy import EsiSecurityProxy +from esipy import EsiClient, EsiApp +from esipy.cache import FileCache +import os +import logging + + pyfalog = Logger(__name__) +server = "https://blitzmann.pythonanywhere.com" +cache_path = os.path.join(config.savePath, config.ESI_CACHE) + +if not os.path.exists(cache_path): + os.mkdir(cache_path) + +file_cache = FileCache(cache_path) + +esiRdy = threading.Event() + class Servers(Enum): TQ = 0 @@ -26,21 +46,236 @@ class CrestModes(Enum): IMPLICIT = 0 USER = 1 +from utils.timer import Timer + + + +class Esi(object): + clientIDs = { + Servers.TQ : 'f9be379951c046339dc13a00e6be7704', + Servers.SISI: 'af87365240d644f7950af563b8418bad' + } -class ESI(object): # @todo: move this to settings clientCallback = 'http://localhost:6461' clientTest = True + esiapp = None + esi_v1 = None + esi_v4 = None + _instance = None + @classmethod + def initEsiApp(cls): + with Timer("Main EsiApp") as t: + cls.esiapp = EsiApp(cache=file_cache) + with Timer('ESI v1') as t: + cls.esi_v1 = cls.esiapp.get_v1_swagger + with Timer('ESI v4') as t: + cls.esi_v4 = cls.esiapp.get_v4_swagger + + # esiRdy.set() + + @classmethod + def genEsiClient(cls, security=None): + return EsiClient( + security=EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) if security is None else security, + cache=file_cache, + headers={'User-Agent': 'pyfa esipy'} + ) + @classmethod def getInstance(cls): if cls._instance is None: - cls._instance = ESI() + cls._instance = Esi() return cls._instance + @classmethod + def restartService(cls): + # This is here to reseed pycrest values when changing preferences + # We first stop the server n case one is running, as creating a new + # instance doesn't do this. + if cls._instance.httpd: + cls._instance.stopServer() + cls._instance = Esi() + cls._instance.mainFrame.updateCrestMenus(type=cls._instance.settings.get('mode')) + return cls._instance def __init__(self): - pass + """ + A note on login/logout events: the character login events happen + whenever a characters is logged into via the SSO, regardless of mod. + However, the mode should be send as an argument. Similarily, + the Logout even happens whenever the character is deleted for either + mode. The mode is sent as an argument, as well as the umber of + characters still in the cache (if USER mode) + """ + Esi.initEsiApp() + + + # prefetch = EsiInitThread() + # prefetch.daemon = True + # prefetch.start() + + self.settings = CRESTSettings.getInstance() + self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] + + # these will be set when needed + self.httpd = None + self.state = None + self.ssoTimer = None + + self.eve_options = { + 'client_id': self.settings.get('clientID') if self.settings.get('mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')), + 'api_key': self.settings.get('clientSecret') if self.settings.get('mode') == CrestModes.USER else None, + 'redirect_uri': self.clientCallback, + 'testing': self.isTestServer + } + + # Base EVE connection that is copied to all characters + self.eve = EVE(**self.eve_options) + + self.implicitCharacter = None + + # The database cache does not seem to be working for some reason. Use + # this as a temporary measure + self.charCache = {} + + # need these here to post events + import gui.mainFrame # put this here to avoid loop + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + + @property + def isTestServer(self): + return self.settings.get('server') == Servers.SISI + + def delCrestCharacter(self, charID): + char = eos.db.getSsoCharacter(charID) + del self.charCache[char.ID] + eos.db.remove(char) + wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=len(self.charCache))) + + def delAllCharacters(self): + chars = eos.db.getSsoCharacters() + for char in chars: + eos.db.remove(char) + self.charCache = {} + wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=0)) + + def getSsoCharacters(self): + chars = eos.db.getSsoCharacters(config.getClientSecret()) + return chars + + def getSsoCharacter(self, charID): + """ + Get character, and modify to include the eve connection + """ + char = eos.db.getSsoCharacter(charID, config.getClientSecret()) + if char.esi_client is None: + char.esi_client = Esi.genEsiClient() + char.esi_client.security.update_token(char.get_sso_data()) + return char + + def getFittings(self, charID): + char = self.getSsoCharacter(charID) + print(repr(char)) + op = Esi.esi_v1.op['get_characters_character_id_fittings']( + character_id=charID + ) + resp = char.esi_client.request(op) + return resp.data + + def postFitting(self, charID, json_str): + # @todo: new fitting ID can be recovered from resp.data, + char = self.getSsoCharacter(charID) + + op = Esi.esi_v1.op['post_characters_character_id_fittings']( + character_id=char.characterID, + fitting=json.loads(json_str) + ) + + resp = char.esi_client.request(op) + + return resp.data + + def delFitting(self, charID, fittingID): + char = self.getSsoCharacter(charID) + print(repr(char)) + op = Esi.esi_v1.op['delete_characters_character_id_fittings_fitting_id']( + character_id=charID, + fitting_id=fittingID + ) + + resp = char.esi_client.request(op) + return resp.data + + + def logout(self): + """Logout of implicit character""" + pyfalog.debug("Character logout") + self.implicitCharacter = None + wx.PostEvent(self.mainFrame, GE.SsoLogout(type=self.settings.get('mode'))) + + def stopServer(self): + pyfalog.debug("Stopping Server") + self.httpd.stop() + self.httpd = None + + def startServer(self): + 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.state = str(uuid.uuid4()) + self.httpd = StoppableHTTPServer(('localhost', 0), AuthHandler) + port = self.httpd.socket.getsockname()[1] + + esisecurity = EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) + + uri = esisecurity.get_auth_uri(state=self.state, redirect='http://localhost:{}'.format(port)) + + self.serverThread = threading.Thread(target=self.httpd.serve, args=(self.handleLogin,)) + self.serverThread.name = "SsoCallbackServer" + self.serverThread.daemon = True + self.serverThread.start() + + return uri + + def handleLogin(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 CREST login with: {0}", message) + + auth_response = json.loads(base64.b64decode(message['SSOInfo'][0])) + + # We need to preload the ESI Security object beforehand with the auth response so that we can use verify to + # get character information + # init the security object + esisecurity = EsiSecurityProxy(sso_url=config.ESI_AUTH_PROXY) + + esisecurity.update_token(auth_response) + + # we get the character information + cdata = esisecurity.verify() + print(cdata) + + currentCharacter = self.getSsoCharacter(cdata['CharacterID']) + + if currentCharacter is None: + currentCharacter = SsoCharacter(cdata['CharacterID'], cdata['CharacterName'], config.getClientSecret()) + currentCharacter.esi_client = Esi.genEsiClient(esisecurity) + currentCharacter.update_token(auth_response) # this also sets the esi security token + + eos.db.save(currentCharacter) + + wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER)) # todo: remove user / implicit authentication diff --git a/service/port.py b/service/port.py index c3cf352c7..8f2851bf6 100644 --- a/service/port.py +++ b/service/port.py @@ -49,7 +49,7 @@ from service.market import Market from utils.strfunctions import sequential_rep, replace_ltgt from abc import ABCMeta, abstractmethod -from service.crest import Crest +from service.esi import Esi from collections import OrderedDict pyfalog = Logger(__name__) @@ -388,7 +388,7 @@ class Port(object): nested_dict = lambda: collections.defaultdict(nested_dict) fit = nested_dict() - sCrest = Crest.getInstance() + sCrest = Esi.getInstance() sFit = svcFit.getInstance() eve = sCrest.eve