1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-14 08:17:13 +03:00

Support authentication when running from source

Fixes #395
This commit is contained in:
Jonathan Harris 2019-01-14 21:33:41 +00:00
parent 2f154ffd03
commit fd492cdf94
4 changed files with 78 additions and 33 deletions

View File

@ -53,7 +53,7 @@ import prefs
import plug
from hotkey import hotkeymgr
from monitor import monitor
from protocol import ProtocolHandler
from protocol import protocolhandler
from dashboard import dashboard
from theme import theme
@ -71,7 +71,7 @@ class AppWindow:
def __init__(self, master):
# Start a protocol handler to handle cAPI registration
self.protocolhandler = ProtocolHandler(master)
protocolhandler.setmaster(master)
self.holdofftime = config.getint('querytime') + companion.holdoff
@ -588,7 +588,7 @@ class AppWindow:
# cAPI auth
def auth(self, event=None):
try:
companion.session.auth_callback(self.protocolhandler.lastpayload)
companion.session.auth_callback()
self.status['text'] = _('Authentication successful') # Successfully authenticated with the Frontier website
except companion.ServerError as e:
self.status['text'] = unicode(e)
@ -680,7 +680,7 @@ class AppWindow:
if platform!='darwin' or self.w.winfo_rooty()>0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+')))
self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
self.protocolhandler.close()
protocolhandler.close()
hotkeymgr.unregister()
dashboard.close()
monitor.close()

View File

@ -219,22 +219,22 @@ Windows:
Running from source
--------
Download and extract the source code of the [latest release](https://github.com/Marginal/EDMarketConnector/releases/latest).
Download and extract the [latest source code](https://github.com/Marginal/EDMarketConnector/archive/master.zip) (or fork and clone if you're comfortable with using `git`). To use the cAPI you will need to visit the [Frontier authentication website](https://auth.frontierstore.net/), login with your Frontier credentials, register a "Developer App" and obtain the "Client Id".
Mac:
* Requires the Python “keyring”, “requests” and “watchdog” modules, plus an up-to-date “py2app” module if you also want to package the app - install these with `easy_install -U keyring requests watchdog py2app` .
* Run with `python ./EDMarketConnector.py` .
* Run with `CLIENT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX python ./EDMarketConnector.py` .
Windows:
* Requires Python2.7 and the Python “keyring”, “requests” and “watchdog” modules, plus “py2exe” 0.6 if you also want to package the app.
* Run with `EDMarketConnector.py` .
* Run with `set CLIENT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX && EDMarketConnector.py` .
Linux:
* Requires the Python “imaging-tk”, “iniparse”, “keyring” and “requests” modules. On Debian-based systems install these with `sudo apt-get install python-imaging-tk python-iniparse python-keyring python-requests` .
* Run with `./EDMarketConnector.py` .
* Run with `CLIENT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX ./EDMarketConnector.py` .
Command-line
--------

View File

@ -16,11 +16,13 @@ import webbrowser
import zlib
from config import appname, appversion, config
from protocol import protocolhandler
holdoff = 60 # be nice
timeout = 10 # requests timeout
CLIENT_ID = None # Replace with FDev Client Id
CLIENT_ID = os.getenv('CLIENT_ID') # Obtain from https://auth.frontierstore.net/client/signup
SERVER_AUTH = 'https://auth.frontierstore.net'
URL_AUTH = '/auth'
URL_TOKEN = '/token'
@ -189,8 +191,8 @@ class Auth:
print 'Auth\tNew authorization request'
self.verifier = self.base64URLEncode(os.urandom(32))
self.state = self.base64URLEncode(os.urandom(8))
# Won't work under IE <= 10 : https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
webbrowser.open('%s%s?response_type=code&audience=frontier&scope=capi&client_id=%s&code_challenge=%s&code_challenge_method=S256&state=%s&redirect_uri=edmc://auth' % (SERVER_AUTH, URL_AUTH, CLIENT_ID, self.base64URLEncode(hashlib.sha256(self.verifier).digest()), self.state))
# Won't work under IE: https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
webbrowser.open('%s%s?response_type=code&audience=frontier&scope=capi&client_id=%s&code_challenge=%s&code_challenge_method=S256&state=%s&redirect_uri=%s' % (SERVER_AUTH, URL_AUTH, CLIENT_ID, self.base64URLEncode(hashlib.sha256(self.verifier).digest()), self.state, protocolhandler.redirect))
def authorize(self, payload):
# Handle OAuth authorization code callback. Returns access token if successful, otherwise raises CredentialsError
@ -218,7 +220,7 @@ class Auth:
'client_id': CLIENT_ID,
'code_verifier': self.verifier,
'code': data['code'][0],
'redirect_uri': 'edmc://auth',
'redirect_uri': protocolhandler.redirect,
}
r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=timeout)
if r.status_code == requests.codes.ok:
@ -311,11 +313,11 @@ class Session:
# Wait for callback
# Callback from protocol handler
def auth_callback(self, payload):
def auth_callback(self):
if self.state != Session.STATE_AUTH:
raise CredentialsError() # Shouldn't be getting a callback
try:
self.start(self.auth.authorize(payload))
self.start(self.auth.authorize(protocolhandler.lastpayload))
self.auth = None
except:
self.state = Session.STATE_INIT # Will try to authorize again on next login or query

