/* * 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 #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&) { 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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); //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 & )), this, SLOT(onSslErrors(QNetworkReply*, const QList & ))); 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()<errorString()<< "bind response="<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(); }