From 7b0f672f049fd3a64d856a31ea4d911323e2e36c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 9 Feb 2018 18:25:53 -0500 Subject: [PATCH] Get encrypted refresh tokens working --- config.py | 19 ++++++++++++++++++- eos/saveddata/ssocharacter.py | 13 ++----------- gui/crestFittings.py | 2 +- gui/mainFrame.py | 1 - service/esi.py | 25 +++++++++++++++++++++++-- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/config.py b/config.py index bac448989..fb834f33e 100644 --- a/config.py +++ b/config.py @@ -5,6 +5,8 @@ from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, StreamHandler, TimedRotatingFileHandler, WARNING import hashlib +from cryptography.fernet import Fernet + pyfalog = Logger(__name__) # Load variable overrides specific to distribution type @@ -34,6 +36,8 @@ gameDB = None logPath = None loggingLevel = None logging_setup = None +cipher = None +clientHash = 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' @@ -48,7 +52,7 @@ LOGLEVEL_MAP = { def getClientSecret(): - return hashlib.sha3_256("This is a secret, this will not remain in here for long".encode('utf-8')).hexdigest() + return clientHash def isFrozen(): @@ -90,6 +94,8 @@ def defPaths(customSavePath=None): global gameDB global saveInRoot global logPath + global cipher + global clientHash pyfalog.debug("Configuring Pyfa") @@ -114,6 +120,17 @@ def defPaths(customSavePath=None): __createDirs(savePath) + # get cipher object based on secret key of this client (stores encryption cipher for ESI refresh token) + secret_file = os.path.join(savePath, "{}.secret".format(hashlib.sha3_256(pyfaPath.encode('utf-8')).hexdigest())) + if not os.path.exists(secret_file): + with open(secret_file, "wb") as _file: + _file.write(Fernet.generate_key()) + + with open(secret_file, 'rb') as fp: + key = fp.read() + clientHash = hashlib.sha3_256(key).hexdigest() + cipher = Fernet(key) + # if isFrozen(): # os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem") # os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem") diff --git a/eos/saveddata/ssocharacter.py b/eos/saveddata/ssocharacter.py index 95314f5e1..db15442d0 100644 --- a/eos/saveddata/ssocharacter.py +++ b/eos/saveddata/ssocharacter.py @@ -31,8 +31,10 @@ class SsoCharacter(object): self.client = client self.accessToken = accessToken self.refreshToken = refreshToken + self.accessTokenExpires = None self.esi_client = None + @reconstructor def init(self): self.esi_client = None @@ -47,14 +49,3 @@ class SsoCharacter(object): self.accessTokenExpires - datetime.datetime.utcnow() ).total_seconds() } - - def update_token(self, tokenResponse): - """ helper function to update token data from SSO response """ - self.accessToken = tokenResponse['access_token'] - self.accessTokenExpires = datetime.datetime.fromtimestamp( - time.time() + tokenResponse['expires_in'], - ) - if 'refresh_token' in tokenResponse: - self.refreshToken = tokenResponse['refresh_token'] - if self.esi_client is not None: - self.esi_client.security.update_token(tokenResponse) \ No newline at end of file diff --git a/gui/crestFittings.py b/gui/crestFittings.py index ed8750355..32c863812 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.esi import Esi, CrestModes +from service.esi import Esi pyfalog = Logger(__name__) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 7cef8ea3f..7e6eb9db2 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -83,7 +83,6 @@ import webbrowser import wx.adv from service.esi import Esi -from service.esi import CrestModes from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt disableOverrideEditor = False diff --git a/service/esi.py b/service/esi.py index 3e47af41a..5014c63c8 100644 --- a/service/esi.py +++ b/service/esi.py @@ -10,6 +10,7 @@ import json import os import eos.db +import datetime from eos.enum import Enum from eos.saveddata.ssocharacter import SsoCharacter import gui.globalEvents as GE @@ -104,7 +105,7 @@ class Esi(object): char = eos.db.getSsoCharacter(id, config.getClientSecret()) if char is not None and char.esi_client is None: char.esi_client = Esi.genEsiClient() - char.esi_client.security.update_token(char.get_sso_data()) + char.esi_client.security.update_token(Esi.get_sso_data(char)) return char def getSkills(self, id): @@ -144,6 +145,26 @@ class Esi(object): resp = char.esi_client.request(op) return resp.data + @staticmethod + def get_sso_data(char): + """ Little "helper" function to get formated data for esipy security + """ + return { + 'access_token': char.accessToken, + 'refresh_token': config.cipher.decrypt(char.refreshToken).decode(), + 'expires_in': (char.accessTokenExpires - datetime.datetime.utcnow()).total_seconds() + } + + @staticmethod + def update_token(char, tokenResponse): + """ helper function to update token data from SSO response """ + char.accessToken = tokenResponse['access_token'] + char.accessTokenExpires = datetime.datetime.fromtimestamp(time.time() + tokenResponse['expires_in']) + if 'refresh_token' in tokenResponse: + char.refreshToken = config.cipher.encrypt(tokenResponse['refresh_token'].encode()) + if char.esi_client is not None: + char.esi_client.security.update_token(tokenResponse) + def stopServer(self): pyfalog.debug("Stopping Server") self.httpd.stop() @@ -200,7 +221,7 @@ class Esi(object): 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 + Esi.update_token(currentCharacter, auth_response) # this also sets the esi security token eos.db.save(currentCharacter)