qoauth2authorizationcodeflow.cpp 13.7 KB
Newer Older
Jesus Fernandez's avatar
Jesus Fernandez committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QT_NO_HTTP

#include <qoauth2authorizationcodeflow.h>
#include <private/qoauth2authorizationcodeflow_p.h>

#include <qmap.h>
#include <qurl.h>
#include <qvariant.h>
#include <qurlquery.h>
#include <qjsonobject.h>
#include <qjsondocument.h>
Jesus Fernandez's avatar
Jesus Fernandez committed
51
#include <qauthenticator.h>
Jesus Fernandez's avatar
Jesus Fernandez committed
52 53 54 55 56 57
#include <qoauthhttpserverreplyhandler.h>

#include <functional>

QT_BEGIN_NAMESPACE

Jesus Fernandez's avatar
Jesus Fernandez committed
58
QOAuth2AuthorizationCodeFlowPrivate::QOAuth2AuthorizationCodeFlowPrivate(
Jesus Fernandez's avatar
Jesus Fernandez committed
59
        QNetworkAccessManager *manager) : QAbstractOAuth2Private(manager)
Jesus Fernandez's avatar
Jesus Fernandez committed
60 61
{}

Jesus Fernandez's avatar
Jesus Fernandez committed
62
void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &data)
Jesus Fernandez's avatar
Jesus Fernandez committed
63 64
{
    Q_Q(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
65
    typedef QAbstractOAuth2Private::OAuth2KeyString Key;
Jesus Fernandez's avatar
Jesus Fernandez committed
66 67 68 69 70 71

    if (status != QAbstractOAuth::Status::NotAuthenticated) {
        qWarning("QOAuth2AuthorizationCodeFlow: Unexpected call");
        return;
    }

Jesus Fernandez's avatar
Jesus Fernandez committed
72 73 74 75 76
    Q_ASSERT(!state.isEmpty());

    const QString error = data.value(Key::error).toString();
    const QString code = data.value(Key::code).toString();
    const QString receivedState = data.value(Key::state).toString();
Jesus Fernandez's avatar
Jesus Fernandez committed
77
    if (error.size()) {
Jesus Fernandez's avatar
Jesus Fernandez committed
78 79 80 81
        const QString uri = data.value(Key::errorUri).toString();
        const QString description = data.value(Key::errorDescription).toString();
        qCritical("QOAuth2AuthorizationCodeFlow: AuthenticationError: %s(%s): %s",
                  qPrintable(error), qPrintable(uri), qPrintable(description));
Jesus Fernandez's avatar
Jesus Fernandez committed
82 83 84 85
        return;
    } else if (code.isEmpty()) {
        qCritical("QOAuth2AuthorizationCodeFlow: AuthenticationError: Code not received");
        return;
Jesus Fernandez's avatar
Jesus Fernandez committed
86 87 88 89 90 91
    } else if (receivedState.isEmpty()) {
        qCritical("QOAuth2AuthorizationCodeFlow: State not received");
        return;
    } else if (state != receivedState) {
        qCritical("QOAuth2AuthorizationCodeFlow: State mismatch");
        return;
Jesus Fernandez's avatar
Jesus Fernandez committed
92 93
    }

Jesus Fernandez's avatar
Jesus Fernandez committed
94
    setStatus(QAbstractOAuth::Status::TemporaryTokenReceived);
Jesus Fernandez's avatar
Jesus Fernandez committed
95 96

    QVariantMap copy(data);
Jesus Fernandez's avatar
Jesus Fernandez committed
97
    copy.remove(Key::code);
Jesus Fernandez's avatar
Jesus Fernandez committed
98
    extraTokens = copy;
Jesus Fernandez's avatar
Jesus Fernandez committed
99
    q->requestAccessToken(code);
Jesus Fernandez's avatar
Jesus Fernandez committed
100 101
}

Jesus Fernandez's avatar
Jesus Fernandez committed
102
void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(QNetworkReply *reply)
Jesus Fernandez's avatar
Jesus Fernandez committed
103 104
{
    Q_Q(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
105
    typedef QAbstractOAuth2Private::OAuth2KeyString Key;
Jesus Fernandez's avatar
Jesus Fernandez committed
106

Jesus Fernandez's avatar
Jesus Fernandez committed
107
    if (reply != currentReply)
Jesus Fernandez's avatar
Jesus Fernandez committed
108
        return;
Jesus Fernandez's avatar
Jesus Fernandez committed
109 110 111 112 113
    if (reply->error() == QNetworkReply::UnknownNetworkError) {
        qWarning("QOAuth2AuthorizationCodeFlow: %s", qPrintable(reply->errorString()));
        setStatus(QAbstractOAuth::Status::NotAuthenticated);
        return;
    }
Jesus Fernandez's avatar
Jesus Fernandez committed
114 115 116 117 118 119 120 121 122
    const QByteArray json = reply->readAll();
    Q_EMIT q->replyDataReceived(json);

    if (json.isEmpty()) {
        qCritical("QOAuth2AuthorizationCodeFlowPrivate::accessTokenRequestFinished: Empty answer");
        return;
    }

    qDebug("QOAuth2AuthorizationCodeFlowPrivate::accessTokenRequestFinished: %s", qPrintable(json));
Jesus Fernandez's avatar
Jesus Fernandez committed
123 124 125 126
    const auto document = QJsonDocument::fromJson(json);
    Q_ASSERT(document.isObject());
    const auto object = document.object();

Jesus Fernandez's avatar
Jesus Fernandez committed
127 128 129 130 131 132
    if (object.contains(Key::error)) {
        const QString error = object.value(Key::error).toString();
        qCritical("QOAuth2AuthorizationCodeFlow Error: %s", qPrintable(error));
        return;
    }

Jesus Fernandez's avatar
Jesus Fernandez committed
133
    const QString accessToken = object.value(Key::accessToken).toString();
Jesus Fernandez's avatar
Jesus Fernandez committed
134
    tokenType = object.value(Key::tokenType).toString();
Jesus Fernandez's avatar
Jesus Fernandez committed
135
    const int expiresIn = object.value(Key::expiresIn).toInt(-1);
Jesus Fernandez's avatar
Jesus Fernandez committed
136 137
    refreshToken = object.value(Key::refreshToken).toString();
    scope = object.value(Key::scope).toString();
Jesus Fernandez's avatar
Jesus Fernandez committed
138 139 140 141
    if (accessToken.isEmpty()) {
        qWarning("QOAuth2AuthorizationCodeFlow: Access token not received");
        return;
    }
Jesus Fernandez's avatar
Jesus Fernandez committed
142
    q->setToken(accessToken);
Jesus Fernandez's avatar
Jesus Fernandez committed
143 144
    expiresAt = expiresIn > 0 ? QDateTime::currentDateTime().addSecs(expiresIn) : QDateTime();

Jesus Fernandez's avatar
Jesus Fernandez committed
145
    setStatus(QAbstractOAuth::Status::Granted);
Jesus Fernandez's avatar
Jesus Fernandez committed
146 147
}

Jesus Fernandez's avatar
Jesus Fernandez committed
148
void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply,
Jesus Fernandez's avatar
Jesus Fernandez committed
149 150
                                                       QAuthenticator *authenticator)
{
Jesus Fernandez's avatar
Jesus Fernandez committed
151
    if (reply == currentReply){
Jesus Fernandez's avatar
Jesus Fernandez committed
152 153
        const auto url = reply->url();
        if (url == accessTokenUrl) {
Jesus Fernandez's avatar
Jesus Fernandez committed
154 155
            authenticator->setUser(clientCredentials.first);
            authenticator->setPassword(QString());
Jesus Fernandez's avatar
Jesus Fernandez committed
156 157 158 159
        }
    }
}

Jesus Fernandez's avatar
Jesus Fernandez committed
160 161
QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QObject *parent) :
    QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate, parent)
