Various updates to support server-aware calls per character
This commit is contained in:
14
config.py
14
config.py
@@ -9,6 +9,7 @@ import hashlib
|
||||
from eos.const import FittingSlot
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
from collections import namedtuple
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -43,13 +44,16 @@ experimentalFeatures = None
|
||||
version = None
|
||||
language = None
|
||||
|
||||
API_CLIENT_ID = '095d8cd841ac40b581330919b49fe746'
|
||||
API_CLIENT_ID_SERENITY = 'bc90aa496a404724a93f41b4f4e97761'
|
||||
ApiServer = namedtuple('ApiBase', ['name', 'sso', 'esi', 'client_id', 'callback'])
|
||||
supported_servers = {
|
||||
"Tranquility": ApiServer("Tranquility", "login.eveonline.com", "esi.evetech.net", '095d8cd841ac40b581330919b49fe746', 'https://pyfa-org.github.io/Pyfa/callback'),
|
||||
# No point having SISI: https://developers.eveonline.com/blog/article/removing-datasource-singularity
|
||||
# "Singularity": ApiServer("Singularity", "sisilogin.testeveonline.com", "esi.evetech.net", 'b9c3cc79448f449ab17f3aebd018842e', 'https://pyfa-org.github.io/Pyfa/callback'),
|
||||
"Serenity": ApiServer("Serenity", "login.evepc.163.com", "esi.evepc.163.com", 'bc90aa496a404724a93f41b4f4e97761', 'https://esi.evepc.163.com/ui/oauth2-redirect.html')
|
||||
}
|
||||
|
||||
ESI_CACHE = 'esi_cache'
|
||||
SSO_CALLBACK = 'https://pyfa-org.github.io/Pyfa/callback'
|
||||
SSO_CALLBACK_SERENITY='https://esi.evepc.163.com/ui/oauth2-redirect.html'
|
||||
SSO_LOGOFF_SERENITY='https://login.evepc.163.com/account/logoff'
|
||||
ESI_CACHE = 'esi_cache'
|
||||
|
||||
LOGLEVEL_MAP = {
|
||||
"critical": CRITICAL,
|
||||
|
||||
19
eos/db/migrations/upgrade47.py
Normal file
19
eos/db/migrations/upgrade47.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Migration 28
|
||||
|
||||
- adds baseItemID and mutaplasmidID to modules table
|
||||
"""
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT server FROM ssoCharacter LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE ssoCharacter ADD COLUMN server VARCHAR;")
|
||||
saveddata_engine.execute("UPDATE ssoCharacter SET server = 'Tranquility';")
|
||||
|
||||
|
||||
|
||||
# update all characters to TQ
|
||||
@@ -44,6 +44,7 @@ sso_table = Table("ssoCharacter", saveddata_meta,
|
||||
Column("client", String, nullable=False),
|
||||
Column("characterID", Integer, nullable=False),
|
||||
Column("characterName", String, nullable=False),
|
||||
Column("server", String, nullable=False),
|
||||
Column("refreshToken", String, nullable=False),
|
||||
Column("accessToken", String, nullable=False),
|
||||
Column("accessTokenExpires", DateTime, nullable=False),
|
||||
|
||||
@@ -493,9 +493,12 @@ def getSsoCharacters(clientHash, eager=None):
|
||||
|
||||
|
||||
@cachedQuery(SsoCharacter, 1, "lookfor", "clientHash")
|
||||
def getSsoCharacter(lookfor, clientHash, eager=None):
|
||||
def getSsoCharacter(lookfor, clientHash, server=None, eager=None):
|
||||
filter = SsoCharacter.client == clientHash
|
||||
|
||||
if server is not None:
|
||||
filter = and_(filter, SsoCharacter.server == server)
|
||||
|
||||
if isinstance(lookfor, int):
|
||||
filter = and_(filter, SsoCharacter.ID == lookfor)
|
||||
elif isinstance(lookfor, str):
|
||||
|
||||
@@ -25,10 +25,11 @@ import time
|
||||
|
||||
|
||||
class SsoCharacter:
|
||||
def __init__(self, charID, name, client, accessToken=None, refreshToken=None):
|
||||
def __init__(self, charID, name, client, server, accessToken=None, refreshToken=None):
|
||||
self.characterID = charID
|
||||
self.characterName = name
|
||||
self.client = client
|
||||
self.server = server
|
||||
self.accessToken = accessToken
|
||||
self.refreshToken = refreshToken
|
||||
self.accessTokenExpires = None
|
||||
@@ -37,6 +38,9 @@ class SsoCharacter:
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def characterDisplay(self):
|
||||
return "{} [{}]".format(self.characterName, self.server)
|
||||
def is_token_expired(self):
|
||||
if self.accessTokenExpires is None:
|
||||
return True
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import config
|
||||
import gui.mainFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.preferenceView import PreferenceView
|
||||
from service.esi import Esi
|
||||
from service.settings import EsiSettings
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -82,7 +84,9 @@ class PFEsiPref(PreferenceView):
|
||||
|
||||
def OnServerChange(self, event):
|
||||
source = self.chESIserver.GetString(self.chESIserver.GetSelection())
|
||||
self.settings.set("server",source)
|
||||
esiService = Esi.getInstance()
|
||||
esiService.init(config.supported_servers[source])
|
||||
self.settings.set("server", source)
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("eve", "gui")
|
||||
|
||||
@@ -856,7 +856,7 @@ class APIView(wx.Panel):
|
||||
noneID = self.charChoice.Append(_t("None"), None)
|
||||
|
||||
for char in ssoChars:
|
||||
currId = self.charChoice.Append(char.characterName, char.ID)
|
||||
currId = self.charChoice.Append(char.characterDisplay, char.ID)
|
||||
|
||||
if sso is not None and char.ID == sso.ID:
|
||||
self.charChoice.SetSelection(currId)
|
||||
|
||||
@@ -96,7 +96,7 @@ class EveFittings(AuxiliaryFrame):
|
||||
|
||||
self.charChoice.Clear()
|
||||
for char in chars:
|
||||
self.charChoice.Append(char.characterName, char.ID)
|
||||
self.charChoice.Append(char.characterDisplay, char.ID)
|
||||
if len(chars) > 0:
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
@@ -330,7 +330,7 @@ class ExportToEve(AuxiliaryFrame):
|
||||
|
||||
self.charChoice.Clear()
|
||||
for char in chars:
|
||||
self.charChoice.Append(char.characterName, char.ID)
|
||||
self.charChoice.Append(char.characterDisplay, char.ID)
|
||||
|
||||
if len(chars) > 0:
|
||||
self.charChoice.SetSelection(0)
|
||||
@@ -414,6 +414,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
|
||||
self.lcCharacters.InsertColumn(0, heading=_t('Character'))
|
||||
self.lcCharacters.InsertColumn(1, heading=_t('Character ID'))
|
||||
self.lcCharacters.InsertColumn(2, heading=_t('Server'))
|
||||
|
||||
self.popCharList()
|
||||
|
||||
@@ -476,9 +477,11 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
self.lcCharacters.InsertItem(index, char.characterName)
|
||||
self.lcCharacters.SetItem(index, 1, str(char.characterID))
|
||||
self.lcCharacters.SetItemData(index, char.ID)
|
||||
self.lcCharacters.SetItem(index, 2, char.server or "<unknown>")
|
||||
|
||||
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
|
||||
self.lcCharacters.SetColumnWidth(2, wx.LIST_AUTOSIZE)
|
||||
|
||||
def addChar(self, event):
|
||||
try:
|
||||
|
||||
@@ -69,8 +69,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
|
||||
|
||||
@@ -109,7 +109,7 @@ class Esi(EsiAccess):
|
||||
with gui.ssoLogin.SsoLogin() as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
message = {}
|
||||
if (self.server_name == "Serenity"):
|
||||
if (self.default_server_name == "Serenity"):
|
||||
import re
|
||||
s=re.search(r'(?<=code=)[a-zA-Z0-9\-_]*',dlg.ssoInfoCtrl.Value.strip())
|
||||
if s:
|
||||
@@ -146,14 +146,14 @@ class Esi(EsiAccess):
|
||||
def handleLogin(self, message):
|
||||
auth_response, data = self.auth(message['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):
|
||||
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)
|
||||
|
||||
|
||||
@@ -30,13 +30,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,11 +56,11 @@ class APIException(Exception):
|
||||
|
||||
|
||||
class EsiAccess:
|
||||
server_meta = {}
|
||||
def __init__(self):
|
||||
self.settings = EsiSettings.getInstance()
|
||||
self.server_name=self.settings.get('server')
|
||||
self.server_base: ApiBase = supported_servers[self.server_name]
|
||||
|
||||
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 = {
|
||||
@@ -81,21 +74,25 @@ class EsiAccess:
|
||||
|
||||
# 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)
|
||||
|
||||
meta_call = cached_session.get("https://%s/.well-known/oauth-authorization-server" % self.server_base.sso)
|
||||
def init(self, server_base):
|
||||
self.server_base: config.ApiServer = server_base
|
||||
self.server_name = self.server_base.name
|
||||
meta_call = self.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"])
|
||||
jwks_call = self.cached_session.get(self.server_meta["jwks_uri"])
|
||||
jwks_call.raise_for_status()
|
||||
self.jwks = jwks_call.json()
|
||||
|
||||
@@ -117,10 +114,7 @@ class EsiAccess:
|
||||
|
||||
@property
|
||||
def client_id(self):
|
||||
if (self.server_name == "Serenity"):
|
||||
return self.settings.get('clientID') or config.API_CLIENT_ID_SERENITY
|
||||
else:
|
||||
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):
|
||||
@@ -145,10 +139,11 @@ class EsiAccess:
|
||||
'redirect': redirect,
|
||||
'state': self.state
|
||||
}
|
||||
|
||||
if(self.server_name=="Serenity"):
|
||||
args = {
|
||||
'response_type': 'code',
|
||||
'redirect_uri': config.SSO_CALLBACK_SERENITY,
|
||||
'redirect_uri': self.server_base.callback,
|
||||
'client_id': self.client_id,
|
||||
'scope': ' '.join(scopes),
|
||||
'state': 'hilltech',
|
||||
@@ -157,7 +152,7 @@ class EsiAccess:
|
||||
else:
|
||||
args = {
|
||||
'response_type': 'code',
|
||||
'redirect_uri': config.SSO_CALLBACK,
|
||||
'redirect_uri': self.server_base.callback,
|
||||
'client_id': self.client_id,
|
||||
'scope': ' '.join(scopes),
|
||||
'code_challenge': code_challenge,
|
||||
@@ -264,6 +259,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:
|
||||
@@ -292,17 +292,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
|
||||
|
||||
@@ -389,7 +389,7 @@ class EsiSettings:
|
||||
self.settings[type] = value
|
||||
|
||||
def keys(self):
|
||||
return list({"Tranquility":"Tranquility","Singularity":"Singularity","Serenity":"Serenity"})
|
||||
return config.supported_servers.keys()
|
||||
|
||||
|
||||
class StatViewSettings:
|
||||
|
||||
Reference in New Issue
Block a user