diff --git a/kqoauth/kqoauthmanager.cpp b/kqoauth/kqoauthmanager.cpp index 0c3e24667..5b6d3cec3 100644 --- a/kqoauth/kqoauthmanager.cpp +++ b/kqoauth/kqoauthmanager.cpp @@ -228,6 +228,7 @@ void KQOAuthManager::executeRequest(KQOAuthRequest *request) { urlWithParams.setQuery(query); #endif networkRequest.setUrl(urlWithParams); + qDebug() << "urlWithParams:" << urlWithParams; // Submit the request including the params. QNetworkReply *reply = d->networkManager->get(networkRequest); diff --git a/src/Cloud/OAuthDialog.cpp b/src/Cloud/OAuthDialog.cpp index b50e03e40..16396f761 100644 --- a/src/Cloud/OAuthDialog.cpp +++ b/src/Cloud/OAuthDialog.cpp @@ -150,10 +150,46 @@ OAuthDialog::OAuthDialog(Context *context, OAuthSite site, QString baseURL, QStr if (baseURL=="") baseURL="https://whats.todaysplan.com.au"; urlstr = QString("%1/authorize/").arg(baseURL); urlstr.append(GC_TODAYSPLAN_CLIENT_ID); + } else if (site == WITHINGS) { + +#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 } // different process to get the token for STRAVA, CYCLINGANALYTICS vs. - // TWITTER + // TWITTER or WITHINGS if (site == DROPBOX || site == STRAVA || site == CYCLING_ANALYTICS || site == GOOGLE_CALENDAR || site == GOOGLE_DRIVE || site == TODAYSPLAN) { url = QUrl(urlstr); @@ -167,22 +203,35 @@ OAuthDialog::OAuthDialog(Context *context, OAuthSite site, QString baseURL, QStr } #ifdef GC_HAVE_KQOAUTH -// ****************** Twitter OAUTH ****************************************************** +// ****************** OAUTH V1 (TWITTER, WITHINGS) ************************************************ void OAuthDialog::onTemporaryTokenReceived(QString, QString) { - QUrl userAuthURL("https://api.twitter.com/oauth/authorize"); + //qDebug() << "onTemporaryTokenReceived"; + QUrl userAuthURL; + + if (site == TWITTER) { + userAuthURL = "https://api.twitter.com/oauth/authorize"; + } else if (site == WITHINGS) { + userAuthURL = "https://oauth.withings.com/account/authorize"; + } if( oauthManager->lastError() == KQOAuthManager::NoError) { oauthManager->getUserAuthorization(userAuthURL); - } + } else + qDebug() << "error" << oauthManager->lastError(); } void OAuthDialog::onAuthorizationReceived(QString, QString) { - // qDebug() << "Authorization token received: " << token << verifier; + //qDebug() << "Authorization token received: " << token << verifier; + + if (site == TWITTER) { + oauthManager->getUserAccessTokens(QUrl("https://api.twitter.com/oauth/access_token")); + } else if (site == WITHINGS) { + oauthManager->getUserAccessTokens(QUrl("https://oauth.withings.com/account/access_token")); + } - oauthManager->getUserAccessTokens(QUrl("https://api.twitter.com/oauth/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); @@ -192,23 +241,34 @@ void OAuthDialog::onAuthorizationReceived(QString, QString) { } void OAuthDialog::onAccessTokenReceived(QString token, QString tokenSecret) { - // qDebug() << "Access token received: " << token << tokenSecret; + //qDebug() << "Access token received: " << token << tokenSecret; + + QString info; + if (site == TWITTER) { + appsettings->setCValue(context->athlete->cyclist, GC_TWITTER_TOKEN, token); + appsettings->setCValue(context->athlete->cyclist, GC_TWITTER_SECRET, tokenSecret); + + info = QString(tr("Twitter authorization was successful.")); + } else if (site == WITHINGS) { + appsettings->setCValue(context->athlete->cyclist, GC_WITHINGS_TOKEN, token); + appsettings->setCValue(context->athlete->cyclist, GC_WITHINGS_SECRET, tokenSecret); + + info = QString(tr("Withings authorization was successful.")); + } - appsettings->setCValue(context->athlete->cyclist, GC_TWITTER_TOKEN, token); - appsettings->setCValue(context->athlete->cyclist, GC_TWITTER_SECRET, tokenSecret); - QString info = QString(tr("Twitter authorization was successful.")); QMessageBox information(QMessageBox::Information, tr("Information"), info); information.exec(); accept(); } void OAuthDialog::onAuthorizedRequestDone() { - // request sent to Twitter - do nothing + // request sent - do nothing } void OAuthDialog::onRequestReady(QByteArray response) { - // qDebug() << "Response received: " << response; + //qDebug() << "Response received: " << response; + QString r = response; if (r.contains("\"errors\"", Qt::CaseInsensitive)) { @@ -216,11 +276,30 @@ void OAuthDialog::onRequestReady(QByteArray response) { 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) { + appsettings->setCValue(context->athlete->cyclist, GC_WIUSER, userid); + } + } } } void OAuthDialog::onAuthorizationPageRequested(QUrl url) { + //qDebug() << "AuthorizationPageRequested: " << url; + // open Authorization page in view view->setUrl(url); diff --git a/src/Cloud/WithingsDownload.cpp b/src/Cloud/WithingsDownload.cpp index 819839d78..5a657e92c 100644 --- a/src/Cloud/WithingsDownload.cpp +++ b/src/Cloud/WithingsDownload.cpp @@ -22,33 +22,122 @@ #include "RideCache.h" #include +#ifdef GC_HAVE_KQOAUTH +#include +#include +#endif + WithingsDownload::WithingsDownload(Context *context) : context(context) { nam = new QNetworkAccessManager(this); connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); parser = new WithingsParser; + + #ifdef GC_HAVE_KQOAUTH + oauthRequest = new KQOAuthRequest(); + oauthManager = new KQOAuthManager(); + + connect(oauthManager, SIGNAL(requestReady(QByteArray)), + this, SLOT(onRequestReady(QByteArray))); + connect(oauthManager, SIGNAL(authorizedRequestDone()), + this, SLOT(onAuthorizedRequestDone())); + #endif } bool WithingsDownload::download() { - // account for trailing slash, remove it if it is there (it was the default in preferences) - QString server = appsettings->cvalue(context->athlete->cyclist, GC_WIURL, "http://wbsapi.withings.net").toString(); - if (server.endsWith("/")) server=server.mid(0, server.length()-1); + // New API (OAuth) + QString strToken = ""; + QString strSecret = ""; + #ifdef GC_HAVE_KQOAUTH + strToken =appsettings->cvalue(context->athlete->cyclist, GC_WITHINGS_TOKEN).toString(); + strSecret= appsettings->cvalue(context->athlete->cyclist, GC_WITHINGS_SECRET).toString(); + #endif - QString request = QString("%1/measure?action=getmeas&userid=%2&publickey=%3") - .arg(server) - .arg(appsettings->cvalue(context->athlete->cyclist, GC_WIUSER, "").toString()) - .arg(appsettings->cvalue(context->athlete->cyclist, GC_WIKEY, "").toString()); + QString strOldKey = appsettings->cvalue(context->athlete->cyclist, GC_WIKEY).toString(); - QNetworkReply *reply = nam->get(QNetworkRequest(QUrl(request))); - - if (reply->error() != QNetworkReply::NoError) { - QMessageBox::warning(context->mainWindow, tr("Withings Data Download"), reply->errorString()); + if((strToken.isEmpty() || strSecret.isEmpty() || + strToken == "" || strToken == "0" || + strSecret == "" || strSecret == "0" ) && + (strOldKey.isEmpty() || strOldKey == "" || strOldKey == "0" )) + { + #ifdef Q_OS_MACX + #define GC_PREF tr("Golden Cheetah->Preferences") + #else + #define GC_PREF tr("Tools->Options") + #endif + QString advise = QString(tr("Error fetching OAuth credentials. Please make sure to complete the Withings authorization procedure found under %1.")).arg(GC_PREF); + QMessageBox oautherr(QMessageBox::Critical, tr("OAuth Error"), advise); + oautherr.exec(); return false; } - return true; + + if(!strToken.isEmpty() &&! strSecret.isEmpty() && + strToken != "" && strToken != "0" && + strSecret != "" && strSecret != "0" ) { + #ifdef GC_HAVE_KQOAUTH + oauthRequest->initRequest(KQOAuthRequest::AuthorizedRequest, QUrl("http://wbsapi.withings.net/measure")); + oauthRequest->setHttpMethod(KQOAuthRequest::GET); + //oauthRequest->setEnableDebugOutput(true); + + oauthRequest->setConsumerKey(GC_WITHINGS_CONSUMER_KEY); + oauthRequest->setConsumerSecretKey(GC_WITHINGS_CONSUMER_SECRET); + + // set the user token and secret + oauthRequest->setToken(strToken); + oauthRequest->setTokenSecret(strSecret); + + KQOAuthParameters params; + params.insert("action", "getmeas"); + params.insert("userid", appsettings->cvalue(context->athlete->cyclist, GC_WIUSER, "").toString()); + + // Hack... + // Why should we add params manually (GET) ???? + QList requestParameters = oauthRequest->requestParameters(); + + for (int i=0; i 0x050000 + QUrlQuery _params; + _params.setQuery(requestParameters.at(i)); +#else + QUrl _params; + _params.setEncodedQuery(requestParameters.at(i)); +#endif + QString value = _params.queryItems().at(0).second; + value = value.replace("\"", ""); + params.insert(_params.queryItems().at(0).first, value); + + } + + oauthRequest->setAdditionalParameters(params); + + oauthManager->executeRequest(oauthRequest); + + #endif + } else { + + // account for trailing slash, remove it if it is there (it was the default in preferences) + QString server = appsettings->cvalue(context->athlete->cyclist, GC_WIURL, "http://wbsapi.withings.net").toString(); + if (server.endsWith("/")) server=server.mid(0, server.length()-1); + + QString request = QString("%1/measure?action=getmeas&userid=%2&publickey=%3") + .arg(server) + .arg(appsettings->cvalue(context->athlete->cyclist, GC_WIUSER, "").toString()) + .arg(appsettings->cvalue(context->athlete->cyclist, GC_WIKEY, "").toString()); + + + QNetworkReply *reply = nam->get(QNetworkRequest(QUrl(request))); + + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::warning(context->mainWindow, tr("Withings Data Download"), reply->errorString()); + return false; + } + return true; + } + return false; } void @@ -59,7 +148,12 @@ WithingsDownload::downloadFinished(QNetworkReply *reply) QMessageBox::warning(context->mainWindow, tr("Network Problem"), tr("No Withings Data downloaded")); return; } - QString text = reply->readAll(); + parse(reply->readAll()); +} + +void +WithingsDownload::parse(QString text) +{ QStringList errors; // parse it @@ -105,3 +199,27 @@ WithingsDownload::downloadFinished(QNetworkReply *reply) return; } +void +WithingsDownload::onAuthorizedRequestDone() { + // qDebug() << "Request sent to Withings!"; +} + +void +WithingsDownload::onRequestReady(QByteArray response) { + //qDebug() << "Response from the Withings's service: " << response; + + QString r = response; + + if (r.contains("\"status\":0", Qt::CaseInsensitive)) + { + parse(response); + } else { + QMessageBox oautherr(QMessageBox::Critical, tr("Error"), + tr("There was an error during fetching. Please check the error description.")); + oautherr.setDetailedText(r); // probably blank + oautherr.exec(); + } + + + +} diff --git a/src/Cloud/WithingsDownload.h b/src/Cloud/WithingsDownload.h index 9929d8425..ae09dce48 100644 --- a/src/Cloud/WithingsDownload.h +++ b/src/Cloud/WithingsDownload.h @@ -27,6 +27,10 @@ #include "Settings.h" #include "WithingsParser.h" +#ifdef GC_HAVE_KQOAUTH +#include +#endif + class WithingsDownload : public QObject { Q_OBJECT @@ -47,5 +51,18 @@ private: int allMeasures; int newMeasures; + + #ifdef GC_HAVE_KQOAUTH + KQOAuthManager *oauthManager; + KQOAuthRequest *oauthRequest; + #endif + + void parse(QString text); + +private slots: + #ifdef GC_HAVE_KQOAUTH + void onRequestReady(QByteArray); + void onAuthorizedRequestDone(); + #endif }; #endif diff --git a/src/Core/Secrets.h b/src/Core/Secrets.h index 6260b85cb..9d1491d12 100644 --- a/src/Core/Secrets.h +++ b/src/Core/Secrets.h @@ -54,10 +54,13 @@ #define GC_GOOGLE_DRIVE_API_KEY "__GC_GOOGLE_DRIVE_API_KEY__" #endif -// used by OAuthDialog.cpp (but currently public in Settings.h (!!) -//#ifndef GC_TWITTER_CONSUMER_SECRET -//#define GC_TWITTER_CONSUMER_SECRET "__GC_TWITTER_CONSUMER_SECRET__" -//#endif +#ifndef GC_TWITTER_CONSUMER_SECRET +#define GC_TWITTER_CONSUMER_SECRET "__GC_TWITTER_CONSUMER_SECRET__" +#endif + +#ifndef GC_WITHINGS_CONSUMER_SECRET +#define GC_WITHINGS_CONSUMER_SECRET "__GC_WITHINGS_CONSUMER_SECRET__" +#endif #ifndef GC_DROPBOX_CLIENT_SECRET #define GC_DROPBOX_CLIENT_SECRET "__GC_DROPBOX_CLIENT_SECRET__" diff --git a/src/Core/Settings.h b/src/Core/Settings.h index d0f4ca14f..c0a988edc 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -37,7 +37,6 @@ //Twitter oauth keys / see also Athlete parameter #define GC_TWITTER_CONSUMER_KEY "qbbmhDt8bG8ZBcT3r9nYw" //< consumer key -#define GC_TWITTER_CONSUMER_SECRET "IWXu2G6mQC5xvhM8V0ohA0mPTUOqAFutiuKIva3LQg" //Google Calendar-CALDAV oauthkeys / see also Athlete parameter #define GC_GOOGLE_CALENDAR_CLIENT_ID "426009671216-c588t1u6hafep30tfs7g0g1nuo72s8ko.apps.googleusercontent.com" @@ -48,6 +47,9 @@ //Cycling Analytics / see also Athlete parameter #define GC_CYCLINGANALYTICS_CLIENT_ID "1504958" // app id +//Withings oauth keys +#define GC_WITHINGS_CONSUMER_KEY "292875b6883b87e27cefd2555d1cb872b2282f02fee25b4906871314934" //< consumer key + // Dropbox id #ifndef GC_DROPBOX_CLIENT_ID #define GC_DROPBOX_CLIENT_ID "753fbblhri06ah3" @@ -294,8 +296,12 @@ #define GC_GOOGLE_DRIVE_FOLDER "google-drive/folder" #define GC_GOOGLE_DRIVE_FOLDER_ID "google-drive/folder_id" +//Twitter #define GC_TWITTER_TOKEN "twitter_token" #define GC_TWITTER_SECRET "twitter_secret" +//Withings +#define GC_WITHINGS_TOKEN "withings_token" +#define GC_WITHINGS_SECRET "withings_secret" //Google Calendar-CALDAV oauthkeys #define GC_GOOGLE_CALENDAR_REFRESH_TOKEN "google_cal_refresh_token" //Strava diff --git a/src/Gui/Pages.cpp b/src/Gui/Pages.cpp index 1e4d29988..b8aec1e48 100644 --- a/src/Gui/Pages.cpp +++ b/src/Gui/Pages.cpp @@ -705,34 +705,35 @@ CredentialsPage::CredentialsPage(QWidget *parent, Context *context) : QScrollAre grid->addWidget(rideWithGPSPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// - // Withings Wifi Scales + // Withings + ////////////////////////////////////////////////// - QLabel *wip = new QLabel(tr("Withings Wifi Scales")); - wip->setFont(current); +#ifdef GC_HAVE_KQOAUTH + QLabel *withingsLabel = new QLabel(tr("Withings")); + withingsLabel->setFont(current); - QLabel *wiurlLabel = new QLabel(tr("Website")); - QLabel *wiuserLabel = new QLabel(tr("User Id")); - QLabel *wipassLabel = new QLabel(tr("Public Key")); + QLabel *wipauthLabel = new QLabel(tr("Authorise")); - wiURL = new QLineEdit(this); - wiURL->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIURL, "http://wbsapi.withings.net/").toString()); + withingsAuthorise = new QPushButton(tr("Authorise"), this); + withingsAuthorised = new QPushButton(this); + withingsAuthorised->setContentsMargins(0,0,0,0); + withingsAuthorised->setIcon(passwords.scaled(16,16)); + withingsAuthorised->setIconSize(QSize(16,16)); + withingsAuthorised->setFixedHeight(16); + withingsAuthorised->setFixedWidth(16); - wiUser = new QLineEdit(this); - wiUser->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIUSER, "").toString()); + grid->addWidget(withingsLabel, ++row, 0); - wiPass = new QLineEdit(this); - wiPass->setText(appsettings->cvalue(context->athlete->cyclist, GC_WIKEY, "").toString()); + grid->addWidget(wipauthLabel, ++row, 0); + grid->addWidget(withingsAuthorise, row, 1, Qt::AlignLeft | Qt::AlignVCenter); + if (appsettings->cvalue(context->athlete->cyclist, GC_WITHINGS_TOKEN, "")!="") + grid->addWidget(withingsAuthorised, row, 1, Qt::AlignLeft | Qt::AlignVCenter); + else + withingsAuthorised->hide(); // if no token no show - grid->addWidget(wip, ++row, 0); + connect(withingsAuthorise, SIGNAL(clicked()), this, SLOT(authoriseWithings())); +#endif - grid->addWidget(wiurlLabel, ++row, 0); - grid->addWidget(wiURL, row, 1, 0); - - grid->addWidget(wiuserLabel, ++row, 0); - grid->addWidget(wiUser, row, 1, Qt::AlignLeft | Qt::AlignVCenter); - - grid->addWidget(wipassLabel, ++row, 0); - grid->addWidget(wiPass, row, 1, Qt::AlignLeft | Qt::AlignVCenter); ////////////////////////////////////////////////// // Web Calendar @@ -1041,6 +1042,17 @@ void CredentialsPage::authoriseTwitter() oauthDialog->exec(); } } + +void CredentialsPage::authoriseWithings() +{ + OAuthDialog *oauthDialog = new OAuthDialog(context, OAuthDialog::WITHINGS); + if (oauthDialog->sslLibMissing()) { + delete oauthDialog; + } else { + oauthDialog->setWindowModality(Qt::ApplicationModal); + oauthDialog->exec(); + } +} #endif #if QT_VERSION >= 0x050000 // only in QT5 or higher @@ -1154,9 +1166,6 @@ CredentialsPage::saveClicked() appsettings->setCValue(context->athlete->cyclist, GC_SPORTPLUSHEALTHPASS, sphPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_SELUSER, selUser->text()); appsettings->setCValue(context->athlete->cyclist, GC_SELPASS, selPass->text()); - appsettings->setCValue(context->athlete->cyclist, GC_WIURL, wiURL->text()); - appsettings->setCValue(context->athlete->cyclist, GC_WIUSER, wiUser->text()); - appsettings->setCValue(context->athlete->cyclist, GC_WIKEY, wiPass->text()); appsettings->setCValue(context->athlete->cyclist, GC_WEBCAL_URL, webcalURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_WEBCAL_URL, webcalURL->text()); appsettings->setCValue(context->athlete->cyclist, GC_TODAYSPLAN_URL, tdpURL->text()); diff --git a/src/Gui/Pages.h b/src/Gui/Pages.h index 742ed9739..cbeeb30fa 100644 --- a/src/Gui/Pages.h +++ b/src/Gui/Pages.h @@ -227,6 +227,7 @@ class CredentialsPage : public QScrollArea public slots: #ifdef GC_HAVE_KQOAUTH void authoriseTwitter(); + void authoriseWithings(); #endif #if QT_VERSION >= 0x050000 void authoriseDropbox(); @@ -253,6 +254,7 @@ class CredentialsPage : public QScrollArea #ifdef GC_HAVE_KQOAUTH QPushButton *twitterAuthorise; + QPushButton *withingsAuthorise; #endif #if QT_VERSION >= 0x050000 QPushButton *dropboxAuthorise; @@ -265,7 +267,7 @@ class CredentialsPage : public QScrollArea #endif QComboBox *dvCALDAVType; - QPushButton *stravaAuthorise, *stravaAuthorised, *twitterAuthorised; + QPushButton *stravaAuthorise, *stravaAuthorised, *twitterAuthorised, *withingsAuthorised; QPushButton *tdpAuthorise, *tdpAuthorised; QPushButton *networkFileStoreFolderBrowse; QLineEdit *networkFileStoreFolder; diff --git a/src/gcconfig.pri.in b/src/gcconfig.pri.in index 29cd2234d..71c54f105 100644 --- a/src/gcconfig.pri.in +++ b/src/gcconfig.pri.in @@ -277,6 +277,10 @@ macx { # if you have your own MAPQUEST KEY #DEFINES += GC_MAPQUESTAPI_KEY=\\\"xxxxxxxxxxxxxxxxxxxxxx\\\" +# USING THE WITHINGS API (http://oauth.withings.com/api) +#DEFINES += GC_WITHINGS_CLIENT_ID=\\\"xxxxxxxxxxxxxxx\\\" +#DEFINES += GC_WITHINGS_CLIENT_SECRET=\\\"xxxxxxxxxxxxxxx\\\" + # What video playback do you want? DEFINES += GC_VIDEO_NONE # dont add any video playback support #DEFINES += GC_VIDEO_QUICKTIME # mac only and the default