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> ¶meters) {
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