mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
.. its dead (not updated for 5 years) .. it introduces dependency issues with openssl/crypto/icu on Linux distros .. we don't need it, since OAuthDialog does the heavy lifting we need (ok, its not pretty but it works). .. old code moved into the deprecated folder Fixes #2881
708 lines
28 KiB
C++
708 lines
28 KiB
C++
/*
|
|
* Copyright (c) 2010 Justin Knotzke (jknotzke@shampoo.ca)
|
|
* Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#include "Secrets.h"
|
|
|
|
#include "OAuthManager.h"
|
|
#include "OAuthDialog.h"
|
|
#include "Athlete.h"
|
|
#include "Context.h"
|
|
#include "Settings.h"
|
|
#include "Colors.h"
|
|
#include "TimeUtils.h"
|
|
|
|
#if QT_VERSION > 0x050000
|
|
#include "GoogleDrive.h"
|
|
#include "PolarFlow.h"
|
|
|
|
#include <QJsonParseError>
|
|
#endif
|
|
|
|
OAuthManager::OAuthManager(Context *context, OAuthSite site, CloudService *service, QString baseURL, QString clientsecret) :
|
|
context(context), site(site), service(service), baseURL(baseURL), clientsecret(clientsecret)
|
|
{
|
|
|
|
if (service) { // ultimately this will be the only way this works
|
|
if (service->id() == "Strava") site = this->site = STRAVA;
|
|
if (service->id() == "Dropbox") site = this->site = DROPBOX;
|
|
if (service->id() == "Cycling Analytics") site = this->site = CYCLING_ANALYTICS;
|
|
if (service->id() == "Google Drive") site = this->site = GOOGLE_DRIVE;
|
|
if (service->id() == "University of Kent") site = this->site = KENTUNI;
|
|
if (service->id() == "Today's Plan") site = this->site = TODAYSPLAN;
|
|
if (service->id() == "Withings") site = this->site = WITHINGS;
|
|
if (service->id() == "PolarFlow") site = this->site = POLAR;
|
|
if (service->id() == "SportTracks.mobi") site = this->site = SPORTTRACKS;
|
|
if (service->id() == "Xert") site = this->site = XERT;
|
|
}
|
|
|
|
// check if SSL is available - if not - message and end
|
|
if (!QSslSocket::supportsSsl()) {
|
|
QString text = QString(tr("SSL Security Libraries required for 'Authorise' are missing in this installation."));
|
|
QMessageBox sslMissing(QMessageBox::Critical, tr("Authorization Error"), text);
|
|
sslMissing.exec();
|
|
noSSLlib = true;
|
|
return;
|
|
}
|
|
|
|
// ignore responses to false, used by POLARFLOW when binding the user
|
|
ignore = false;
|
|
|
|
// SSL is available - so authorisation can take place
|
|
noSSLlib = false;
|
|
}
|
|
|
|
void
|
|
OAuthManager::authorize()
|
|
{
|
|
//
|
|
// All services have some kind of initial authorisation URL where the user needs
|
|
// to login and confirm they are willing to authorise the particular app and
|
|
// provide a temporary token to get the real token with
|
|
//
|
|
QString urlstr = "";
|
|
|
|
if (site == STRAVA) {
|
|
|
|
urlstr = QString("https://www.strava.com/oauth/authorize?");
|
|
urlstr.append("client_id=").append(GC_STRAVA_CLIENT_ID).append("&");
|
|
urlstr.append("scope=view_private,write&");
|
|
urlstr.append("redirect_uri=http://www.goldencheetah.org/&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("approval_prompt=force");
|
|
|
|
} else if (site == DROPBOX) {
|
|
|
|
urlstr = QString("https://www.dropbox.com/oauth2/authorize?");
|
|
#ifdef GC_DROPBOX_CLIENT_ID
|
|
urlstr.append("client_id=").append(GC_DROPBOX_CLIENT_ID).append("&");
|
|
#endif
|
|
urlstr.append("redirect_uri=https://goldencheetah.github.io/blank.html&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("force_reapprove=true");
|
|
|
|
} else if (site == CYCLING_ANALYTICS) {
|
|
|
|
urlstr = QString("https://www.cyclinganalytics.com/api/auth?");
|
|
urlstr.append("client_id=").append(GC_CYCLINGANALYTICS_CLIENT_ID).append("&");
|
|
urlstr.append("scope=modify_rides&");
|
|
urlstr.append("redirect_uri=http://www.goldencheetah.org/&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("approval_prompt=force");
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
|
|
} else if (site == GOOGLE_DRIVE) {
|
|
|
|
const QString scope = service->getSetting(GC_GOOGLE_DRIVE_AUTH_SCOPE, "drive.appdata").toString();
|
|
// OAUTH 2.0 - Google flow for installed applications
|
|
urlstr = QString("https://accounts.google.com/o/oauth2/auth?");
|
|
// We only request access to the application data folder, not all files.
|
|
urlstr.append("scope=https://www.googleapis.com/auth/" + scope + "&");
|
|
urlstr.append("redirect_uri=urn:ietf:wg:oauth:2.0:oob&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("client_id=").append(GC_GOOGLE_DRIVE_CLIENT_ID);
|
|
|
|
} else if (site == KENTUNI) {
|
|
|
|
const QString scope = service->getSetting(GC_UOK_GOOGLE_DRIVE_AUTH_SCOPE, "drive.appdata").toString();
|
|
|
|
// OAUTH 2.0 - Google flow for installed applications
|
|
urlstr = QString("https://accounts.google.com/o/oauth2/auth?");
|
|
// We only request access to the application data folder, not all files.
|
|
urlstr.append("scope=https://www.googleapis.com/auth/" + scope + "&");
|
|
urlstr.append("redirect_uri=urn:ietf:wg:oauth:2.0:oob&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("client_id=").append(GC_GOOGLE_DRIVE_CLIENT_ID);
|
|
|
|
#endif
|
|
|
|
} else if (site == TODAYSPLAN) {
|
|
|
|
//urlstr = QString("https://whats.todaysplan.com.au/en/authorize/"); //XXX fixup below when pages.cpp goes
|
|
if (baseURL=="") baseURL=service->getSetting(GC_TODAYSPLAN_URL, "https://whats.todaysplan.com.au").toString();
|
|
urlstr = QString("%1/authorize/").arg(baseURL);
|
|
urlstr.append(GC_TODAYSPLAN_CLIENT_ID);
|
|
|
|
} else if (site == POLAR) {
|
|
|
|
// OAUTH 2.0 - Google flow for installed applications
|
|
urlstr = QString("%1?").arg(GC_POLARFLOW_OAUTH_URL);
|
|
// We only request access to the application data folder, not all files.
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("client_id=").append(GC_POLARFLOW_CLIENT_ID);
|
|
|
|
} else if (site == SPORTTRACKS) {
|
|
|
|
// We only request access to the application data folder, not all files.
|
|
urlstr = QString("https://api.sporttracks.mobi/oauth2/authorize?");
|
|
urlstr.append("redirect_uri=http://www.goldencheetah.org&");
|
|
urlstr.append("state=xyzzy&");
|
|
urlstr.append("response_type=code&");
|
|
urlstr.append("client_id=").append(GC_SPORTTRACKS_CLIENT_ID);
|
|
|
|
} else if (site == WITHINGS) {
|
|
|
|
// Withings is the only service that uses KQOauth for now.
|
|
|
|
#ifdef GC_HAVE_KQOAUTH
|
|
oauthRequest = new KQOAuthRequest;
|
|
oauthManager = new KQOAuthManager(this);
|
|
|
|
connect(oauthManager, SIGNAL(temporaryTokenReceived(QString,QString)), this, SLOT(onTemporaryTokenReceived(QString, QString)));
|
|
connect(oauthManager, SIGNAL(authorizationReceived(QString,QString)), this, SLOT( onAuthorizationReceived(QString, QString)));
|
|
connect(oauthManager, SIGNAL(accessTokenReceived(QString,QString)), this, SLOT(onAccessTokenReceived(QString,QString)));
|
|
connect(oauthManager, SIGNAL(requestReady(QByteArray)), this, SLOT(onRequestReady(QByteArray)));
|
|
connect(oauthManager, SIGNAL(authorizationPageRequested(QUrl)), this, SLOT(onAuthorizationPageRequested(QUrl)));
|
|
|
|
oauthRequest->initRequest(KQOAuthRequest::TemporaryCredentials, QUrl("https://oauth.withings.com/account/request_token"));
|
|
//oauthRequest->setEnableDebugOutput(true);
|
|
oauthRequest->setHttpMethod(KQOAuthRequest::GET);
|
|
oauthRequest->setConsumerKey(GC_WITHINGS_CONSUMER_KEY);
|
|
oauthRequest->setConsumerSecretKey(GC_WITHINGS_CONSUMER_SECRET);
|
|
//oauthRequest->setCallbackUrl(QUrl("http://www.goldencheetah.org"));
|
|
oauthManager->setHandleUserAuthorization(true); // false to use callback
|
|
oauthManager->setHandleAuthorizationPageOpening(false);
|
|
|
|
oauthManager->executeRequest(oauthRequest);
|
|
#endif
|
|
|
|
} else if (site == XERT) {
|
|
getTokenWithCode("");
|
|
}
|
|
|
|
//
|
|
// STEP 1: LOGIN AND AUTHORISE THE APPLICATION
|
|
//
|
|
/*if (site == DROPBOX || site == STRAVA || site == CYCLING_ANALYTICS || site == POLAR || site == SPORTTRACKS || site == GOOGLE_DRIVE || site == KENTUNI || site == TODAYSPLAN) {
|
|
|
|
url = QUrl(urlstr);
|
|
view->setUrl(url);
|
|
|
|
// connects
|
|
connect(view, SIGNAL(urlChanged(const QUrl&)), this, SLOT(urlChanged(const QUrl&)));
|
|
connect(view, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
|
|
}*/
|
|
|
|
}
|
|
|
|
// just ignore SSL handshake errors at all times
|
|
void
|
|
OAuthManager::onSslErrors(QNetworkReply *reply, const QList<QSslError>&)
|
|
{
|
|
reply->ignoreSslErrors();
|
|
}
|
|
|
|
|
|
#ifdef GC_HAVE_KQOAUTH
|
|
|
|
//
|
|
// KQOauth call backs
|
|
//
|
|
void
|
|
OAuthManager::onTemporaryTokenReceived(QString, QString)
|
|
{
|
|
//qDebug() << "onTemporaryTokenReceived";
|
|
QUrl userAuthURL;
|
|
|
|
if (site == WITHINGS) {
|
|
userAuthURL = "https://oauth.withings.com/account/authorize";
|
|
}
|
|
|
|
if(oauthManager->lastError() == KQOAuthManager::NoError) {
|
|
oauthManager->getUserAuthorization(userAuthURL);
|
|
} else
|
|
qDebug() << "error" << oauthManager->lastError();
|
|
|
|
}
|
|
|
|
void
|
|
OAuthManager::onAuthorizationReceived(QString, QString)
|
|
{
|
|
//qDebug() << "Authorization token received: " << token << verifier;
|
|
|
|
if (site == WITHINGS) {
|
|
oauthManager->getUserAccessTokens(QUrl("https://oauth.withings.com/account/access_token"));
|
|
}
|
|
|
|
if(oauthManager->lastError() != KQOAuthManager::NoError) {
|
|
QString error = QString(tr("Error fetching OAuth credentials - Endpoint: /oauth/access_token"));
|
|
QMessageBox oautherr(QMessageBox::Critical, tr("Authorization Error"), error);
|
|
oautherr.exec();
|
|
//accept();
|
|
}
|
|
}
|
|
|
|
void
|
|
OAuthManager::onAccessTokenReceived(QString token, QString tokenSecret)
|
|
{
|
|
//qDebug() << "Access token received: " << token << tokenSecret;
|
|
|
|
QString info;
|
|
if (site == WITHINGS) {
|
|
service->setSetting(GC_WITHINGS_TOKEN, token);
|
|
service->setSetting(GC_WITHINGS_SECRET, tokenSecret);
|
|
appsettings->setCValue(context->athlete->cyclist, GC_NOKIA_REFRESH_TOKEN, "");
|
|
|
|
info = QString(tr("Nokia Health (Withings) authorization was successful."));
|
|
}
|
|
|
|
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
//accept();
|
|
}
|
|
|
|
|
|
void
|
|
OAuthManager::onAuthorizedRequestDone() {} // request sent - do nothing
|
|
|
|
void
|
|
OAuthManager::onRequestReady(QByteArray response)
|
|
{
|
|
//qDebug() << "Response received: " << response;
|
|
|
|
QString r = response;
|
|
if (r.contains("\"errors\"", Qt::CaseInsensitive)) {
|
|
|
|
QMessageBox oautherr(QMessageBox::Critical, tr("Error in authorization"),
|
|
tr("There was an error during authorization. Please check the error description."));
|
|
oautherr.setDetailedText(r); // probably blank
|
|
oautherr.exec();
|
|
|
|
} else {
|
|
|
|
if (site == WITHINGS) {
|
|
|
|
QString userid;
|
|
|
|
#if QT_VERSION > 0x050000
|
|
QUrlQuery params;
|
|
params.setQuery(response);
|
|
#else
|
|
QUrl params;
|
|
params.setEncodedQuery(response);
|
|
#endif
|
|
userid = params.queryItemValue("userid");
|
|
|
|
if (userid.isEmpty() == false) {
|
|
service->setSetting(GC_WIUSER, userid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OAuthManager::onAuthorizationPageRequested(QUrl url) {
|
|
// open Authorization page in view
|
|
view->setUrl(url);
|
|
|
|
}
|
|
#endif // KQOAuth callbacks used by Withings only
|
|
|
|
|
|
//
|
|
// STEP 2: AUTHORISATION REDIRECT WITH TEMPORARY CODE
|
|
//
|
|
// When the URL changes, it will be the redirect with the temporary token after
|
|
// the initial authorisation. The URL will have some parameters to indicate this
|
|
// if they exist we should intercept the redirect to get the permanent token.
|
|
// If they don't get passed then we don't need to do anything.
|
|
//
|
|
void
|
|
OAuthManager::getTokenWithCode(QString code)
|
|
{
|
|
QString authheader;
|
|
|
|
// sites that use this scheme
|
|
if (site == DROPBOX || site == STRAVA || site == CYCLING_ANALYTICS || site == TODAYSPLAN || site == POLAR || site == SPORTTRACKS || site == XERT) {
|
|
|
|
|
|
// sporttracks insists on passing state
|
|
if (code.endsWith("&state=xyzzy")) code = code.mid(0,code.length()-12);
|
|
|
|
QByteArray data;
|
|
#if QT_VERSION > 0x050000
|
|
QUrlQuery params;
|
|
#else
|
|
QUrl params;
|
|
#endif
|
|
QString urlstr = "";
|
|
|
|
// now get the final token to store
|
|
if (site == DROPBOX) {
|
|
|
|
urlstr = QString("https://api.dropboxapi.com/oauth2/token?");
|
|
urlstr.append("redirect_uri=https://goldencheetah.github.io/blank.html&");
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
#ifdef GC_DROPBOX_CLIENT_ID
|
|
params.addQueryItem("client_id", GC_DROPBOX_CLIENT_ID);
|
|
#endif
|
|
#ifdef GC_DROPBOX_CLIENT_SECRET
|
|
params.addQueryItem("client_secret", GC_DROPBOX_CLIENT_SECRET);
|
|
#endif
|
|
|
|
} else if (site == POLAR) {
|
|
|
|
urlstr = QString("%1?").arg(GC_POLARFLOW_TOKEN_URL);
|
|
urlstr.append("redirect_uri=http://www.goldencheetah.org");
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
#if (defined GC_POLARFLOW_CLIENT_ID) && (defined GC_POLARFLOW_CLIENT_SECRET)
|
|
authheader = QString("%1:%2").arg(GC_POLARFLOW_CLIENT_ID).arg(GC_POLARFLOW_CLIENT_SECRET);
|
|
#endif
|
|
|
|
} else if (site == SPORTTRACKS) {
|
|
|
|
urlstr = QString("https://api.sporttracks.mobi/oauth2/token?");
|
|
params.addQueryItem("client_id", GC_SPORTTRACKS_CLIENT_ID);
|
|
params.addQueryItem("client_secret", GC_SPORTTRACKS_CLIENT_SECRET);
|
|
params.addQueryItem("redirect_uri","http://www.goldencheetah.org");
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
|
|
} else if (site == STRAVA) {
|
|
|
|
urlstr = QString("https://www.strava.com/oauth/token?");
|
|
params.addQueryItem("client_id", GC_STRAVA_CLIENT_ID);
|
|
#ifdef GC_STRAVA_CLIENT_SECRET
|
|
params.addQueryItem("client_secret", GC_STRAVA_CLIENT_SECRET);
|
|
#endif
|
|
|
|
} else if (site == CYCLING_ANALYTICS) {
|
|
|
|
urlstr = QString("https://www.cyclinganalytics.com/api/token?");
|
|
params.addQueryItem("client_id", GC_CYCLINGANALYTICS_CLIENT_ID);
|
|
#ifdef GC_CYCLINGANALYTICS_CLIENT_SECRET
|
|
params.addQueryItem("client_secret", GC_CYCLINGANALYTICS_CLIENT_SECRET);
|
|
#endif
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
|
|
} else if (site == TODAYSPLAN) {
|
|
|
|
if (baseURL=="") baseURL=service->getSetting(GC_TODAYSPLAN_URL, "https://whats.todaysplan.com.au").toString();
|
|
urlstr = QString("%1/rest/oauth/access_token?").arg(baseURL);
|
|
params.addQueryItem("client_id", GC_TODAYSPLAN_CLIENT_ID);
|
|
#ifdef GC_TODAYSPLAN_CLIENT_SECRET
|
|
if (clientsecret != "") //XXX get rid when pages.cpp goes
|
|
params.addQueryItem("client_secret", clientsecret);
|
|
else if (service->getSetting(GC_TODAYSPLAN_USERKEY, "").toString() != "")
|
|
params.addQueryItem("client_secret", service->getSetting(GC_TODAYSPLAN_USERKEY, "").toString());
|
|
else
|
|
params.addQueryItem("client_secret", GC_TODAYSPLAN_CLIENT_SECRET);
|
|
|
|
#endif
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
params.addQueryItem("redirect_uri", "https://goldencheetah.github.io/blank.html");
|
|
|
|
} else if (site == XERT) {
|
|
|
|
urlstr = QString("https://www.xertonline.com/oauth/token");
|
|
params.addQueryItem("username", service->getSetting(GC_XERTUSER, "").toString());
|
|
params.addQueryItem("password", service->getSetting(GC_XERTPASS, "").toString());
|
|
params.addQueryItem("grant_type", "password");
|
|
|
|
authheader = QString("%1:%1").arg("xert_public");
|
|
}
|
|
|
|
// all services will need us to send the temporary code received
|
|
params.addQueryItem("code", code);
|
|
|
|
#if QT_VERSION > 0x050000
|
|
data.append(params.query(QUrl::FullyEncoded));
|
|
#else
|
|
data=params.encodedQuery();
|
|
#endif
|
|
|
|
// trade-in the temporary access code retrieved by the Call-Back URL for the finale token
|
|
QUrl url = QUrl(urlstr);
|
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
|
|
|
|
// client id and secret are encoded and sent in the header for POLAR and XERT
|
|
if (site == POLAR || site == XERT) request.setRawHeader("Authorization", "Basic " + authheader.toLatin1().toBase64());
|
|
|
|
// now get the final token - but ignore errors
|
|
manager = new QNetworkAccessManager(this);
|
|
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
//connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
|
|
QNetworkReply *reply = manager->post(request, data);
|
|
|
|
QEventLoop loop;
|
|
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
loop.exec();
|
|
networkRequestFinished(reply);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// GOOGLE DRIVE gets the code in the HTML title field (different to other services)
|
|
//
|
|
void
|
|
OAuthManager::loadFinished(bool ok)
|
|
{
|
|
|
|
if (site == GOOGLE_DRIVE || site == KENTUNI) {
|
|
|
|
if (ok && url.toString().startsWith("https://accounts.google.com/o/oauth2/auth")) {
|
|
|
|
// retrieve the code from the HTML page title
|
|
QString title = view->title();
|
|
|
|
if (title.contains("code")) {
|
|
|
|
QString code = title.right(title.length()-title.indexOf("code=")-5);
|
|
QByteArray data;
|
|
#if QT_VERSION > 0x050000
|
|
QUrlQuery params;
|
|
#else
|
|
QUrl params;
|
|
#endif
|
|
QString urlstr = "https://www.googleapis.com/oauth2/v3/token?";
|
|
params.addQueryItem("client_id", GC_GOOGLE_DRIVE_CLIENT_ID);
|
|
|
|
if (site == GOOGLE_DRIVE || site == KENTUNI) {
|
|
params.addQueryItem("client_secret", GC_GOOGLE_DRIVE_CLIENT_SECRET);
|
|
}
|
|
|
|
params.addQueryItem("code", code);
|
|
params.addQueryItem("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
|
|
params.addQueryItem("grant_type", "authorization_code");
|
|
|
|
#if QT_VERSION > 0x050000
|
|
data.append(params.query(QUrl::FullyEncoded));
|
|
#else
|
|
data=params.encodedQuery();
|
|
#endif
|
|
|
|
// trade-in the temporary access code retrieved by the
|
|
// Call-Back URL for the finale token
|
|
QUrl url = QUrl(urlstr);
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
|
|
// not get the final token - ignoring errors
|
|
manager = new QNetworkAccessManager(this);
|
|
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
|
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
|
|
manager->post(request, data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if QT_VERSION < 0x050000
|
|
static QString RawJsonStringGrab(const QByteArray& payload,
|
|
const QString& needle) {
|
|
// A RegExp based JSON string parser. Not the best, but it does the job.
|
|
QString regex =
|
|
// This matches the key.
|
|
"(" + needle + "|\"" + needle + "\"|'" + needle + "')"
|
|
// Matches the separator.
|
|
"[\\s]*:[\\s]*"
|
|
// matches the value.
|
|
"(\"\\S+\"|'\\S+')";
|
|
QRegExp q(regex);
|
|
if (!q.isValid()) {
|
|
// Somehow failed to build the regex.
|
|
return "";
|
|
}
|
|
int start = q.indexIn(payload);
|
|
int pos = q.pos(2);
|
|
if (start == -1 || pos == -1) {
|
|
// Failed to find the key or the value.
|
|
return "";
|
|
}
|
|
QString ret = payload.mid(pos, q.matchedLength() + start - pos);
|
|
// Remove " or ' from the value.
|
|
ret.remove(0, 1);
|
|
ret.remove(ret.size() - 1, 1);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// STEP 3: REFRESH AND ACCESS TOKEN RECEIVED
|
|
//
|
|
// this is when we get the refresh or access tokens after a redirect has been loaded
|
|
// so pretty much at the end of the process. Each service can have slightly special
|
|
// needs and certainly needs to set the right setting anyway.
|
|
//
|
|
void
|
|
OAuthManager::networkRequestFinished(QNetworkReply *reply)
|
|
{
|
|
|
|
// we've been told to ignore responses (used by POLAR, maybe others in future)
|
|
if (ignore) return;
|
|
|
|
// we can handle SSL handshake errors, if we got here then some kind of protocol was agreed
|
|
if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
|
|
|
QByteArray payload = reply->readAll(); // JSON
|
|
QString refresh_token;
|
|
QString access_token;
|
|
double polar_userid=0;
|
|
|
|
// parse the response and extract the tokens, pretty much the same for all services
|
|
// although polar choose to also pass a user id, which is needed for future calls
|
|
#if QT_VERSION > 0x050000
|
|
QJsonParseError parseError;
|
|
QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
|
if (parseError.error == QJsonParseError::NoError) {
|
|
refresh_token = document.object()["refresh_token"].toString();
|
|
access_token = document.object()["access_token"].toString();
|
|
if (site == POLAR) polar_userid = document.object()["x_user_id"].toDouble();
|
|
}
|
|
#else
|
|
refresh_token = RawJsonStringGrab(payload, "refresh_token");
|
|
access_token = RawJsonStringGrab(payload, "access_token");
|
|
#endif
|
|
|
|
// if we failed to extract then we have a big problem
|
|
// google uses a refresh token so trap for them only
|
|
if (((site == GOOGLE_DRIVE || site == KENTUNI) && refresh_token == "") || access_token == "") {
|
|
|
|
// Something failed.
|
|
// Only Google uses both refresh and access tokens.
|
|
QString error = QString(tr("Error retrieving authoriation credentials"));
|
|
QMessageBox oautherr(QMessageBox::Critical, tr("Authorization Error"), error);
|
|
oautherr.setDetailedText(error);
|
|
oautherr.exec();
|
|
|
|
return;
|
|
}
|
|
|
|
// now set the tokens etc
|
|
if (site == DROPBOX) {
|
|
|
|
service->setSetting(GC_DROPBOX_TOKEN, access_token);
|
|
QString info = QString(tr("Dropbox authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == SPORTTRACKS) {
|
|
|
|
service->setSetting(GC_SPORTTRACKS_TOKEN, access_token);
|
|
service->setSetting(GC_SPORTTRACKS_REFRESH_TOKEN, refresh_token);
|
|
service->setSetting(GC_SPORTTRACKS_LAST_REFRESH, QDateTime::currentDateTime());
|
|
QString info = QString(tr("SportTracks authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == POLAR) {
|
|
|
|
service->setSetting(GC_POLARFLOW_TOKEN, access_token);
|
|
service->setSetting(GC_POLARFLOW_USER_ID, polar_userid);
|
|
|
|
// we now need to bind the user, this is a one time deal.
|
|
QString url = QString("%1/v3/users").arg(GC_POLARFLOW_URL);
|
|
|
|
// request using the bearer token
|
|
QNetworkRequest request(url);
|
|
request.setRawHeader("Authorization", (QString("Bearer %1").arg(access_token)).toLatin1());
|
|
request.setRawHeader("Accept", "application/json");
|
|
request.setRawHeader("Content-Type", "application/json");
|
|
|
|
// data to post
|
|
QByteArray data;
|
|
data.append(QString("{\"member-id\":\"%1\"}").arg(context->athlete->cyclist));
|
|
|
|
// the request will fallback to this method on networkRequestFinished
|
|
// but we are done, so set ignore= true to get this function to just
|
|
// return without doing anything
|
|
ignore=true;
|
|
QNetworkReply *bind = manager->post(request, data);
|
|
|
|
// blocking request
|
|
QEventLoop loop;
|
|
connect(bind, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
loop.exec();
|
|
|
|
// Bind response lists athlete details, we ignore them for now
|
|
QByteArray r = bind->readAll();
|
|
//qDebug()<<bind->errorString()<< "bind response="<<r;
|
|
|
|
QString info = QString(tr("PolarFlow authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == STRAVA) {
|
|
|
|
service->setSetting(GC_STRAVA_TOKEN, access_token);
|
|
QString info = QString(tr("Strava authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == CYCLING_ANALYTICS) {
|
|
|
|
service->setSetting(GC_CYCLINGANALYTICS_TOKEN, access_token);
|
|
QString info = QString(tr("Cycling Analytics authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == KENTUNI) {
|
|
|
|
service->setSetting(GC_UOK_GOOGLE_DRIVE_REFRESH_TOKEN, refresh_token);
|
|
service->setSetting(GC_UOK_GOOGLE_DRIVE_ACCESS_TOKEN, access_token);
|
|
service->setSetting(GC_UOK_GOOGLE_DRIVE_LAST_ACCESS_TOKEN_REFRESH, QDateTime::currentDateTime());
|
|
QString info = QString(tr("Kent University Google Drive authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == GOOGLE_DRIVE) {
|
|
|
|
service->setSetting(GC_GOOGLE_DRIVE_REFRESH_TOKEN, refresh_token);
|
|
service->setSetting(GC_GOOGLE_DRIVE_ACCESS_TOKEN, access_token);
|
|
service->setSetting(GC_GOOGLE_DRIVE_LAST_ACCESS_TOKEN_REFRESH, QDateTime::currentDateTime());
|
|
QString info = QString(tr("Google Drive authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
|
|
} else if (site == TODAYSPLAN) {
|
|
service->setSetting(GC_TODAYSPLAN_TOKEN, access_token);
|
|
QString info = QString(tr("Today's Plan authorization was successful."));
|
|
QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
information.exec();
|
|
} else if (site == XERT) {
|
|
|
|
service->setSetting(GC_XERT_TOKEN, access_token);
|
|
service->setSetting(GC_XERT_REFRESH_TOKEN, refresh_token);
|
|
|
|
// Try without Message Box
|
|
|
|
//QString info = QString(tr("Xert authorization was successful."));
|
|
//QMessageBox information(QMessageBox::Information, tr("Information"), info);
|
|
//information.exec();
|
|
|
|
service->message = "Xert authorization was successful.";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// general error getting response
|
|
QString error = QString(tr("Error retrieving access token, %1 (%2)")).arg(reply->errorString()).arg(reply->error());
|
|
QMessageBox oautherr(QMessageBox::Critical, tr("SSL Token Refresh Error"), error);
|
|
oautherr.setDetailedText(error);
|
|
oautherr.exec();
|
|
}
|
|
|
|
// job done, dialog can be closed
|
|
//accept();
|
|
}
|