View File

@ -3,27 +3,30 @@
import threading
import urllib2
from sys import platform
import sys
from config import appname
class GenericProtocolHandler:
def __init__(self, master):
self.master = master
def __init__(self):
self.redirect = 'edmc://auth' # Base redirection URL
self.master = None
self.lastpayload = None
def setmaster(self, master):
self.master = master
def close(self):
pass
def event(self, url):
if url.startswith('edmc://'):
self.lastpayload = url[7:]
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
self.lastpayload = url
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
if platform == 'darwin':
if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
import struct
import objc
@ -36,15 +39,14 @@ if platform == 'darwin':
POLL = 100 # ms
def __init__(self, master):
GenericProtocolHandler.__init__(self, master)
def __init__(self):
GenericProtocolHandler.__init__(self)
self.eventhandler = EventHandler.alloc().init()
self.eventhandler.handler = self
self.lasturl = None
def poll(self):
# No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash, so poll.
if self.lasturl:
if self.lasturl and self.lasturl.startswith(self.redirect):
self.event(self.lasturl)
self.lasturl = None
@ -56,11 +58,11 @@ if platform == 'darwin':
return self
def handleEvent_withReplyEvent_(self, event, replyEvent):
self.handler.lasturl = urllib2.unquote(event.paramDescriptorForKeyword_(keyDirectObject).stringValue()).strip()
self.handler.master.after(ProtocolHandler.POLL, self.handler.poll)
protocolhandler.lasturl = urllib2.unquote(event.paramDescriptorForKeyword_(keyDirectObject).stringValue()).strip()
protocolhandler.master.after(ProtocolHandler.POLL, protocolhandler.poll)
elif platform == 'win32':
elif sys.platform == 'win32' and getattr(sys, 'frozen', False):
from ctypes import *
from ctypes.wintypes import *
@ -135,8 +137,8 @@ elif platform == 'win32':
class ProtocolHandler(GenericProtocolHandler):
def __init__(self, master):
GenericProtocolHandler.__init__(self, master)
def __init__(self):
GenericProtocolHandler.__init__(self)
self.thread = threading.Thread(target=self.worker, name='DDE worker')
self.thread.daemon = True
self.thread.start()
@ -177,7 +179,9 @@ elif platform == 'win32':
args = wstring_at(GlobalLock(msg.lParam)).strip()
GlobalUnlock(msg.lParam)
if args.lower().startswith('open("') and args.endswith('")'):
self.event(urllib2.unquote(args[6:-2]).strip())
url = urllib2.unquote(args[6:-2]).strip()
if url.startswith(self.redirect):
self.event(url)
SetForegroundWindow(GetParent(self.master.winfo_id())) # raise app window
PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
else:
@ -190,8 +194,47 @@ elif platform == 'win32':
else:
print 'Failed to register DDE for cAPI'
else: # Linux
else: # Linux / Run from source
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
class ProtocolHandler(GenericProtocolHandler):
pass
def __init__(self):
GenericProtocolHandler.__init__(self)
self.httpd = HTTPServer(('localhost', 0), HTTPRequestHandler)
self.redirect = 'http://localhost:%d/auth' % self.httpd.server_port
self.thread = threading.Thread(target=self.worker, name='DDE worker')
self.thread.daemon = True
self.thread.start()
def close(self):
thread = self.thread
if thread:
self.thread = None
self.httpd.shutdown()
thread.join() # Wait for it to quit
def worker(self):
self.httpd.serve_forever()
class HTTPRequestHandler(BaseHTTPRequestHandler):
def do_HEAD(self):
self.do_GET()
def do_GET(self):
url = urllib2.unquote(self.path)
if url.startswith('/auth'):
protocolhandler.event(url)
self.send_response(200)
else:
self.send_response(404) # Not found
self.end_headers()
def log_request(self, code, size=None):
pass
# singleton
protocolhandler = ProtocolHandler()