diff --git a/config.py b/config.py index e0b4ba0c0..cdab4bffa 100644 --- a/config.py +++ b/config.py @@ -25,21 +25,6 @@ expansionName = "Vanguard" expansionVersion = "1.0" evemonMinVersion = "4081" -# CREST client information -clientID = '554727742a354f62ad9dfb34a188abc2' -clientSecret = 'fyCksblVC4AHafeYI9XOcV44xi0AOnMLV8tEU45M' -clientCallback = 'http://localhost:6461' -clientTest = True - -# There are times we will need to access the base object outside of CREST calls -# (for example when exporting we need the correct href of the server) -# This will probably move elsewhere eventually -pycrest_eve = pycrest.EVE( - client_id=clientID, - api_key=clientSecret, - redirect_uri=clientCallback, - testing=clientTest) - pyfaPath = None savePath = None saveDB = None diff --git a/gui/builtinPreferenceViews/__init__.py b/gui/builtinPreferenceViews/__init__.py index 923e011ab..20d2cba38 100644 --- a/gui/builtinPreferenceViews/__init__.py +++ b/gui/builtinPreferenceViews/__init__.py @@ -1 +1 @@ -__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences"] +__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences","pyfaCrestPreferences"] diff --git a/gui/crestFittings.py b/gui/crestFittings.py index a89ffb925..3ff8a1748 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -145,7 +145,6 @@ class CrestLogin(wx.Frame): self.mainFrame = parent sCrest = service.Crest.getInstance() - mainSizer = wx.BoxSizer( wx.HORIZONTAL ) self.loginBtn = wx.Button( self, wx.ID_ANY, u"Login via SSO", wx.DefaultPosition, wx.DefaultSize, 5 ) mainSizer.Add( self.loginBtn, 0, wx.ALL, 5 ) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 499219e37..c86865808 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -55,7 +55,6 @@ from gui.utils.clipboard import toClipboard, fromClipboard from gui.fleetBrowser import FleetBrowser from gui.updateDialog import UpdateDialog from gui.builtinViews import * - from time import gmtime, strftime #dummy panel(no paint no erasebk) @@ -491,8 +490,13 @@ class MainFrame(wx.Frame): dlg.Show() def ssoLogin(self, event): - dlg=CrestLogin(self) - dlg.Show() + sCrest = service.Crest.getInstance() + if sCrest.settings.get('mode') == 0: # Implicit, go directly to login + uri = sCrest.startServer() + wx.LaunchDefaultBrowser(uri) + else: + dlg=CrestLogin(self) + dlg.Show() def exportToEve(self, event): dlg=ExportToEve(self) diff --git a/pycrest/eve.py b/pycrest/eve.py index bc7495c71..7262457cf 100644 --- a/pycrest/eve.py +++ b/pycrest/eve.py @@ -122,7 +122,6 @@ class APIConnection(object): self.cache = DictCache() def get(self, resource, params=None): - print resource, params logger.debug('Getting resource %s', resource) if params is None: params = {} @@ -195,6 +194,9 @@ class EVE(APIConnection): self._endpoint = self._public_endpoint self._cache = {} self._data = None + self.token = None + self.refresh_token = None + self.expires = None APIConnection.__init__(self, **kwargs) def __call__(self): @@ -207,8 +209,11 @@ class EVE(APIConnection): def auth_uri(self, scopes=None, state=None): s = [] if not scopes else scopes - return "%s/authorize?response_type=code&redirect_uri=%s&client_id=%s%s%s" % ( + grant_type = "token" if self.api_key is None else "code" + + return "%s/authorize?response_type=%s&redirect_uri=%s&client_id=%s%s%s" % ( self._oauth_endpoint, + grant_type, quote(self.redirect_uri, safe=''), self.client_id, "&scope=%s" % ' '.join(s) if scopes else '', @@ -223,49 +228,30 @@ class EVE(APIConnection): raise APIException("Got unexpected status code from API: %i" % res.status_code) return res.json() - def authorize(self, code): - res = self._authorize(params={"grant_type": "authorization_code", "code": code}) - return AuthedConnection(res, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - def refr_authorize(self, refresh_token): - res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": refresh_token}) - return AuthedConnection({'access_token': res['access_token'], - 'refresh_token': refresh_token, - 'expires_in': res['expires_in']}, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - def temptoken_authorize(self, access_token, expires_in, refresh_token): - return AuthedConnection({'access_token': access_token, - 'refresh_token': refresh_token, - 'expires_in': expires_in}, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - -class AuthedConnection(EVE): - def __init__(self, res, endpoint, oauth_endpoint, client_id=None, api_key=None, **kwargs): - EVE.__init__(self, **kwargs) - self.client_id = client_id - self.api_key = api_key + def set_auth_values(self, res): + self.__class__ = AuthedConnection self.token = res['access_token'] self.refresh_token = res['refresh_token'] self.expires = int(time.time()) + res['expires_in'] - self._oauth_endpoint = oauth_endpoint - self._endpoint = endpoint + self._endpoint = self._authed_endpoint self._session.headers.update({"Authorization": "Bearer %s" % self.token}) + def authorize(self, code): + res = self._authorize(params={"grant_type": "authorization_code", "code": code}) + self.set_auth_values(res) + + def refr_authorize(self, refresh_token): + res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": refresh_token}) + self.set_auth_values(res) + + def temptoken_authorize(self, access_token=None, expires_in=0, refresh_token=None): + self.set_auth_values({'access_token': access_token, + 'refresh_token': refresh_token, + 'expires_in': expires_in}) + + +class AuthedConnection(EVE): + def __call__(self): if not self._data: self._data = APIObject(self.get(self._endpoint), self) diff --git a/server.py b/server.py deleted file mode 100644 index dc5b9ac63..000000000 --- a/server.py +++ /dev/null @@ -1,24 +0,0 @@ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -import urlparse - -class RequestHandler(BaseHTTPRequestHandler): - - def __init__(self, *args, **kwargs): - BaseHTTPRequestHandler.__init__(self, *args, **kwargs) - - def do_GET(self): - bits = urlparse.urlparse(self.path) - print urlparse.parse_qs(bits.query) - - -def main(): - try: - server = HTTPServer(('', 6461), RequestHandler) - print('Test server running...') - server.serve_forever() - except KeyboardInterrupt: - print "Closing socket" - server.socket.close() - -if __name__ == '__main__': - main() diff --git a/service/crest.py b/service/crest.py index 236435a38..12a6b18bd 100644 --- a/service/crest.py +++ b/service/crest.py @@ -1,9 +1,23 @@ import eos.db from eos.types import Crest as CrestUser -import config +import pycrest +import copy +import service +from service.server import * +import uuid +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub + + +# TODO: +# With implicit grant, make sure we know when it expires and delete/inactive char class Crest(): + # @todo: move this to settings + clientCallback = 'http://localhost:6461' + clientTest = True + _instance = None @classmethod def getInstance(cls): @@ -13,22 +27,47 @@ class Crest(): return cls._instance def __init__(self): - pass + self.settings = service.settings.CRESTSettings.getInstance() + self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) + self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] + self.state = None + + # Base EVE connection that is copied to all characters + self.eve = pycrest.EVE( + client_id=self.settings.get('clientID'), + api_key=self.settings.get('clientSecret') if self.settings.get('mode') == 1 else None, + redirect_uri=self.clientCallback, + testing=self.clientTest) + + self.implicitCharacter = None + pub.subscribe(self.handleLogin, 'sso_login') def getCrestCharacters(self): - return eos.db.getCrestCharacters() + chars = eos.db.getCrestCharacters() + for char in chars: + if not hasattr(char, "eve"): + char.eve = copy.copy(self.eve) + # Give EVE instance refresh info. This allows us to set it + # without actually making the request to authorize at this time. + char.eve.temptoken_authorize(refresh_token=char.refresh_token) + return chars def getCrestCharacter(self, charID): - return eos.db.getCrestCharacter(charID) + ''' + Get character, and modify to include the eve connection + ''' + char = eos.db.getCrestCharacter(charID) + if not hasattr(char, "eve"): + char.eve = copy.copy(self.eve) + char.eve.temptoken_authorize(refresh_token=char.refresh_token) + return char def getFittings(self, charID): char = self.getCrestCharacter(charID) - char.auth() return char.eve.get('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID) def postFitting(self, charID, json): char = self.getCrestCharacter(charID) - char.auth() res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) return res @@ -38,3 +77,37 @@ class Crest(): char = CrestUser(info['CharacterName'], info['CharacterID'], connection.refresh_token) eos.db.save(char) + def startServer(self): + thread.start_new_thread(self.httpd.serve, ()) + self.state = str(uuid.uuid4()) + return self.eve.auth_uri(scopes=self.scopes, state=self.state) + + def handleLogin(self, message): + if not message: + return + + if message['state'][0] != self.state: + return + + print "handling login by making characters and stuff" + print message + + if 'access_token' in message: # implicit + eve = copy.copy(self.eve) + eve.temptoken_authorize( + access_token=message['access_token'][0], + expires_in=int(message['expires_in'][0]) + ) + eve() + info = eve.whoami() + self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) + self.implicitCharacter.eve = eve + wx.CallAfter(pub.sendMessage, 'login_success', type=0) + + elif 'code' in message: + print "handle authentication code" + + #wx.CallAfter(pub.sendMessage, 'login_success', type=1) + + + diff --git a/service/html.py b/service/html.py new file mode 100644 index 000000000..b073f7803 --- /dev/null +++ b/service/html.py @@ -0,0 +1,30 @@ +# HTML is stored here as frankly I'm not sure how loading a file would work with +# our current zipfile packaging (and I'm too lazy to find out) + +HTML = ''' + + +
+Done. Please close this window. + +(will put more interesting messages here later) + + + +''' diff --git a/service/server.py b/service/server.py index 3bc28a62b..e36db9631 100644 --- a/service/server.py +++ b/service/server.py @@ -7,18 +7,20 @@ import wx from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub +from html import HTML + # https://github.com/fuzzysteve/CREST-Market-Downloader/ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): - print "GET" if self.path == "/favicon.ico": return parsed_path = urlparse.urlparse(self.path) - parts=urlparse.parse_qs(parsed_path.query) + parts = urlparse.parse_qs(parsed_path.query) self.send_response(200) self.end_headers() - self.wfile.write('Login successful. you can close this window now') - wx.CallAfter(pub.sendMessage, 'sso_login', message=str(parts['code'][0])) + self.wfile.write(HTML) + + wx.CallAfter(pub.sendMessage, 'sso_login', message=parts) def log_message(self, format, *args): return @@ -51,7 +53,7 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): # this can happen if stopping server in middle of request? pass -if __name__=="__main__": +if __name__ == "__main__": httpd = StoppableHTTPServer(('', 6461), AuthHandler) thread.start_new_thread(httpd.serve, ()) raw_input("Press