1 #include <QList>
2 #include <QPair>
3 #include <QDebug>
4 #include <QTcpServer>
5 #include <QMap>
6 #include <QNetworkRequest>
7 #include <QNetworkReply>
8 #include <QNetworkAccessManager>
9 #include <QDateTime>
10 #include <QCryptographicHash>
11 #include <QTimer>
12 #include <QVariantMap>
13 
14 #if QT_VERSION >= 0x050000
15 #include <QUrlQuery>
16 #include <QJsonDocument>
17 #include <QJsonObject>
18 #else
19 #include <QScriptEngine>
20 #include <QScriptValueIterator>
21 #endif
22 
23 #include "o2.h"
24 #include "o2replyserver.h"
25 #include "o0globals.h"
26 #include "o0settingsstore.h"
27 
28 /// Parse JSON data into a QVariantMap
parseTokenResponse(const QByteArray & data)29 static QVariantMap parseTokenResponse(const QByteArray &data) {
30 #if QT_VERSION >= 0x050000
31     QJsonParseError err;
32     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
33     if (err.error != QJsonParseError::NoError) {
34         qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString();
35         return QVariantMap();
36     }
37 
38     if (!doc.isObject()) {
39         qWarning() << "parseTokenResponse: Token response is not an object";
40         return QVariantMap();
41     }
42 
43     return doc.object().toVariantMap();
44 #else
45     QScriptEngine engine;
46     QScriptValue value = engine.evaluate("(" + QString(data) + ")");
47     QScriptValueIterator it(value);
48     QVariantMap map;
49 
50     while (it.hasNext()) {
51         it.next();
52         map.insert(it.name(), it.value().toVariant());
53     }
54 
55     return map;
56 #endif
57 }
58 
59 /// Add query parameters to a query
addQueryParametersToUrl(QUrl & url,QList<QPair<QString,QString>> parameters)60 static void addQueryParametersToUrl(QUrl &url,  QList<QPair<QString, QString> > parameters) {
61 #if QT_VERSION < 0x050000
62     url.setQueryItems(parameters);
63 #else
64     QUrlQuery query(url);
65     query.setQueryItems(parameters);
66     url.setQuery(query);
67 #endif
68 }
69 
O2(QObject * parent,QNetworkAccessManager * manager,O0AbstractStore * store)70 O2::O2(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) {
71     manager_ = manager ? manager : new QNetworkAccessManager(this);
72     replyServer_ = new O2ReplyServer(this);
73     grantFlow_ = GrantFlowAuthorizationCode;
74     localhostPolicy_ = QString(O2_CALLBACK_URL);
75     qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
76     connect(replyServer_, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
77     connect(replyServer_, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool)));
78 }
79 
grantFlow()80 O2::GrantFlow O2::grantFlow() {
81     return grantFlow_;
82 }
83 
setGrantFlow(O2::GrantFlow value)84 void O2::setGrantFlow(O2::GrantFlow value) {
85     grantFlow_ = value;
86     Q_EMIT grantFlowChanged();
87 }
88 
username()89 QString O2::username() {
90     return username_;
91 }
92 
setUsername(const QString & value)93 void O2::setUsername(const QString &value) {
94     username_ = value;
95     Q_EMIT usernameChanged();
96 }
97 
password()98 QString O2::password() {
99     return password_;
100 }
101 
setPassword(const QString & value)102 void O2::setPassword(const QString &value) {
103     password_ = value;
104     Q_EMIT passwordChanged();
105 }
106 
scope()107 QString O2::scope() {
108     return scope_;
109 }
110 
setScope(const QString & value)111 void O2::setScope(const QString &value) {
112     scope_ = value;
113     Q_EMIT scopeChanged();
114 }
115 
requestUrl()116 QString O2::requestUrl() {
117     return requestUrl_.toString();
118 }
119 
setRequestUrl(const QString & value)120 void O2::setRequestUrl(const QString &value) {
121     requestUrl_ = QUrl(value);
122     Q_EMIT requestUrlChanged();
123 }
124 
extraRequestParams()125 QVariantMap O2::extraRequestParams()
126 {
127   return extraReqParams_;
128 }
129 
setExtraRequestParams(const QVariantMap & value)130 void O2::setExtraRequestParams(const QVariantMap &value)
131 {
132   extraReqParams_ = value;
133   Q_EMIT extraRequestParamsChanged();
134 }
135 
tokenUrl()136 QString O2::tokenUrl() {
137     return tokenUrl_.toString();
138 }
139 
setTokenUrl(const QString & value)140 void O2::setTokenUrl(const QString &value) {
141     tokenUrl_= QUrl(value);
142     Q_EMIT tokenUrlChanged();
143 }
144 
refreshTokenUrl()145 QString O2::refreshTokenUrl() {
146     return refreshTokenUrl_.toString();
147 }
148 
setRefreshTokenUrl(const QString & value)149 void O2::setRefreshTokenUrl(const QString &value) {
150     refreshTokenUrl_ = QUrl(value);
151     Q_EMIT refreshTokenUrlChanged();
152 }
153 
link()154 void O2::link() {
155     qDebug() << "O2::link";
156 
157     if (linked()) {
158         qDebug() << "O2::link: Linked already";
159         Q_EMIT linkingSucceeded();
160         return;
161     }
162 
163     setLinked(false);
164     setToken("");
165     setTokenSecret("");
166     setExtraTokens(QVariantMap());
167     setRefreshToken(QString());
168     setExpires(0);
169 
170     if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) {
171         // Start listening to authentication replies
172         if (!replyServer_->isListening()) {
173 	        if (replyServer_->listen(QHostAddress::Any, localPort_)) {
174 	            qDebug() << "O2::link: Reply server listening on port" << localPort();
175 	        } else {
176 	            qWarning() << "O2::link: Reply server failed to start listening on port" << localPort();
177 	            Q_EMIT linkingFailed();
178 	            return;
179 	        }
180 		}
181 
182         // Save redirect URI, as we have to reuse it when requesting the access token
183         redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort());
184 
185         // Assemble initial authentication URL
186         QList<QPair<QString, QString> > parameters;
187         parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE),
188                                     (grantFlow_ == GrantFlowAuthorizationCode)? QString(O2_OAUTH2_GRANT_TYPE_CODE): QString(O2_OAUTH2_GRANT_TYPE_TOKEN)));
189         parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_));
190         if ( !redirectUri_.isEmpty() )
191             parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_));
192         if ( !scope_.isEmpty() )
193             parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_.replace( " ", "+" )));
194         if ( !apiKey_.isEmpty() )
195             parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_));
196         foreach (QString key, extraRequestParams().keys()) {
197             parameters.append(qMakePair(key, extraRequestParams().value(key).toString()));
198         }
199         // Show authentication URL with a web browser
200         QUrl url(requestUrl_);
201         addQueryParametersToUrl(url, parameters);
202         qDebug() << "O2::link: Emit openBrowser" << url.toString();
203         Q_EMIT openBrowser(url);
204     } else if (grantFlow_ == GrantFlowResourceOwnerPasswordCredentials) {
205         QList<O0RequestParameter> parameters;
206         parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8()));
207         if ( !clientSecret_.isEmpty() )
208             parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8()));
209         parameters.append(O0RequestParameter(O2_OAUTH2_USERNAME, username_.toUtf8()));
210         parameters.append(O0RequestParameter(O2_OAUTH2_PASSWORD, password_.toUtf8()));
211         parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_GRANT_TYPE_PASSWORD));
212         parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8()));
213         if ( !apiKey_.isEmpty() )
214             parameters.append(O0RequestParameter(O2_OAUTH2_API_KEY, apiKey_.toUtf8()));
215         foreach (QString key, extraRequestParams().keys()) {
216             parameters.append(O0RequestParameter(key.toUtf8(), extraRequestParams().value(key).toByteArray()));
217         }
218         QByteArray payload = O0BaseAuth::createQueryParameters(parameters);
219 
220         qDebug() << "O2::link: Sending token request for resource owner flow";
221         QUrl url(tokenUrl_);
222         QNetworkRequest tokenRequest(url);
223         tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
224         QNetworkReply *tokenReply = manager_->post(tokenRequest, payload);
225 
226         connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
227         connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
228     }
229 }
230 
unlink()231 void O2::unlink() {
232     qDebug() << "O2::unlink";
233     setLinked(false);
234     setToken(QString());
235     setRefreshToken(QString());
236     setExpires(0);
237     setExtraTokens(QVariantMap());
238     Q_EMIT linkingSucceeded();
239 }
240 
onVerificationReceived(const QMap<QString,QString> response)241 void O2::onVerificationReceived(const QMap<QString, QString> response) {
242     qDebug() << "O2::onVerificationReceived:" << response;
243     qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()";
244     Q_EMIT closeBrowser();
245 
246     if (response.contains("error")) {
247         qWarning() << "O2::onVerificationReceived: Verification failed:" << response;
248         Q_EMIT linkingFailed();
249         return;
250     }
251 
252     if (grantFlow_ == GrantFlowAuthorizationCode) {
253         // Save access code
254         setCode(response.value(QString(O2_OAUTH2_GRANT_TYPE_CODE)));
255 
256         // Exchange access code for access/refresh tokens
257         QString query;
258         if(!apiKey_.isEmpty())
259             query = QString("?" + QString(O2_OAUTH2_API_KEY) + "=" + apiKey_);
260         QNetworkRequest tokenRequest(QUrl(tokenUrl_.toString() + query));
261         tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
262         tokenRequest.setRawHeader("Accept", O2_MIME_TYPE_JSON);
263         QMap<QString, QString> parameters;
264         parameters.insert(O2_OAUTH2_GRANT_TYPE_CODE, code());
265         parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
266         parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
267         parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_);
268         parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE);
269         QByteArray data = buildRequestBody(parameters);
270 
271         qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(QString(data));
272 
273         QNetworkReply *tokenReply = manager_->post(tokenRequest, data);
274         timedReplies_.add(tokenReply);
275         connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
276         connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
277     } else if (grantFlow_ == GrantFlowImplicit) {
278       // Check for mandatory tokens
279       if (response.contains(O2_OAUTH2_ACCESS_TOKEN)) {
280           qDebug() << "O2::onVerificationReceived: Access token returned for implicit flow";
281           setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
282           if (response.contains(O2_OAUTH2_EXPIRES_IN)) {
283             bool ok = false;
284             int expiresIn = response.value(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
285             if (ok) {
286                 qDebug() << "O2::onVerificationReceived: Token expires in" << expiresIn << "seconds";
287                 setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn);
288             }
289           }
290           setLinked(true);
291           Q_EMIT linkingSucceeded();
292       } else {
293           qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit flow";
294           Q_EMIT linkingFailed();
295       }
296     } else {
297         setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
298         setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN));
299     }
300 }
301 
code()302 QString O2::code() {
303     QString key = QString(O2_KEY_CODE).arg(clientId_);
304     return store_->value(key);
305 }
306 
setCode(const QString & c)307 void O2::setCode(const QString &c) {
308     QString key = QString(O2_KEY_CODE).arg(clientId_);
309     store_->setValue(key, c);
310 }
311 
onTokenReplyFinished()312 void O2::onTokenReplyFinished() {
313     qDebug() << "O2::onTokenReplyFinished";
314     QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
315     if (!tokenReply)
316     {
317       qDebug() << "O2::onTokenReplyFinished: reply is null";
318       return;
319     }
320     if (tokenReply->error() == QNetworkReply::NoError) {
321         QByteArray replyData = tokenReply->readAll();
322 
323         // Dump replyData
324         // SENSITIVE DATA in RelWithDebInfo or Debug builds
325         //qDebug() << "O2::onTokenReplyFinished: replyData\n";
326         //qDebug() << QString( replyData );
327 
328         QVariantMap tokens = parseTokenResponse(replyData);
329 
330         // Dump tokens
331         qDebug() << "O2::onTokenReplyFinished: Tokens returned:\n";
332         foreach (QString key, tokens.keys()) {
333             // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
334             qDebug() << key << ": "<< tokens.value( key ).toString().left( 3 ) << "...";
335         }
336 
337         // Check for mandatory tokens
338         if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) {
339             qDebug() << "O2::onTokenReplyFinished: Access token returned";
340             setToken(tokens.take(O2_OAUTH2_ACCESS_TOKEN).toString());
341             bool ok = false;
342             int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
343             if (ok) {
344                 qDebug() << "O2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds";
345                 setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn);
346             }
347             setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString());
348             setExtraTokens(tokens);
349             timedReplies_.remove(tokenReply);
350             setLinked(true);
351             Q_EMIT linkingSucceeded();
352         } else {
353             qWarning() << "O2::onTokenReplyFinished: Access token missing from response";
354             Q_EMIT linkingFailed();
355         }
356     }
357     tokenReply->deleteLater();
358 }
359 
onTokenReplyError(QNetworkReply::NetworkError error)360 void O2::onTokenReplyError(QNetworkReply::NetworkError error) {
361     QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
362     qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString();
363     qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll();
364     setToken(QString());
365     setRefreshToken(QString());
366     timedReplies_.remove(tokenReply);
367     Q_EMIT linkingFailed();
368 }
369 
buildRequestBody(const QMap<QString,QString> & parameters)370 QByteArray O2::buildRequestBody(const QMap<QString, QString> &parameters) {
371     QByteArray body;
372     bool first = true;
373     foreach (QString key, parameters.keys()) {
374         if (first) {
375             first = false;
376         } else {
377             body.append("&");
378         }
379         QString value = parameters.value(key);
380         body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value));
381     }
382     return body;
383 }
384 
expires()385 int O2::expires() {
386     QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
387     return store_->value(key).toInt();
388 }
389 
setExpires(int v)390 void O2::setExpires(int v) {
391     QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
392     store_->setValue(key, QString::number(v));
393 }
394 
refreshToken()395 QString O2::refreshToken() {
396     QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
397     return store_->value(key);
398 }
399 
setRefreshToken(const QString & v)400 void O2::setRefreshToken(const QString &v) {
401     qDebug() << "O2::setRefreshToken" << v.left(4) << "...";
402     QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
403     store_->setValue(key, v);
404 }
405 
refresh()406 void O2::refresh() {
407     qDebug() << "O2::refresh: Token: ..." << refreshToken().right(7);
408 
409     if (refreshToken().isEmpty()) {
410         qWarning() << "O2::refresh: No refresh token";
411         onRefreshError(QNetworkReply::AuthenticationRequiredError);
412         return;
413     }
414     if (refreshTokenUrl_.isEmpty()) {
415         qWarning() << "O2::refresh: Refresh token URL not set";
416         onRefreshError(QNetworkReply::AuthenticationRequiredError);
417         return;
418     }
419 
420     QNetworkRequest refreshRequest(refreshTokenUrl_);
421     refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
422     QMap<QString, QString> parameters;
423     parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
424     parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
425     parameters.insert(O2_OAUTH2_REFRESH_TOKEN, refreshToken());
426     parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_REFRESH_TOKEN);
427 
428     QByteArray data = buildRequestBody(parameters);
429     QNetworkReply *refreshReply = manager_->post(refreshRequest, data);
430     timedReplies_.add(refreshReply);
431     connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection);
432     connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
433 }
434 
onRefreshFinished()435 void O2::onRefreshFinished() {
436     QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
437     qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString();
438     if (refreshReply->error() == QNetworkReply::NoError) {
439         QByteArray reply = refreshReply->readAll();
440         QVariantMap tokens = parseTokenResponse(reply);
441         setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString());
442         setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt());
443         setRefreshToken(tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString());
444         timedReplies_.remove(refreshReply);
445         setLinked(true);
446         Q_EMIT linkingSucceeded();
447         Q_EMIT refreshFinished(QNetworkReply::NoError);
448         qDebug() << " New token expires in" << expires() << "seconds";
449     }
450     refreshReply->deleteLater();
451 }
452 
onRefreshError(QNetworkReply::NetworkError error)453 void O2::onRefreshError(QNetworkReply::NetworkError error) {
454     QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
455     qWarning() << "O2::onRefreshError: " << error;
456     unlink();
457     timedReplies_.remove(refreshReply);
458     Q_EMIT refreshFinished(error);
459 }
460 
serverHasClosed(bool paramsfound)461 void O2::serverHasClosed(bool paramsfound)
462 {
463     if ( !paramsfound ) {
464         // server has probably timed out after receiving first response
465         Q_EMIT linkingFailed();
466     }
467 }
468 
localhostPolicy() const469 QString O2::localhostPolicy() const {
470     return localhostPolicy_;
471 }
472 
setLocalhostPolicy(const QString & value)473 void O2::setLocalhostPolicy(const QString &value) {
474     localhostPolicy_ = value;
475 }
476 
apiKey()477 QString O2::apiKey() {
478     return apiKey_;
479 }
480 
setApiKey(const QString & value)481 void O2::setApiKey(const QString &value) {
482     apiKey_ = value;
483 }
484 
replyContent()485 QByteArray O2::replyContent() {
486     return replyServer_->replyContent();
487 }
488 
setReplyContent(const QByteArray & value)489 void O2::setReplyContent(const QByteArray &value) {
490     replyServer_->setReplyContent(value);
491 }
492 
ignoreSslErrors()493 bool O2::ignoreSslErrors() {
494     return timedReplies_.ignoreSslErrors();
495 }
496 
setIgnoreSslErrors(bool ignoreSslErrors)497 void O2::setIgnoreSslErrors(bool ignoreSslErrors) {
498     timedReplies_.setIgnoreSslErrors(ignoreSslErrors);
499 }
500