diff --git a/package/contents/scripts/google_redirect.py b/package/contents/scripts/google_redirect.py new file mode 100755 index 00000000..bae0d6f0 --- /dev/null +++ b/package/contents/scripts/google_redirect.py @@ -0,0 +1,69 @@ +"""Script to handle oauth redirects from Google""" + +import json +import urllib.parse +import urllib.request +import urllib.error +import argparse +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlparse, parse_qs + +client_id = client_secret = listen_port = None + + +def exchange_code_for_token(code): + # Exchange code for token from https://oauth2.googleapis.com/token + # using the following POST request: + token_params = { + "code": code, + "client_id": client_id, + "client_secret": client_secret, + "redirect_uri": "http://127.0.0.1:{}/".format(listen_port), + "grant_type": "authorization_code", + } + data = urllib.parse.urlencode(token_params).encode("utf-8") + req = urllib.request.Request("https://oauth2.googleapis.com/token", data) + response = urllib.request.urlopen(req) + token_data = json.loads(response.read().decode("utf-8")) + return token_data + + +class OAuthRedirectHandler(BaseHTTPRequestHandler): + def do_GET(self): + query = urlparse(self.path).query + params = parse_qs(query) + # handle OAuth redirect here + if "code" in params: + code = params["code"][0] + try: + token_data = exchange_code_for_token(code) + except urllib.error.HTTPError as e: + print(e.read().decode("utf-8")) + self.wfile.write(b"Handling redirect failed.") + raise SystemExit(1) + print(json.dumps(token_data, sort_keys=True)) + + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write( + b"OAuth redirect handled successfully. You can close this tab now." + ) + raise SystemExit(0) + self.wfile.write(b"Missing code parameter in redirect.") + raise SystemExit(1) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--client_id", required=True) + parser.add_argument("--client_secret", required=True) + parser.add_argument("--listen_port", required=True, type=int) + args = parser.parse_args() + client_id = args.client_id + client_secret = args.client_secret + listen_port = args.listen_port + + server_address = ("", listen_port) + httpd = HTTPServer(server_address, OAuthRedirectHandler) + httpd.serve_forever() diff --git a/package/contents/ui/config/ConfigGoogleCalendar.qml b/package/contents/ui/config/ConfigGoogleCalendar.qml index a2109048..efeeb2fe 100644 --- a/package/contents/ui/config/ConfigGoogleCalendar.qml +++ b/package/contents/ui/config/ConfigGoogleCalendar.qml @@ -114,72 +114,15 @@ ConfigPage { visible: !googleLoginManager.isLoggedIn Label { Layout.fillWidth: true - text: i18n("To sync with Google Calendar") + text: i18n("To sync with Google Calendar click the button to first authorize your account.") color: readableNegativeTextColor wrapMode: Text.Wrap } - LinkText { - Layout.fillWidth: true - text: i18n("Visit %2 (opens in your web browser). After you login and give permission to access your calendar, it will give you a code to paste below.", googleLoginManager.authorizationCodeUrl, 'https://accounts.google.com/...') - color: readableNegativeTextColor - wrapMode: Text.Wrap - - // Tooltip - // QQC2.ToolTip.visible: !!hoveredLink - // QQC2.ToolTip.text: googleLoginManager.authorizationCodeUrl - - // ContextMenu - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - if (mouse.button === Qt.RightButton) { - contextMenu.popup() - } - } - onPressAndHold: { - if (mouse.source === Qt.MouseEventNotSynthesized) { - contextMenu.popup() - } - } - - QQC2.Menu { - id: contextMenu - QQC2.MenuItem { - text: i18n("Copy Link") - onTriggered: clipboardHelper.copyText(googleLoginManager.authorizationCodeUrl) - } - } - - TextEdit { - id: clipboardHelper - visible: false - function copyText(text) { - clipboardHelper.text = text - clipboardHelper.selectAll() - clipboardHelper.copy() - } - } - } - } RowLayout { - TextField { - id: authorizationCodeInput - Layout.fillWidth: true - - placeholderText: i18n("Enter code here (Eg: %1)", '1/2B3C4defghijklmnopqrst-uvwxyz123456789ab-cdeFGHIJKlmnio') - text: "" - } Button { - text: i18n("Submit") + text: i18n("Authorize") onClicked: { - if (authorizationCodeInput.text) { - googleLoginManager.fetchAccessToken({ - authorizationCode: authorizationCodeInput.text, - }) - } else { - messageWidget.err(i18n("Invalid Google Authorization Code")) - } + googleLoginManager.fetchAccessToken() } } } diff --git a/package/contents/ui/config/GoogleLoginManager.qml b/package/contents/ui/config/GoogleLoginManager.qml index 2c2cd574..728a5de7 100644 --- a/package/contents/ui/config/GoogleLoginManager.qml +++ b/package/contents/ui/config/GoogleLoginManager.qml @@ -5,6 +5,8 @@ import "../lib/Requests.js" as Requests Item { id: session + ExecUtil { id: executable } + property int callbackListenPort: 8001 Logger { id: logger @@ -72,49 +74,43 @@ Item { signal sessionReset() signal error(string err) - - //--- readonly property string authorizationCodeUrl: { var url = 'https://accounts.google.com/o/oauth2/v2/auth' url += '?scope=' + encodeURIComponent('https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/tasks') url += '&response_type=code' - url += '&redirect_uri=' + encodeURIComponent('urn:ietf:wg:oauth:2.0:oob') + url += '&redirect_uri=' + encodeURIComponent("http://127.0.0.1:" + callbackListenPort.toString() + "/") url += '&client_id=' + encodeURIComponent(plasmoid.configuration.latestClientId) return url } - function fetchAccessToken(args) { - var url = 'https://www.googleapis.com/oauth2/v4/token' - Requests.post({ - url: url, - data: { - client_id: plasmoid.configuration.latestClientId, - client_secret: plasmoid.configuration.latestClientSecret, - code: args.authorizationCode, - grant_type: 'authorization_code', - redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', - }, - }, function(err, data, xhr) { - logger.debug('/oauth2/v4/token Response', data) - // Check for errors - if (err) { - handleError(err, null) + function fetchAccessToken() { + var cmd = [ + 'python3', + plasmoid.file("", "scripts/google_redirect.py"), + "--client_id", plasmoid.configuration.latestClientId, + "--client_secret", plasmoid.configuration.latestClientSecret, + "--listen_port", callbackListenPort.toString(), + ] + + Qt.openUrlExternally(authorizationCodeUrl); + + executable.exec(cmd, function(cmd, exitCode, exitStatus, stdout, stderr) { + if (exitCode) { + logger.log('fetchAccessToken.stderr', stderr) + logger.log('fetchAccessToken.stdout', stdout) return } + try { - data = JSON.parse(data) + var data = JSON.parse(stdout) + updateAccessToken(data) } catch (e) { - handleError('Error parsing /oauth2/v4/token data as JSON', null) - return - } - if (data && data.error) { - handleError(err, data) + logger.log('fetchAccessToken.e', e) + handleError('Error parsing JSON', null) return } - // Ready - updateAccessToken(data) }) }