Jesus Fernandez's avatar
Jesus Fernandez committed
162 163
{}

Jesus Fernandez's avatar
Jesus Fernandez committed
164
QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QNetworkAccessManager *manager,
Jesus Fernandez's avatar
Jesus Fernandez committed
165 166
                                                           QObject *parent) :
    QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(manager), parent)
Jesus Fernandez's avatar
Jesus Fernandez committed
167
{}
Jesus Fernandez's avatar
Jesus Fernandez committed
168

Jesus Fernandez's avatar
Jesus Fernandez committed
169
QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
Jesus Fernandez's avatar
Jesus Fernandez committed
170
                                                           QNetworkAccessManager *manager,
Jesus Fernandez's avatar
Jesus Fernandez committed
171 172
                                                           QObject *parent) :
    QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(manager), parent)
Jesus Fernandez's avatar
Jesus Fernandez committed
173 174
{
    Q_D(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
175
    d->clientCredentials.first = clientIdentifier;
Jesus Fernandez's avatar
Jesus Fernandez committed
176 177
}

Jesus Fernandez's avatar
Jesus Fernandez committed
178
QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QUrl &authenticateUrl,
Jesus Fernandez's avatar
Jesus Fernandez committed
179 180
                                                           const QUrl &accessTokenUrl,
                                                           QNetworkAccessManager *manager,
Jesus Fernandez's avatar
Jesus Fernandez committed
181 182
                                                           QObject *parent) :
    QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(manager), parent)
