Get logging into EVE working and SSO characters saving, along with client hashes being stored alongside characters.
This commit is contained in:
@@ -35,6 +35,9 @@ logPath = None
|
||||
loggingLevel = None
|
||||
logging_setup = None
|
||||
|
||||
ESI_AUTH_PROXY = "http://localhost:5015" # "https://blitzmann.pythonanywhere.com" // need to get this set up, and actually put on it's own domain
|
||||
ESI_CACHE = 'esi_cache'
|
||||
|
||||
LOGLEVEL_MAP = {
|
||||
"critical": CRITICAL,
|
||||
"error": ERROR,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, String, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, String, DateTime, UniqueConstraint
|
||||
from sqlalchemy.orm import mapper
|
||||
import datetime
|
||||
|
||||
@@ -27,12 +27,15 @@ from eos.saveddata.ssocharacter import SsoCharacter
|
||||
sso_table = Table("ssoCharacter", saveddata_meta,
|
||||
Column("ID", Integer, primary_key=True),
|
||||
Column("client", String, nullable=False),
|
||||
Column("characterID", Integer, nullable=False, unique=True),
|
||||
Column("characterName", String, nullable=False, unique=True),
|
||||
Column("characterID", Integer, nullable=False),
|
||||
Column("characterName", String, nullable=False),
|
||||
Column("refreshToken", String, nullable=False),
|
||||
Column("accessToken", String, nullable=False),
|
||||
Column("accessTokenExpires", DateTime, nullable=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
UniqueConstraint('client', 'characterID', name='uix_client_characterID'),
|
||||
UniqueConstraint('client', 'characterName', name='uix_client_characterName')
|
||||
)
|
||||
|
||||
mapper(SsoCharacter, sso_table)
|
||||
|
||||
@@ -467,29 +467,28 @@ def getProjectedFits(fitID):
|
||||
raise TypeError("Need integer as argument")
|
||||
|
||||
|
||||
def getSsoCharacters(eager=None):
|
||||
def getSsoCharacters(clientHash, eager=None):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
characters = saveddata_session.query(SsoCharacter).options(*eager).all()
|
||||
characters = saveddata_session.query(SsoCharacter).filter(SsoCharacter.client == clientHash).options(*eager).all()
|
||||
return characters
|
||||
|
||||
|
||||
@cachedQuery(SsoCharacter, 1, "lookfor")
|
||||
def getSsoCharacter(lookfor, eager=None):
|
||||
def getSsoCharacter(lookfor, clientHash, eager=None):
|
||||
filter = SsoCharacter.client == clientHash
|
||||
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(SsoCharacter).get(lookfor)
|
||||
else:
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(SsoCharacter).options(*eager).filter(SsoCharacter.ID == lookfor).first()
|
||||
filter = and_(filter, SsoCharacter.characterID == lookfor)
|
||||
elif isinstance(lookfor, str):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(SsoCharacter).options(*eager).filter(SsoCharacter.name == lookfor).first()
|
||||
filter = and_(filter, SsoCharacter.characterName == lookfor)
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(SsoCharacter).options(*eager).filter(filter).first()
|
||||
|
||||
return character
|
||||
|
||||
|
||||
@@ -544,7 +543,7 @@ def commit():
|
||||
try:
|
||||
saveddata_session.commit()
|
||||
saveddata_session.flush()
|
||||
except Exception:
|
||||
except Exception as ex:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||
|
||||
@@ -25,13 +25,13 @@ import time
|
||||
|
||||
|
||||
class SsoCharacter(object):
|
||||
def __init__(self, id, charID, name, client, accessToken, refreshToken=None):
|
||||
self.ID = id
|
||||
def __init__(self, charID, name, client, accessToken=None, refreshToken=None):
|
||||
self.characterID = charID
|
||||
self.characterName = name
|
||||
self.client = client
|
||||
self.accessToken = accessToken
|
||||
self.refreshToken = refreshToken
|
||||
self.esi_client = None
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
@@ -51,7 +51,7 @@ class SsoCharacter(object):
|
||||
def update_token(self, tokenResponse):
|
||||
""" helper function to update token data from SSO response """
|
||||
self.accessToken = tokenResponse['access_token']
|
||||
self.accessTokenExpires = datetime.fromtimestamp(
|
||||
self.accessTokenExpires = datetime.datetime.fromtimestamp(
|
||||
time.time() + tokenResponse['expires_in'],
|
||||
)
|
||||
if 'refresh_token' in tokenResponse:
|
||||
|
||||
@@ -310,7 +310,9 @@ class CrestMgmt(wx.Dialog):
|
||||
self.lcCharacters = wx.ListCtrl(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT)
|
||||
|
||||
self.lcCharacters.InsertColumn(0, heading='Character')
|
||||
self.lcCharacters.InsertColumn(1, heading='Refresh Token')
|
||||
self.lcCharacters.InsertColumn(1, heading='Character ID')
|
||||
self.lcCharacters.InsertColumn(2, heading='Access Token')
|
||||
self.lcCharacters.InsertColumn(3, heading='Refresh Token')
|
||||
|
||||
self.popCharList()
|
||||
|
||||
@@ -347,8 +349,8 @@ class CrestMgmt(wx.Dialog):
|
||||
self.lcCharacters.DeleteAllItems()
|
||||
|
||||
for index, char in enumerate(chars):
|
||||
self.lcCharacters.InsertItem(index, char.name)
|
||||
self.lcCharacters.SetStringItem(index, 1, char.refresh_token)
|
||||
self.lcCharacters.InsertItem(index, char.characterName)
|
||||
self.lcCharacters.SetStringItem(index, 1, char.refreshToken)
|
||||
self.lcCharacters.SetItemData(index, char.ID)
|
||||
|
||||
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
|
||||
105
service/crest.py
105
service/crest.py
@@ -5,6 +5,9 @@ import threading
|
||||
import copy
|
||||
import uuid
|
||||
import time
|
||||
import config
|
||||
import base64
|
||||
import json
|
||||
|
||||
import eos.db
|
||||
from eos.enum import Enum
|
||||
@@ -14,8 +17,20 @@ 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
|
||||
from esipy.cache import FileCache
|
||||
import os
|
||||
|
||||
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)
|
||||
|
||||
class Servers(Enum):
|
||||
TQ = 0
|
||||
@@ -39,6 +54,14 @@ class Crest(object):
|
||||
|
||||
_instance = None
|
||||
|
||||
@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:
|
||||
@@ -113,32 +136,30 @@ class Crest(object):
|
||||
wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=0))
|
||||
|
||||
def getCrestCharacters(self):
|
||||
chars = eos.db.getSsoCharacters()
|
||||
chars = eos.db.getSsoCharacters(config.getClientSecret())
|
||||
return chars
|
||||
|
||||
def getCrestCharacter(self, charID):
|
||||
def getSsoCharacter(self, charID):
|
||||
"""
|
||||
Get character, and modify to include the eve connection
|
||||
"""
|
||||
if charID in self.charCache:
|
||||
return self.charCache.get(charID)
|
||||
|
||||
char = eos.db.getSsoCharacter(charID)
|
||||
self.charCache[charID] = char
|
||||
char = eos.db.getSsoCharacter(charID, config.getClientSecret())
|
||||
if char.esi_client is None:
|
||||
char.esi_client = Crest.genEsiClient()
|
||||
return char
|
||||
|
||||
def getFittings(self, charID):
|
||||
char = self.getCrestCharacter(charID)
|
||||
char = self.getSsoCharacter(charID)
|
||||
return char.eve.get('%scharacters/%d/fittings/' % (char.eve._authed_endpoint, char.ID))
|
||||
|
||||
def postFitting(self, charID, json):
|
||||
# @todo: new fitting ID can be recovered from Location header,
|
||||
# ie: Location -> https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/
|
||||
char = self.getCrestCharacter(charID)
|
||||
char = self.getSsoCharacter(charID)
|
||||
return char.eve.post('%scharacters/%d/fittings/' % (char.eve._authed_endpoint, char.ID), data=json)
|
||||
|
||||
def delFitting(self, charID, fittingID):
|
||||
char = self.getCrestCharacter(charID)
|
||||
char = self.getSsoCharacter(charID)
|
||||
return char.eve.delete('%scharacters/%d/fittings/%d/' % (char.eve._authed_endpoint, char.ID, fittingID))
|
||||
|
||||
def logout(self):
|
||||
@@ -154,19 +175,26 @@ class Crest(object):
|
||||
|
||||
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)
|
||||
# we need this to ensure that the previous get_request finishes, and then the socket will close
|
||||
self.httpd = StoppableHTTPServer(('localhost', 6461), AuthHandler)
|
||||
|
||||
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 = "CRESTServer"
|
||||
self.serverThread.name = "SsoCallbackServer"
|
||||
self.serverThread.daemon = True
|
||||
self.serverThread.start()
|
||||
|
||||
self.state = str(uuid.uuid4())
|
||||
return self.eve.auth_uri(scopes=self.scopes, state=self.state)
|
||||
return uri
|
||||
|
||||
def handleLogin(self, message):
|
||||
if not message:
|
||||
@@ -178,41 +206,26 @@ class Crest(object):
|
||||
|
||||
pyfalog.debug("Handling CREST login with: {0}", message)
|
||||
|
||||
if 'access_token' in message: # implicit
|
||||
eve = EVE(**self.eve_options)
|
||||
eve.temptoken_authorize(
|
||||
access_token=message['access_token'][0],
|
||||
expires_in=int(message['expires_in'][0])
|
||||
)
|
||||
self.ssoTimer = threading.Timer(int(message['expires_in'][0]), self.logout)
|
||||
self.ssoTimer.start()
|
||||
auth_response = json.loads(base64.b64decode(message['SSOInfo'][0]))
|
||||
|
||||
eve()
|
||||
info = eve.whoami()
|
||||
# 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)
|
||||
|
||||
pyfalog.debug("Got character info: {0}", info)
|
||||
esisecurity.update_token(auth_response)
|
||||
|
||||
self.implicitCharacter = CrestChar(info['CharacterID'], info['CharacterName'])
|
||||
self.implicitCharacter.eve = eve
|
||||
# self.implicitCharacter.fetchImage()
|
||||
# we get the character information
|
||||
cdata = esisecurity.verify()
|
||||
print(cdata)
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.IMPLICIT))
|
||||
elif 'code' in message:
|
||||
eve = EVE(**self.eve_options)
|
||||
eve.authorize(message['code'][0])
|
||||
eve()
|
||||
info = eve.whoami()
|
||||
currentCharacter = self.getSsoCharacter(cdata['CharacterID'])
|
||||
|
||||
pyfalog.debug("Got character info: {0}", info)
|
||||
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
|
||||
|
||||
# check if we have character already. If so, simply replace refresh_token
|
||||
char = self.getCrestCharacter(int(info['CharacterID']))
|
||||
if char:
|
||||
char.refresh_token = eve.refresh_token
|
||||
else:
|
||||
char = CrestChar(info['CharacterID'], info['CharacterName'], eve.refresh_token)
|
||||
char.eve = eve
|
||||
self.charCache[int(info['CharacterID'])] = char
|
||||
eos.db.save(char)
|
||||
eos.db.save(currentCharacter)
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER))
|
||||
wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER)) # todo: remove user / implicit authentication
|
||||
|
||||
@@ -26,11 +26,9 @@ class EsiSecurityProxy(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
redirect_uri,
|
||||
**kwargs):
|
||||
""" Init the ESI Security Object
|
||||
|
||||
:param redirect_uri: the uri to redirect the user after login into SSO
|
||||
:param sso_url: the default sso URL used when no "app" is provided
|
||||
:param esi_url: the default esi URL used for verify endpoint
|
||||
:param app: (optionnal) the pyswagger app object
|
||||
@@ -43,7 +41,6 @@ class EsiSecurityProxy(object):
|
||||
esi_url = kwargs.pop('esi_url', "https://esi.tech.ccp.is")
|
||||
|
||||
self.security_name = kwargs.pop('security_name', 'evesso')
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
# we provide app object, so we don't use sso_url
|
||||
if app is not None:
|
||||
|
||||
@@ -82,14 +82,14 @@ class AuthHandler(http.server.BaseHTTPRequestHandler):
|
||||
try:
|
||||
if step2:
|
||||
self.server.callback(parts)
|
||||
pyfalog.info("Successfully logged into CREST.")
|
||||
pyfalog.info("Successfully logged into EVE.")
|
||||
msg = "If you see this message then it means you should be logged into CREST. You may close this window and return to the application."
|
||||
else:
|
||||
# For implicit mode, we have to serve up the page which will take the hash and redirect useing a querystring
|
||||
pyfalog.info("Processing response from EVE Online.")
|
||||
msg = "Processing response from EVE Online"
|
||||
except Exception as ex:
|
||||
pyfalog.error("Error in CREST AuthHandler")
|
||||
pyfalog.error("Error logging into EVE")
|
||||
pyfalog.error(ex)
|
||||
msg = "<h2>Error</h2>\n<p>{}</p>".format(ex.message)
|
||||
finally:
|
||||
@@ -109,10 +109,10 @@ class AuthHandler(http.server.BaseHTTPRequestHandler):
|
||||
class StoppableHTTPServer(http.server.HTTPServer):
|
||||
def server_bind(self):
|
||||
http.server.HTTPServer.server_bind(self)
|
||||
self.settings = CRESTSettings.getInstance()
|
||||
# self.settings = CRESTSettings.getInstance()
|
||||
|
||||
# Allow listening for x seconds
|
||||
sec = self.settings.get('timeout')
|
||||
sec = 120
|
||||
pyfalog.debug("Running server for {0} seconds", sec)
|
||||
|
||||
self.socket.settimeout(1)
|
||||
@@ -131,7 +131,7 @@ class StoppableHTTPServer(http.server.HTTPServer):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pyfalog.warning("Setting CREST server to stop.")
|
||||
pyfalog.warning("Setting pyfa server to stop.")
|
||||
self.run = False
|
||||
|
||||
def handle_timeout(self):
|
||||
|
||||
Reference in New Issue
Block a user