Merge pull request #2590 from huangzheng2016/master
Update the SSO Login for Serenity and Singularity server's player
This commit is contained in:
@@ -6,14 +6,14 @@ import time
|
||||
import base64
|
||||
import json
|
||||
import config
|
||||
import webbrowser
|
||||
import re
|
||||
|
||||
import eos.db
|
||||
from service.const import EsiLoginMethod, EsiSsoMode
|
||||
from eos.saveddata.ssocharacter import SsoCharacter
|
||||
from service.esiAccess import APIException, GenericSsoError
|
||||
import gui.globalEvents as GE
|
||||
from gui.ssoLogin import SsoLogin, SsoLoginServer
|
||||
from gui.ssoLogin import SsoLogin
|
||||
from service.server import StoppableHTTPServer, AuthHandler
|
||||
from service.settings import EsiSettings
|
||||
from service.esiAccess import EsiAccess
|
||||
@@ -22,6 +22,7 @@ import gui.mainFrame
|
||||
from requests import Session
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
class Esi(EsiAccess):
|
||||
@@ -69,8 +70,8 @@ class Esi(EsiAccess):
|
||||
chars = eos.db.getSsoCharacters(config.getClientSecret())
|
||||
return chars
|
||||
|
||||
def getSsoCharacter(self, id):
|
||||
char = eos.db.getSsoCharacter(id, config.getClientSecret())
|
||||
def getSsoCharacter(self, id, server=None):
|
||||
char = eos.db.getSsoCharacter(id, config.getClientSecret(), server)
|
||||
eos.db.commit()
|
||||
return char
|
||||
|
||||
@@ -101,15 +102,36 @@ class Esi(EsiAccess):
|
||||
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('loginMode') == EsiLoginMethod.SERVER:
|
||||
with gui.ssoLogin.SsoLoginServer(0) as dlg:
|
||||
dlg.ShowModal()
|
||||
else:
|
||||
with gui.ssoLogin.SsoLogin() as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
message = json.loads(base64.b64decode(dlg.ssoInfoCtrl.Value.strip()))
|
||||
self.handleLogin(message)
|
||||
start_server = self.settings.get('loginMode') == EsiLoginMethod.SERVER and self.server_base.supports_auto_login
|
||||
with gui.ssoLogin.SsoLogin(self.server_base, start_server) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
from gui.esiFittings import ESIExceptionHandler
|
||||
|
||||
try:
|
||||
if self.server_name == "Serenity":
|
||||
s = re.search(r'(?<=code=)[a-zA-Z0-9\-_]*', dlg.ssoInfoCtrl.Value.strip())
|
||||
if s:
|
||||
# skip state verification and go directly through the auth code processing
|
||||
self.handleLogin(s.group(0))
|
||||
else:
|
||||
pass
|
||||
# todo: throw error
|
||||
else:
|
||||
self.handleServerRequest(json.loads(base64.b64decode(dlg.ssoInfoCtrl.Value.strip())))
|
||||
except GenericSsoError as ex:
|
||||
pyfalog.error(ex)
|
||||
with wx.MessageDialog(
|
||||
self.mainFrame,
|
||||
str(ex),
|
||||
_t("SSO Error"),
|
||||
wx.OK | wx.ICON_ERROR
|
||||
) as dlg:
|
||||
dlg.ShowModal()
|
||||
except APIException as ex:
|
||||
pyfalog.error(ex)
|
||||
ESIExceptionHandler(ex)
|
||||
pass
|
||||
|
||||
|
||||
def stopServer(self):
|
||||
pyfalog.debug("Stopping Server")
|
||||
@@ -127,24 +149,26 @@ class Esi(EsiAccess):
|
||||
|
||||
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 = threading.Thread(target=self.httpd.serve, args=(self.handleServerRequest,))
|
||||
self.serverThread.name = "SsoCallbackServer"
|
||||
self.serverThread.daemon = True
|
||||
self.serverThread.start()
|
||||
|
||||
return 'http://localhost:{}'.format(port)
|
||||
|
||||
def handleLogin(self, message):
|
||||
auth_response, data = self.auth(message['code'])
|
||||
def handleLogin(self, code):
|
||||
auth_response, data = self.auth(code)
|
||||
|
||||
currentCharacter = self.getSsoCharacter(data['name'])
|
||||
currentCharacter = self.getSsoCharacter(data['name'], self.server_base.name)
|
||||
|
||||
sub_split = data["sub"].split(":")
|
||||
if (len(sub_split) != 3):
|
||||
|
||||
if len(sub_split) != 3:
|
||||
raise GenericSsoError("JWT sub does not contain the expected data. Contents: %s" % data["sub"])
|
||||
|
||||
cid = sub_split[-1]
|
||||
if currentCharacter is None:
|
||||
currentCharacter = SsoCharacter(cid, data['name'], config.getClientSecret())
|
||||
currentCharacter = SsoCharacter(cid, data['name'], config.getClientSecret(), self.server_base.name)
|
||||
|
||||
Esi.update_token(currentCharacter, auth_response)
|
||||
|
||||
@@ -153,7 +177,7 @@ class Esi(EsiAccess):
|
||||
|
||||
# get (endpoint, char, data?)
|
||||
|
||||
def handleServerLogin(self, message):
|
||||
def handleServerRequest(self, message):
|
||||
if not message:
|
||||
raise GenericSsoError("Could not parse out querystring parameters.")
|
||||
|
||||
@@ -169,4 +193,4 @@ class Esi(EsiAccess):
|
||||
|
||||
pyfalog.debug("Handling SSO login with: {0}", message)
|
||||
|
||||
self.handleLogin(message)
|
||||
self.handleLogin(message['code'])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# noinspection PyPackageRequirements
|
||||
from collections import namedtuple
|
||||
|
||||
import requests
|
||||
from logbook import Logger
|
||||
import uuid
|
||||
import time
|
||||
@@ -30,13 +31,6 @@ scopes = [
|
||||
'esi-fittings.write_fittings.v1'
|
||||
]
|
||||
|
||||
ApiBase = namedtuple('ApiBase', ['sso', 'esi'])
|
||||
supported_servers = {
|
||||
"Tranquility": ApiBase("login.eveonline.com", "esi.evetech.net"),
|
||||
"Singularity": ApiBase("sisilogin.testeveonline.com", "esi.evetech.net"),
|
||||
"Serenity": ApiBase("login.evepc.163.com", "esi.evepc.163.com")
|
||||
}
|
||||
|
||||
class GenericSsoError(Exception):
|
||||
""" Exception used for generic SSO errors that aren't directly related to an API call
|
||||
"""
|
||||
@@ -63,10 +57,11 @@ class APIException(Exception):
|
||||
|
||||
|
||||
class EsiAccess:
|
||||
server_meta = {}
|
||||
def __init__(self):
|
||||
self.settings = EsiSettings.getInstance()
|
||||
self.server_base: ApiBase = supported_servers[self.settings.get("server")]
|
||||
|
||||
self.default_server_name = self.settings.get('server')
|
||||
self.default_server_base = config.supported_servers[self.default_server_name]
|
||||
# session request stuff
|
||||
self._session = Session()
|
||||
self._basicHeaders = {
|
||||
@@ -78,23 +73,38 @@ class EsiAccess:
|
||||
self._session.headers.update(self._basicHeaders)
|
||||
self._session.proxies = NetworkSettings.getInstance().getProxySettingsInRequestsFormat()
|
||||
|
||||
self.mem_cached_session = {}
|
||||
|
||||
# Set up cached session. This is only used for SSO meta data for now, but can be expanded to actually handle
|
||||
# various ESI caching (using ETag, for example) in the future
|
||||
cached_session = CachedSession(
|
||||
self.cached_session = CachedSession(
|
||||
os.path.join(config.savePath, config.ESI_CACHE),
|
||||
backend="sqlite",
|
||||
cache_control=True, # Use Cache-Control headers for expiration, if available
|
||||
expire_after=timedelta(days=1), # Otherwise expire responses after one day
|
||||
stale_if_error=True, # In case of request errors, use stale cache data if possible
|
||||
)
|
||||
cached_session.headers.update(self._basicHeaders)
|
||||
cached_session.proxies = NetworkSettings.getInstance().getProxySettingsInRequestsFormat()
|
||||
self.cached_session.headers.update(self._basicHeaders)
|
||||
self.cached_session.proxies = NetworkSettings.getInstance().getProxySettingsInRequestsFormat()
|
||||
self.init(self.default_server_base)
|
||||
|
||||
def init(self, server_base):
|
||||
self.server_base: config.ApiServer = server_base
|
||||
self.server_name = self.server_base.name
|
||||
try:
|
||||
meta_call = self.cached_session.get("https://%s/.well-known/oauth-authorization-server" % self.server_base.sso)
|
||||
except:
|
||||
# The http data of expire_after in evepc.163.com is -1
|
||||
meta_call = requests.get("https://%s/.well-known/oauth-authorization-server" % self.server_base.sso)
|
||||
|
||||
meta_call = cached_session.get("https://%s/.well-known/oauth-authorization-server" % self.server_base.sso)
|
||||
meta_call.raise_for_status()
|
||||
self.server_meta = meta_call.json()
|
||||
|
||||
jwks_call = cached_session.get(self.server_meta["jwks_uri"])
|
||||
try:
|
||||
jwks_call = self.cached_session.get(self.server_meta["jwks_uri"])
|
||||
except:
|
||||
jwks_call = requests.get(self.server_meta["jwks_uri"])
|
||||
|
||||
jwks_call.raise_for_status()
|
||||
self.jwks = jwks_call.json()
|
||||
|
||||
@@ -116,7 +126,7 @@ class EsiAccess:
|
||||
|
||||
@property
|
||||
def client_id(self):
|
||||
return self.settings.get('clientID') or config.API_CLIENT_ID
|
||||
return self.settings.get('clientID') or self.server_base.client_id
|
||||
|
||||
@staticmethod
|
||||
def update_token(char, tokenResponse):
|
||||
@@ -142,16 +152,25 @@ class EsiAccess:
|
||||
'state': self.state
|
||||
}
|
||||
|
||||
args = {
|
||||
'response_type': 'code',
|
||||
'redirect_uri': config.SSO_CALLBACK,
|
||||
'client_id': self.client_id,
|
||||
'scope': ' '.join(scopes),
|
||||
'code_challenge': code_challenge,
|
||||
'code_challenge_method': 'S256',
|
||||
'state': base64.b64encode(bytes(json.dumps(state_arg), 'utf-8'))
|
||||
}
|
||||
|
||||
if(self.server_name=="Serenity"):
|
||||
args = {
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.server_base.callback,
|
||||
'client_id': self.client_id,
|
||||
'scope': ' '.join(scopes),
|
||||
'state': 'hilltech',
|
||||
'device_id': 'eims'
|
||||
}
|
||||
else:
|
||||
args = {
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.server_base.callback,
|
||||
'client_id': self.client_id,
|
||||
'scope': ' '.join(scopes),
|
||||
'code_challenge': code_challenge,
|
||||
'code_challenge_method': 'S256',
|
||||
'state': base64.b64encode(bytes(json.dumps(state_arg), 'utf-8'))
|
||||
}
|
||||
return '%s?%s' % (
|
||||
self.oauth_authorize,
|
||||
urlencode(args)
|
||||
@@ -252,6 +271,11 @@ class EsiAccess:
|
||||
"https://login.eveonline.com: {}".format(str(e)))
|
||||
|
||||
def _before_request(self, ssoChar):
|
||||
if ssoChar:
|
||||
self.init(config.supported_servers[ssoChar.server])
|
||||
else:
|
||||
self.init(self.default_server_base)
|
||||
|
||||
self._session.headers.clear()
|
||||
self._session.headers.update(self._basicHeaders)
|
||||
if ssoChar is None:
|
||||
@@ -280,17 +304,17 @@ class EsiAccess:
|
||||
def get(self, ssoChar, endpoint, **kwargs):
|
||||
self._before_request(ssoChar)
|
||||
endpoint = endpoint.format(**kwargs)
|
||||
return self._after_request(self._session.get("{}{}".format(self.esi_url, endpoint)))
|
||||
return self._after_request(self._session.get("{}{}?datasource={}".format(self.esi_url, endpoint, self.server_name.lower())))
|
||||
|
||||
def post(self, ssoChar, endpoint, json, **kwargs):
|
||||
self._before_request(ssoChar)
|
||||
endpoint = endpoint.format(**kwargs)
|
||||
return self._after_request(self._session.post("{}{}".format(self.esi_url, endpoint), data=json))
|
||||
return self._after_request(self._session.post("{}{}?datasource={}".format(self.esi_url, endpoint, self.server_name.lower()), data=json))
|
||||
|
||||
def delete(self, ssoChar, endpoint, **kwargs):
|
||||
self._before_request(ssoChar)
|
||||
endpoint = endpoint.format(**kwargs)
|
||||
return self._after_request(self._session.delete("{}{}".format(self.esi_url, endpoint)))
|
||||
return self._after_request(self._session.delete("{}{}?datasource={}".format(self.esi_url, endpoint, self.server_name.lower())))
|
||||
|
||||
# todo: move these off to another class which extends this one. This class should only handle the low level
|
||||
# authentication and
|
||||
|
||||
@@ -392,6 +392,9 @@ class EsiSettings:
|
||||
def set(self, type, value):
|
||||
self.settings[type] = value
|
||||
|
||||
def keys(self):
|
||||
return config.supported_servers.keys()
|
||||
|
||||
|
||||
class StatViewSettings:
|
||||
_instance = None
|
||||
|
||||
Reference in New Issue
Block a user