@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace Katabasis {
|
||||
@ -11,17 +11,13 @@ enum class Activity {
|
||||
LoggingIn,
|
||||
LoggingOut,
|
||||
Refreshing,
|
||||
FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated
|
||||
FailedHard, //!< hard failure. auth is invalid
|
||||
FailedGone, //!< hard failure. auth is invalid, and the account no longer exists
|
||||
FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated
|
||||
FailedHard, //!< hard failure. auth is invalid
|
||||
FailedGone, //!< hard failure. auth is invalid, and the account no longer exists
|
||||
Succeeded
|
||||
};
|
||||
|
||||
enum class Validity {
|
||||
None,
|
||||
Assumed,
|
||||
Certain
|
||||
};
|
||||
enum class Validity { None, Assumed, Certain };
|
||||
|
||||
struct Token {
|
||||
QDateTime issueInstant;
|
||||
@ -34,4 +30,4 @@ struct Token {
|
||||
bool persistent = true;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPair>
|
||||
|
||||
#include "Bits.h"
|
||||
#include "Reply.h"
|
||||
#include "RequestParameter.h"
|
||||
#include "Bits.h"
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
@ -16,14 +16,12 @@ class ReplyServer;
|
||||
class PollServer;
|
||||
|
||||
/// Simple OAuth2 Device Flow authenticator.
|
||||
class DeviceFlow: public QObject
|
||||
{
|
||||
class DeviceFlow : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
public:
|
||||
Q_ENUMS(GrantFlow)
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
struct Options {
|
||||
QString userAgent = QStringLiteral("Katabasis/1.0");
|
||||
QString responseType = QStringLiteral("code");
|
||||
@ -34,7 +32,7 @@ public:
|
||||
QUrl accessTokenUrl;
|
||||
};
|
||||
|
||||
public:
|
||||
public:
|
||||
/// Are we authenticated?
|
||||
bool linked();
|
||||
|
||||
@ -44,21 +42,21 @@ public:
|
||||
/// Provider-specific extra tokens, available after a successful authentication
|
||||
QVariantMap extraTokens();
|
||||
|
||||
public:
|
||||
public:
|
||||
// TODO: put in `Options`
|
||||
/// User-defined extra parameters to append to request URL
|
||||
QVariantMap extraRequestParams();
|
||||
void setExtraRequestParams(const QVariantMap &value);
|
||||
void setExtraRequestParams(const QVariantMap& value);
|
||||
|
||||
// TODO: split up the class into multiple, each implementing one OAuth2 flow
|
||||
/// Grant type (if non-standard)
|
||||
QString grantType();
|
||||
void setGrantType(const QString &value);
|
||||
void setGrantType(const QString& value);
|
||||
|
||||
public:
|
||||
public:
|
||||
/// Constructor.
|
||||
/// @param parent Parent object.
|
||||
explicit DeviceFlow(Options & opts, Token & token, QObject *parent = 0, QNetworkAccessManager *manager = 0);
|
||||
explicit DeviceFlow(Options& opts, Token& token, QObject* parent = 0, QNetworkAccessManager* manager = 0);
|
||||
|
||||
/// Get refresh token.
|
||||
QString refreshToken();
|
||||
@ -66,7 +64,7 @@ public:
|
||||
/// Get token expiration time
|
||||
QDateTime expires();
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
/// Authenticate.
|
||||
void login();
|
||||
|
||||
@ -79,24 +77,24 @@ public slots:
|
||||
/// Handle situation where reply server has opted to close its connection
|
||||
void serverHasClosed(bool paramsfound = false);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
/// Emitted when client needs to open a web browser window, with the given URL.
|
||||
void openBrowser(const QUrl &url);
|
||||
void openBrowser(const QUrl& url);
|
||||
|
||||
/// Emitted when client can close the browser window.
|
||||
void closeBrowser();
|
||||
|
||||
/// Emitted when client needs to show a verification uri and user code
|
||||
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
|
||||
/// Emitted when the internal state changes
|
||||
void activityChanged(Activity activity);
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
/// Handle verification response.
|
||||
void onVerificationReceived(QMap<QString, QString>);
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
/// Handle completion of a Device Authorization Request
|
||||
void onDeviceAuthReplyFinished();
|
||||
|
||||
@ -104,20 +102,20 @@ protected slots:
|
||||
void onRefreshFinished();
|
||||
|
||||
/// Handle failure of a refresh request.
|
||||
void onRefreshError(QNetworkReply::NetworkError error, QNetworkReply *reply);
|
||||
void onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* reply);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
/// Set refresh token.
|
||||
void setRefreshToken(const QString &v);
|
||||
void setRefreshToken(const QString& v);
|
||||
|
||||
/// Set token expiration time.
|
||||
void setExpires(QDateTime v);
|
||||
|
||||
/// Start polling authorization server
|
||||
void startPollServer(const QVariantMap ¶ms, int expiresIn);
|
||||
void startPollServer(const QVariantMap& params, int expiresIn);
|
||||
|
||||
/// Set authentication token.
|
||||
void setToken(const QString &v);
|
||||
void setToken(const QString& v);
|
||||
|
||||
/// Set the linked state
|
||||
void setLinked(bool v);
|
||||
@ -126,26 +124,26 @@ protected:
|
||||
void setExtraTokens(QVariantMap extraTokens);
|
||||
|
||||
/// Set local poll server
|
||||
void setPollServer(PollServer *server);
|
||||
void setPollServer(PollServer* server);
|
||||
|
||||
PollServer * pollServer() const;
|
||||
PollServer* pollServer() const;
|
||||
|
||||
void updateActivity(Activity activity);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
Options options_;
|
||||
|
||||
QVariantMap extraReqParams_;
|
||||
QNetworkAccessManager *manager_ = nullptr;
|
||||
QNetworkAccessManager* manager_ = nullptr;
|
||||
ReplyList timedReplies_;
|
||||
QString grantType_;
|
||||
|
||||
protected:
|
||||
Token &token_;
|
||||
protected:
|
||||
Token& token_;
|
||||
|
||||
private:
|
||||
PollServer *pollServer_ = nullptr;
|
||||
private:
|
||||
PollServer* pollServer_ = nullptr;
|
||||
Activity activity_ = Activity::Idle;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -45,7 +45,7 @@ const char OAUTH2_EXPIRES_IN[] = "expires_in";
|
||||
const char OAUTH2_DEVICE_CODE[] = "device_code";
|
||||
const char OAUTH2_USER_CODE[] = "user_code";
|
||||
const char OAUTH2_VERIFICATION_URI[] = "verification_uri";
|
||||
const char OAUTH2_VERIFICATION_URL[] = "verification_url"; // Google sign-in
|
||||
const char OAUTH2_VERIFICATION_URL[] = "verification_url"; // Google sign-in
|
||||
const char OAUTH2_VERIFICATION_URI_COMPLETE[] = "verification_uri_complete";
|
||||
const char OAUTH2_INTERVAL[] = "interval";
|
||||
|
||||
@ -56,4 +56,4 @@ const char AUTHORIZATION_CODE[] = "authorization_code";
|
||||
const char HTTP_HTTP_HEADER[] = "HTTP";
|
||||
const char HTTP_AUTHORIZATION_HEADER[] = "Authorization";
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -12,32 +12,35 @@ class QNetworkAccessManager;
|
||||
namespace Katabasis {
|
||||
|
||||
/// Poll an authorization server for token
|
||||
class PollServer : public QObject
|
||||
{
|
||||
class PollServer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PollServer(QNetworkAccessManager * manager, const QNetworkRequest &request, const QByteArray & payload, int expiresIn, QObject *parent = 0);
|
||||
public:
|
||||
explicit PollServer(QNetworkAccessManager* manager,
|
||||
const QNetworkRequest& request,
|
||||
const QByteArray& payload,
|
||||
int expiresIn,
|
||||
QObject* parent = 0);
|
||||
|
||||
/// Seconds to wait between polling requests
|
||||
Q_PROPERTY(int interval READ interval WRITE setInterval)
|
||||
int interval() const;
|
||||
void setInterval(int interval);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void verificationReceived(QMap<QString, QString>);
|
||||
void serverClosed(bool); // whether it has found parameters
|
||||
void serverClosed(bool); // whether it has found parameters
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
void startPolling();
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
void onPollTimeout();
|
||||
void onExpiration();
|
||||
void onReplyFinished();
|
||||
|
||||
protected:
|
||||
QNetworkAccessManager *manager_;
|
||||
protected:
|
||||
QNetworkAccessManager* manager_;
|
||||
const QNetworkRequest request_;
|
||||
const QByteArray payload_;
|
||||
const int expiresIn_;
|
||||
@ -45,4 +48,4 @@ protected:
|
||||
QTimer pollTimer;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -1,38 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
constexpr int defaultTimeout = 30 * 1000;
|
||||
|
||||
/// A network request/reply pair that can time out.
|
||||
class Reply: public QTimer {
|
||||
class Reply : public QTimer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Reply(QNetworkReply *reply, int timeOut = defaultTimeout, QObject *parent = 0);
|
||||
public:
|
||||
Reply(QNetworkReply* reply, int timeOut = defaultTimeout, QObject* parent = 0);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void error(QNetworkReply::NetworkError);
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
/// When time out occurs, the QNetworkReply's error() signal is triggered.
|
||||
void onTimeOut();
|
||||
|
||||
public:
|
||||
QNetworkReply *reply;
|
||||
public:
|
||||
QNetworkReply* reply;
|
||||
bool timedOut = false;
|
||||
};
|
||||
|
||||
/// List of O2Replies.
|
||||
class ReplyList {
|
||||
public:
|
||||
public:
|
||||
ReplyList() { ignoreSslErrors_ = false; }
|
||||
|
||||
/// Destructor.
|
||||
@ -40,24 +40,24 @@ public:
|
||||
virtual ~ReplyList();
|
||||
|
||||
/// Create a new O2Reply from a QNetworkReply, and add it to this list.
|
||||
void add(QNetworkReply *reply, int timeOut = defaultTimeout);
|
||||
void add(QNetworkReply* reply, int timeOut = defaultTimeout);
|
||||
|
||||
/// Add an O2Reply to the list, while taking ownership of it.
|
||||
void add(Reply *reply);
|
||||
void add(Reply* reply);
|
||||
|
||||
/// Remove item from the list that corresponds to a QNetworkReply.
|
||||
void remove(QNetworkReply *reply);
|
||||
void remove(QNetworkReply* reply);
|
||||
|
||||
/// Find an O2Reply in the list, corresponding to a QNetworkReply.
|
||||
/// @return Matching O2Reply or NULL.
|
||||
Reply *find(QNetworkReply *reply);
|
||||
Reply* find(QNetworkReply* reply);
|
||||
|
||||
bool ignoreSslErrors();
|
||||
void setIgnoreSslErrors(bool ignoreSslErrors);
|
||||
|
||||
protected:
|
||||
QList<Reply *> replies_;
|
||||
protected:
|
||||
QList<Reply*> replies_;
|
||||
bool ignoreSslErrors_;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -4,12 +4,10 @@ namespace Katabasis {
|
||||
|
||||
/// Request parameter (name-value pair) participating in authentication.
|
||||
struct RequestParameter {
|
||||
RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) {}
|
||||
bool operator <(const RequestParameter &other) const {
|
||||
return (name == other.name)? (value < other.value): (name < other.name);
|
||||
}
|
||||
RequestParameter(const QByteArray& n, const QByteArray& v) : name(n), value(v) {}
|
||||
bool operator<(const RequestParameter& other) const { return (name == other.name) ? (value < other.value) : (name < other.name); }
|
||||
QByteArray name;
|
||||
QByteArray value;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -1,26 +1,26 @@
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QDebug>
|
||||
#include <QTcpServer>
|
||||
#include <QMap>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDateTime>
|
||||
#include <QCryptographicHash>
|
||||
#include <QTimer>
|
||||
#include <QVariantMap>
|
||||
#include <QUuid>
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPair>
|
||||
#include <QTcpServer>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "katabasis/DeviceFlow.h"
|
||||
#include "katabasis/PollServer.h"
|
||||
#include "katabasis/Globals.h"
|
||||
#include "katabasis/PollServer.h"
|
||||
|
||||
#include "KatabasisLogging.h"
|
||||
#include "JsonResponse.h"
|
||||
#include "KatabasisLogging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -43,10 +43,11 @@ bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray createQueryParameters(const QList<Katabasis::RequestParameter> ¶meters) {
|
||||
QByteArray createQueryParameters(const QList<Katabasis::RequestParameter>& parameters)
|
||||
{
|
||||
QByteArray ret;
|
||||
bool first = true;
|
||||
for( auto & h: parameters) {
|
||||
for (auto& h : parameters) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
@ -56,32 +57,35 @@ QByteArray createQueryParameters(const QList<Katabasis::RequestParameter> ¶m
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
DeviceFlow::DeviceFlow(Options & opts, Token & token, QObject *parent, QNetworkAccessManager *manager) : QObject(parent), token_(token) {
|
||||
DeviceFlow::DeviceFlow(Options& opts, Token& token, QObject* parent, QNetworkAccessManager* manager) : QObject(parent), token_(token)
|
||||
{
|
||||
manager_ = manager ? manager : new QNetworkAccessManager(this);
|
||||
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
|
||||
options_ = opts;
|
||||
}
|
||||
|
||||
bool DeviceFlow::linked() {
|
||||
bool DeviceFlow::linked()
|
||||
{
|
||||
return token_.validity != Validity::None;
|
||||
}
|
||||
void DeviceFlow::setLinked(bool v) {
|
||||
qDebug() << "DeviceFlow::setLinked:" << (v? "true": "false");
|
||||
void DeviceFlow::setLinked(bool v)
|
||||
{
|
||||
qDebug() << "DeviceFlow::setLinked:" << (v ? "true" : "false");
|
||||
token_.validity = v ? Validity::Certain : Validity::None;
|
||||
}
|
||||
|
||||
void DeviceFlow::updateActivity(Activity activity)
|
||||
{
|
||||
if(activity_ == activity) {
|
||||
if (activity_ == activity) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity_ = activity;
|
||||
switch(activity) {
|
||||
switch (activity) {
|
||||
case Katabasis::Activity::Idle:
|
||||
case Katabasis::Activity::LoggingIn:
|
||||
case Katabasis::Activity::LoggingOut:
|
||||
@ -103,22 +107,26 @@ void DeviceFlow::updateActivity(Activity activity)
|
||||
emit activityChanged(activity_);
|
||||
}
|
||||
|
||||
QString DeviceFlow::token() {
|
||||
QString DeviceFlow::token()
|
||||
{
|
||||
return token_.token;
|
||||
}
|
||||
void DeviceFlow::setToken(const QString &v) {
|
||||
void DeviceFlow::setToken(const QString& v)
|
||||
{
|
||||
token_.token = v;
|
||||
}
|
||||
|
||||
QVariantMap DeviceFlow::extraTokens() {
|
||||
QVariantMap DeviceFlow::extraTokens()
|
||||
{
|
||||
return token_.extra;
|
||||
}
|
||||
|
||||
void DeviceFlow::setExtraTokens(QVariantMap extraTokens) {
|
||||
void DeviceFlow::setExtraTokens(QVariantMap extraTokens)
|
||||
{
|
||||
token_.extra = extraTokens;
|
||||
}
|
||||
|
||||
void DeviceFlow::setPollServer(PollServer *server)
|
||||
void DeviceFlow::setPollServer(PollServer* server)
|
||||
{
|
||||
if (pollServer_)
|
||||
pollServer_->deleteLater();
|
||||
@ -126,7 +134,7 @@ void DeviceFlow::setPollServer(PollServer *server)
|
||||
pollServer_ = server;
|
||||
}
|
||||
|
||||
PollServer *DeviceFlow::pollServer() const
|
||||
PollServer* DeviceFlow::pollServer() const
|
||||
{
|
||||
return pollServer_;
|
||||
}
|
||||
@ -136,7 +144,7 @@ QVariantMap DeviceFlow::extraRequestParams()
|
||||
return extraReqParams_;
|
||||
}
|
||||
|
||||
void DeviceFlow::setExtraRequestParams(const QVariantMap &value)
|
||||
void DeviceFlow::setExtraRequestParams(const QVariantMap& value)
|
||||
{
|
||||
extraReqParams_ = value;
|
||||
}
|
||||
@ -149,13 +157,14 @@ QString DeviceFlow::grantType()
|
||||
return OAUTH2_GRANT_TYPE_DEVICE;
|
||||
}
|
||||
|
||||
void DeviceFlow::setGrantType(const QString &value)
|
||||
void DeviceFlow::setGrantType(const QString& value)
|
||||
{
|
||||
grantType_ = value;
|
||||
}
|
||||
|
||||
// First get the URL and token to display to the user
|
||||
void DeviceFlow::login() {
|
||||
void DeviceFlow::login()
|
||||
{
|
||||
qDebug() << "DeviceFlow::link";
|
||||
|
||||
updateActivity(Activity::LoggingIn);
|
||||
@ -173,7 +182,7 @@ void DeviceFlow::login() {
|
||||
QUrl url(options_.authorizationUrl);
|
||||
QNetworkRequest deviceRequest(url);
|
||||
deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
QNetworkReply *tokenReply = manager_->post(deviceRequest, payload);
|
||||
QNetworkReply* tokenReply = manager_->post(deviceRequest, payload);
|
||||
|
||||
connect(tokenReply, &QNetworkReply::finished, this, &DeviceFlow::onDeviceAuthReplyFinished, Qt::QueuedConnection);
|
||||
}
|
||||
@ -182,19 +191,18 @@ void DeviceFlow::login() {
|
||||
void DeviceFlow::onDeviceAuthReplyFinished()
|
||||
{
|
||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished";
|
||||
QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (!tokenReply)
|
||||
{
|
||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null";
|
||||
return;
|
||||
QNetworkReply* tokenReply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!tokenReply) {
|
||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null";
|
||||
return;
|
||||
}
|
||||
if (tokenReply->error() == QNetworkReply::NoError) {
|
||||
QByteArray replyData = tokenReply->readAll();
|
||||
|
||||
// Dump replyData
|
||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds
|
||||
//qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n";
|
||||
//qDebug() << QString( replyData );
|
||||
// qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n";
|
||||
// qDebug() << QString( replyData );
|
||||
|
||||
QVariantMap params = parseJsonResponse(replyData);
|
||||
|
||||
@ -202,7 +210,7 @@ void DeviceFlow::onDeviceAuthReplyFinished()
|
||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Tokens returned:\n";
|
||||
foreach (QString key, params.keys()) {
|
||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
|
||||
qDebug() << key << ": "<< params.value( key ).toString();
|
||||
qDebug() << key << ": " << params.value(key).toString();
|
||||
}
|
||||
|
||||
// Check for mandatory parameters
|
||||
@ -237,7 +245,7 @@ void DeviceFlow::onDeviceAuthReplyFinished()
|
||||
}
|
||||
|
||||
// Spin up polling for the user completing the login flow out of band
|
||||
void DeviceFlow::startPollServer(const QVariantMap ¶ms, int expiresIn)
|
||||
void DeviceFlow::startPollServer(const QVariantMap& params, int expiresIn)
|
||||
{
|
||||
qDebug() << "DeviceFlow::startPollServer: device_ and user_code expires in" << expiresIn << "seconds";
|
||||
|
||||
@ -250,14 +258,14 @@ void DeviceFlow::startPollServer(const QVariantMap ¶ms, int expiresIn)
|
||||
|
||||
QList<RequestParameter> parameters;
|
||||
parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8()));
|
||||
if ( !options_.clientSecret.isEmpty() ) {
|
||||
if (!options_.clientSecret.isEmpty()) {
|
||||
parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8()));
|
||||
}
|
||||
parameters.append(RequestParameter(OAUTH2_CODE, deviceCode.toUtf8()));
|
||||
parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8()));
|
||||
QByteArray payload = createQueryParameters(parameters);
|
||||
|
||||
PollServer * pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this);
|
||||
PollServer* pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this);
|
||||
if (params.contains(OAUTH2_INTERVAL)) {
|
||||
bool ok = false;
|
||||
int interval = params[OAUTH2_INTERVAL].toInt(&ok);
|
||||
@ -272,7 +280,8 @@ void DeviceFlow::startPollServer(const QVariantMap ¶ms, int expiresIn)
|
||||
}
|
||||
|
||||
// Once the user completes the flow, update the internal state and report it to observers
|
||||
void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response) {
|
||||
void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response)
|
||||
{
|
||||
qDebug() << "DeviceFlow::onVerificationReceived: Emitting closeBrowser()";
|
||||
emit closeBrowser();
|
||||
|
||||
@ -307,7 +316,7 @@ void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response) {
|
||||
// Or if the flow fails or the polling times out, update the internal state with error and report it to observers
|
||||
void DeviceFlow::serverHasClosed(bool paramsfound)
|
||||
{
|
||||
if ( !paramsfound ) {
|
||||
if (!paramsfound) {
|
||||
// server has probably timed out after receiving first response
|
||||
updateActivity(Activity::FailedHard);
|
||||
}
|
||||
@ -315,7 +324,8 @@ void DeviceFlow::serverHasClosed(bool paramsfound)
|
||||
setPollServer(NULL);
|
||||
}
|
||||
|
||||
void DeviceFlow::logout() {
|
||||
void DeviceFlow::logout()
|
||||
{
|
||||
qDebug() << "DeviceFlow::unlink";
|
||||
updateActivity(Activity::LoggingOut);
|
||||
// FIXME: implement logout flows... if they exist
|
||||
@ -323,24 +333,29 @@ void DeviceFlow::logout() {
|
||||
updateActivity(Activity::FailedHard);
|
||||
}
|
||||
|
||||
QDateTime DeviceFlow::expires() {
|
||||
QDateTime DeviceFlow::expires()
|
||||
{
|
||||
return token_.notAfter;
|
||||
}
|
||||
void DeviceFlow::setExpires(QDateTime v) {
|
||||
void DeviceFlow::setExpires(QDateTime v)
|
||||
{
|
||||
token_.notAfter = v;
|
||||
}
|
||||
|
||||
QString DeviceFlow::refreshToken() {
|
||||
QString DeviceFlow::refreshToken()
|
||||
{
|
||||
return token_.refresh_token;
|
||||
}
|
||||
|
||||
void DeviceFlow::setRefreshToken(const QString &v) {
|
||||
void DeviceFlow::setRefreshToken(const QString& v)
|
||||
{
|
||||
qCDebug(katabasisCredentials) << "new refresh token:" << v;
|
||||
token_.refresh_token = v;
|
||||
}
|
||||
|
||||
namespace {
|
||||
QByteArray buildRequestBody(const QMap<QString, QString> ¶meters) {
|
||||
QByteArray buildRequestBody(const QMap<QString, QString>& parameters)
|
||||
{
|
||||
QByteArray body;
|
||||
bool first = true;
|
||||
foreach (QString key, parameters.keys()) {
|
||||
@ -354,9 +369,10 @@ QByteArray buildRequestBody(const QMap<QString, QString> ¶meters) {
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool DeviceFlow::refresh() {
|
||||
bool DeviceFlow::refresh()
|
||||
{
|
||||
qDebug() << "DeviceFlow::refresh: Token: ..." << refreshToken().right(7);
|
||||
|
||||
updateActivity(Activity::Refreshing);
|
||||
@ -376,21 +392,22 @@ bool DeviceFlow::refresh() {
|
||||
refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM);
|
||||
QMap<QString, QString> parameters;
|
||||
parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier);
|
||||
if ( !options_.clientSecret.isEmpty() ) {
|
||||
if (!options_.clientSecret.isEmpty()) {
|
||||
parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret);
|
||||
}
|
||||
parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken());
|
||||
parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN);
|
||||
|
||||
QByteArray data = buildRequestBody(parameters);
|
||||
QNetworkReply *refreshReply = manager_->post(refreshRequest, data);
|
||||
QNetworkReply* refreshReply = manager_->post(refreshRequest, data);
|
||||
timedReplies_.add(refreshReply);
|
||||
connect(refreshReply, &QNetworkReply::finished, this, &DeviceFlow::onRefreshFinished, Qt::QueuedConnection);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceFlow::onRefreshFinished() {
|
||||
QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
|
||||
void DeviceFlow::onRefreshFinished()
|
||||
{
|
||||
QNetworkReply* refreshReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
auto networkError = refreshReply->error();
|
||||
if (networkError == QNetworkReply::NoError) {
|
||||
@ -399,10 +416,9 @@ void DeviceFlow::onRefreshFinished() {
|
||||
setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString());
|
||||
setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt()));
|
||||
QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString();
|
||||
if(!refreshToken.isEmpty()) {
|
||||
if (!refreshToken.isEmpty()) {
|
||||
setRefreshToken(refreshToken);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qDebug() << "No new refresh token. Keep the old one.";
|
||||
}
|
||||
timedReplies_.remove(refreshReply);
|
||||
@ -415,37 +431,37 @@ void DeviceFlow::onRefreshFinished() {
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply *refreshReply) {
|
||||
void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* refreshReply)
|
||||
{
|
||||
QString errorString = "No Reply";
|
||||
if(refreshReply) {
|
||||
if (refreshReply) {
|
||||
timedReplies_.remove(refreshReply);
|
||||
errorString = refreshReply->errorString();
|
||||
}
|
||||
|
||||
switch (error)
|
||||
{
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
updateActivity(Activity::FailedHard);
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
updateActivity(Activity::FailedGone);
|
||||
break;
|
||||
switch (error) {
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
updateActivity(Activity::FailedHard);
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
updateActivity(Activity::FailedGone);
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::TimeoutError:
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
default:
|
||||
updateActivity(Activity::FailedSoft);
|
||||
return;
|
||||
}
|
||||
case QNetworkReply::TimeoutError:
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
default:
|
||||
updateActivity(Activity::FailedSoft);
|
||||
return;
|
||||
}
|
||||
if(refreshReply) {
|
||||
if (refreshReply) {
|
||||
refreshReply->deleteLater();
|
||||
}
|
||||
qDebug() << "DeviceFlow::onRefreshFinished: Error" << static_cast<int>(error) << " - " << errorString;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -7,7 +7,8 @@
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
QVariantMap parseJsonResponse(const QByteArray &data) {
|
||||
QVariantMap parseJsonResponse(const QByteArray& data)
|
||||
{
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
@ -23,4 +24,4 @@ QVariantMap parseJsonResponse(const QByteArray &data) {
|
||||
return doc.object().toVariantMap();
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -6,7 +6,7 @@ class QByteArray;
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
/// Parse JSON data into a QVariantMap
|
||||
QVariantMap parseJsonResponse(const QByteArray &data);
|
||||
/// Parse JSON data into a QVariantMap
|
||||
QVariantMap parseJsonResponse(const QByteArray& data);
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -1,30 +1,28 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "katabasis/PollServer.h"
|
||||
#include "JsonResponse.h"
|
||||
#include "katabasis/PollServer.h"
|
||||
|
||||
namespace {
|
||||
QMap<QString, QString> toVerificationParams(const QVariantMap &map)
|
||||
QMap<QString, QString> toVerificationParams(const QVariantMap& map)
|
||||
{
|
||||
QMap<QString, QString> params;
|
||||
for (QVariantMap::const_iterator i = map.constBegin();
|
||||
i != map.constEnd(); ++i)
|
||||
{
|
||||
for (QVariantMap::const_iterator i = map.constBegin(); i != map.constEnd(); ++i) {
|
||||
params[i.key()] = i.value().toString();
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
PollServer::PollServer(QNetworkAccessManager *manager, const QNetworkRequest &request, const QByteArray &payload, int expiresIn, QObject *parent)
|
||||
: QObject(parent)
|
||||
, manager_(manager)
|
||||
, request_(request)
|
||||
, payload_(payload)
|
||||
, expiresIn_(expiresIn)
|
||||
PollServer::PollServer(QNetworkAccessManager* manager,
|
||||
const QNetworkRequest& request,
|
||||
const QByteArray& payload,
|
||||
int expiresIn,
|
||||
QObject* parent)
|
||||
: QObject(parent), manager_(manager), request_(request), payload_(payload), expiresIn_(expiresIn)
|
||||
{
|
||||
expirationTimer.setTimerType(Qt::VeryCoarseTimer);
|
||||
expirationTimer.setInterval(expiresIn * 1000);
|
||||
@ -58,7 +56,7 @@ void PollServer::startPolling()
|
||||
void PollServer::onPollTimeout()
|
||||
{
|
||||
qDebug() << "PollServer::onPollTimeout: retrying";
|
||||
QNetworkReply * reply = manager_->post(request_, payload_);
|
||||
QNetworkReply* reply = manager_->post(request_, payload_);
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
|
||||
}
|
||||
|
||||
@ -70,7 +68,7 @@ void PollServer::onExpiration()
|
||||
|
||||
void PollServer::onReplyFinished()
|
||||
{
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (!reply) {
|
||||
qDebug() << "PollServer::onReplyFinished: reply is null";
|
||||
@ -93,8 +91,7 @@ void PollServer::onReplyFinished()
|
||||
// polling interval on each such connection timeout, is RECOMMENDED."
|
||||
setInterval(interval() * 2);
|
||||
pollTimer.start();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
QString error = params.value("error");
|
||||
if (error == "slow_down") {
|
||||
// rfc8628#section-3.2
|
||||
@ -103,14 +100,12 @@ void PollServer::onReplyFinished()
|
||||
// be increased by 5 seconds for this and all subsequent requests."
|
||||
setInterval(interval() + 5);
|
||||
pollTimer.start();
|
||||
}
|
||||
else if (error == "authorization_pending") {
|
||||
} else if (error == "authorization_pending") {
|
||||
// keep trying - rfc8628#section-3.2
|
||||
// "The authorization request is still pending as the end user hasn't
|
||||
// yet completed the user-interaction steps (Section 3.3)."
|
||||
pollTimer.start();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
expirationTimer.stop();
|
||||
emit serverClosed(true);
|
||||
// let O2 handle the other cases
|
||||
@ -120,4 +115,4 @@ void PollServer::onReplyFinished()
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
@ -1,42 +1,48 @@
|
||||
#include <QTimer>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
|
||||
#include "katabasis/Reply.h"
|
||||
|
||||
namespace Katabasis {
|
||||
|
||||
Reply::Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) {
|
||||
Reply::Reply(QNetworkReply* r, int timeOut, QObject* parent) : QTimer(parent), reply(r)
|
||||
{
|
||||
setSingleShot(true);
|
||||
connect(this, &Reply::timeout, this, &Reply::onTimeOut, Qt::QueuedConnection);
|
||||
start(timeOut);
|
||||
}
|
||||
|
||||
void Reply::onTimeOut() {
|
||||
void Reply::onTimeOut()
|
||||
{
|
||||
timedOut = true;
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
|
||||
ReplyList::~ReplyList() {
|
||||
foreach (Reply *timedReply, replies_) {
|
||||
ReplyList::~ReplyList()
|
||||
{
|
||||
foreach (Reply* timedReply, replies_) {
|
||||
delete timedReply;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplyList::add(QNetworkReply *reply, int timeOut) {
|
||||
void ReplyList::add(QNetworkReply* reply, int timeOut)
|
||||
{
|
||||
if (reply && ignoreSslErrors()) {
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
add(new Reply(reply, timeOut));
|
||||
}
|
||||
|
||||
void ReplyList::add(Reply *reply) {
|
||||
void ReplyList::add(Reply* reply)
|
||||
{
|
||||
replies_.append(reply);
|
||||
}
|
||||
|
||||
void ReplyList::remove(QNetworkReply *reply) {
|
||||
Reply *o2Reply = find(reply);
|
||||
void ReplyList::remove(QNetworkReply* reply)
|
||||
{
|
||||
Reply* o2Reply = find(reply);
|
||||
if (o2Reply) {
|
||||
o2Reply->stop();
|
||||
(void)replies_.removeOne(o2Reply);
|
||||
@ -45,8 +51,9 @@ void ReplyList::remove(QNetworkReply *reply) {
|
||||
}
|
||||
}
|
||||
|
||||
Reply *ReplyList::find(QNetworkReply *reply) {
|
||||
foreach (Reply *timedReply, replies_) {
|
||||
Reply* ReplyList::find(QNetworkReply* reply)
|
||||
{
|
||||
foreach (Reply* timedReply, replies_) {
|
||||
if (timedReply->reply == reply) {
|
||||
return timedReply;
|
||||
}
|
||||
@ -64,4 +71,4 @@ void ReplyList::setIgnoreSslErrors(bool ignoreSslErrors)
|
||||
ignoreSslErrors_ = ignoreSslErrors;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Katabasis
|
||||
|
Reference in New Issue
Block a user