Jesus Fernandez's avatar
Jesus Fernandez committed
183 184
{
    Q_D(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
185
    d->authorizationUrl = authenticateUrl;
Jesus Fernandez's avatar
Jesus Fernandez committed
186
    d->accessTokenUrl = accessTokenUrl;
Jesus Fernandez's avatar
Jesus Fernandez committed
187 188
}

Jesus Fernandez's avatar
Jesus Fernandez committed
189
QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
Jesus Fernandez's avatar
Jesus Fernandez committed
190 191
                                                           const QUrl &authenticateUrl,
                                                           const QUrl &accessTokenUrl,
Jesus Fernandez's avatar
Jesus Fernandez committed
192
                                                           QNetworkAccessManager *manager,
Jesus Fernandez's avatar
Jesus Fernandez committed
193 194
                                                           QObject *parent) :
    QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(manager), parent)
Jesus Fernandez's avatar
Jesus Fernandez committed
195 196
{
    Q_D(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
197
    d->authorizationUrl = authenticateUrl;
Jesus Fernandez's avatar
Jesus Fernandez committed
198
    d->accessTokenUrl = accessTokenUrl;
Jesus Fernandez's avatar
Jesus Fernandez committed
199
    d->clientCredentials.first = clientIdentifier;
Jesus Fernandez's avatar
Jesus Fernandez committed
200 201 202 203 204
}

QOAuth2AuthorizationCodeFlow::~QOAuth2AuthorizationCodeFlow()
{}

Jesus Fernandez's avatar
Jesus Fernandez committed
205
QString QOAuth2AuthorizationCodeFlow::responseType() const
Jesus Fernandez's avatar
Jesus Fernandez committed
206 207 208 209
{
    return QStringLiteral("code");
}

Jesus Fernandez's avatar
Jesus Fernandez committed
210
QUrl QOAuth2AuthorizationCodeFlow::accessTokenUrl() const
Jesus Fernandez's avatar
Jesus Fernandez committed
211 212
{
    Q_D(const QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
213 214 215 216 217 218 219 220
    return d->accessTokenUrl;
}

void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &accessTokenUrl)
{
    Q_D(QOAuth2AuthorizationCodeFlow);
    if (d->accessTokenUrl != accessTokenUrl) {
        d->accessTokenUrl = accessTokenUrl;
Jesus Fernandez's avatar
Jesus Fernandez committed
221
        Q_EMIT accessTokenUrlChanged(accessTokenUrl);
Jesus Fernandez's avatar
Jesus Fernandez committed
222 223 224 225 226
    }
}

void QOAuth2AuthorizationCodeFlow::grant()
{
Jesus Fernandez's avatar
Jesus Fernandez committed
227
    Q_D(QOAuth2AuthorizationCodeFlow);
Jesus Fernandez's avatar
Jesus Fernandez committed
228 229 230 231 232 233 234
    if (d->authorizationUrl.isEmpty()) {
        qCritical("QOAuth2AuthorizationCodeFlow: No authenticate Url set");
        return;
    } else if (d->accessTokenUrl.isEmpty()) {
        qCritical("QOAuth2AuthorizationCodeFlow: No request access token Url set");
        return;
    }
Jesus Fernandez's avatar
Jesus Fernandez committed
235

Jesus Fernandez's avatar
Jesus Fernandez committed
236
    resourceOwnerAuthorization(d->authorizationUrl);
Jesus Fernandez's avatar
Jesus Fernandez committed
237 238
}

Jesus Fernandez's avatar
Jesus Fernandez committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
void QOAuth2AuthorizationCodeFlow::refreshAccessToken()
{
    Q_D(QOAuth2AuthorizationCodeFlow);

    if (d->refreshToken.isEmpty()) {
        qWarning("QOAuth2AuthorizationCodeFlow::refreshAccessToken: Cannot refresh access token. "
                 "Empty refresh token");
        return;
    }

    typedef QAbstractOAuth2Private::OAuth2KeyString Key;

    QVariantMap parameters;
    QNetworkRequest request(d->accessTokenUrl);
    QUrlQuery query;
    parameters.insert(Key::grantType, QStringLiteral("refresh_token"));
    parameters.insert(Key::refreshToken, d->refreshToken);
    parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback()));
    query = QAbstractOAuthPrivate::createQuery(parameters);
    request.setHeader(QNetworkRequest::ContentTypeHeader,
                      QStringLiteral("application/x-www-form-urlencoded"));

    const QString data = query.toString(QUrl::FullyEncoded);
    d->currentReply = d->networkAccessManager()->post(request, data.toUtf8());

    QObjectPrivate::connect(d->networkAccessManager(), &QNetworkAccessManager::finished,
Jesus Fernandez's avatar
Jesus Fernandez committed
265
                            d, &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished);
Jesus Fernandez's avatar
Jesus Fernandez committed
266 267
    QObjectPrivate::connect(d->networkAccessManager(),
                            &QNetworkAccessManager::authenticationRequired,
Jesus Fernandez's avatar
Jesus Fernandez committed
268
                            d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
Jesus Fernandez's avatar
Jesus Fernandez committed
269 270 271
                            Qt::UniqueConnection);
}

Jesus Fernandez's avatar
Jesus Fernandez committed
272 273 274 275 276
QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QVariantMap &parameters)
{
    Q_D(QOAuth2AuthorizationCodeFlow);
    typedef QAbstractOAuth2Private::OAuth2KeyString Key;

Jesus Fernandez's avatar
Jesus Fernandez committed
277 278 279
    if (d->state.isEmpty())
        d->state = QAbstractOAuth2Private::generateRandomState();
    const QString state = d->state;
Jesus Fernandez's avatar
Jesus Fernandez committed
280

Jesus Fernandez's avatar
Jesus Fernandez committed
281
    QVariantMap p(parameters);
Jesus Fernandez's avatar
Jesus Fernandez committed
282
    QUrl url(d->authorizationUrl);
Jesus Fernandez's avatar
Jesus Fernandez committed
283
    p.insert(Key::responseType, responseType());
Jesus Fernandez's avatar
Jesus Fernandez committed
284
    p.insert(Key::clientIdentifier, d->clientCredentials.first);
Jesus Fernandez's avatar
Jesus Fernandez committed
285 286 287
    p.insert(Key::redirectUri, callback());
    p.insert(Key::scope, d->scope);
    p.insert(Key::state, state);
Jesus Fernandez's avatar
Jesus Fernandez committed
288
    p.insert(Key::apiKey, QVariant());
Jesus Fernandez's avatar
Jesus Fernandez committed
289 290
    if (d->modifyParametersFunction)
        d->modifyParametersFunction(Stage::RequestingAuthorization, &p);
Jesus Fernandez's avatar
Jesus Fernandez committed
291
    url.setQuery(d->createQuery(p));
Jesus Fernandez's avatar
Jesus Fernandez committed
292
    connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::callbackReceived, this,
Jesus Fernandez's avatar
Jesus Fernandez committed
293
            &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, Qt::UniqueConnection);
Jesus Fernandez's avatar
Jesus Fernandez committed
294
    setStatus(QAbstractOAuth::Status::NotAuthenticated);
Jesus Fernandez's avatar
Jesus Fernandez committed
295
    qDebug("QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl: %s", qPrintable(url.toString()));
Jesus Fernandez's avatar
Jesus Fernandez committed
296 297 298
    return url;
}

Jesus Fernandez's avatar
Jesus Fernandez committed
299 300 301 302 303 304 305 306
void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code)
{
    Q_D(QOAuth2AuthorizationCodeFlow);
    typedef QAbstractOAuth2Private::OAuth2KeyString Key;

    QVariantMap parameters;
    QNetworkRequest request(d->accessTokenUrl);
    QUrlQuery query;
Jesus Fernandez's avatar
Jesus Fernandez committed
307
    parameters.insert(Key::grantType, QStringLiteral("authorization_code"));
Jesus Fernandez's avatar
Jesus Fernandez committed
308 309
    parameters.insert(Key::code, QUrl::toPercentEncoding(code));
    parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback()));
Jesus Fernandez's avatar
Jesus Fernandez committed
310 311 312
    parameters.insert(Key::clientIdentifier, QUrl::toPercentEncoding(d->clientCredentials.first));
    if (!d->clientCredentials.second.isEmpty())
        parameters.insert(Key::clientSharedSecret, d->clientCredentials.second);
Jesus Fernandez's avatar
Jesus Fernandez committed
313 314 315 316 317 318
    if (d->modifyParametersFunction)
        d->modifyParametersFunction(Stage::RequestingAccessToken, &parameters);
    query = QAbstractOAuthPrivate::createQuery(parameters);
    request.setHeader(QNetworkRequest::ContentTypeHeader,
                      QStringLiteral("application/x-www-form-urlencoded"));

Jesus Fernandez's avatar
Jesus Fernandez committed
319 320
    const QString data = query.toString(QUrl::FullyEncoded);
    d->currentReply = d->networkAccessManager()->post(request, data.toUtf8());
321
    QObjectPrivate::connect(d->networkAccessManager(), &QNetworkAccessManager::finished,
Jesus Fernandez's avatar
Jesus Fernandez committed
322
                            d, &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished);
323 324
    QObjectPrivate::connect(d->networkAccessManager(),
                            &QNetworkAccessManager::authenticationRequired,
Jesus Fernandez's avatar
Jesus Fernandez committed
325 326 327 328 329 330 331 332 333 334 335 336
                            d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
                            Qt::UniqueConnection);
}

void QOAuth2AuthorizationCodeFlow::resourceOwnerAuthorization(const QUrl &url,
                                                              const QVariantMap &parameters)
{
    Q_D(QOAuth2AuthorizationCodeFlow);
    Q_ASSERT(url == d->authorizationUrl);
    const QUrl u = buildAuthenticateUrl(parameters);
    QObjectPrivate::connect(this, &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, d,
                            &QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback,
Jesus Fernandez's avatar
Jesus Fernandez committed
337
                            Qt::UniqueConnection);
Jesus Fernandez's avatar
Jesus Fernandez committed
338
    Q_EMIT authorizeWithBrowser(u);
Jesus Fernandez's avatar
Jesus Fernandez committed
339 340
}

Jesus Fernandez's avatar
Jesus Fernandez committed
341 342 343
QT_END_NAMESPACE

#endif // QT_NO_HTTP