NOISSUE bulk addition of code from Katabasis

This commit is contained in:
Petr Mrázek
2021-07-22 20:15:20 +02:00
parent 2568752af5
commit dd13368085
31 changed files with 3687 additions and 18 deletions

View File

@ -0,0 +1,33 @@
#pragma once
#include <QString>
#include <QDateTime>
#include <QMap>
#include <QVariantMap>
namespace Katabasis {
enum class Activity {
Idle,
LoggingIn,
LoggingOut,
Refreshing
};
enum class Validity {
None,
Assumed,
Certain
};
struct Token {
QDateTime issueInstant;
QDateTime notAfter;
QString token;
QString refresh_token;
QVariantMap extra;
Validity validity = Validity::None;
bool persistent = true;
};
}

View File

@ -0,0 +1,59 @@
#pragma once
namespace Katabasis {
// Common constants
const char ENCRYPTION_KEY[] = "12345678";
const char MIME_TYPE_XFORM[] = "application/x-www-form-urlencoded";
const char MIME_TYPE_JSON[] = "application/json";
// OAuth 1/1.1 Request Parameters
const char OAUTH_CALLBACK[] = "oauth_callback";
const char OAUTH_CONSUMER_KEY[] = "oauth_consumer_key";
const char OAUTH_NONCE[] = "oauth_nonce";
const char OAUTH_SIGNATURE[] = "oauth_signature";
const char OAUTH_SIGNATURE_METHOD[] = "oauth_signature_method";
const char OAUTH_TIMESTAMP[] = "oauth_timestamp";
const char OAUTH_VERSION[] = "oauth_version";
// OAuth 1/1.1 Response Parameters
const char OAUTH_TOKEN[] = "oauth_token";
const char OAUTH_TOKEN_SECRET[] = "oauth_token_secret";
const char OAUTH_CALLBACK_CONFIRMED[] = "oauth_callback_confirmed";
const char OAUTH_VERFIER[] = "oauth_verifier";
// OAuth 2 Request Parameters
const char OAUTH2_RESPONSE_TYPE[] = "response_type";
const char OAUTH2_CLIENT_ID[] = "client_id";
const char OAUTH2_CLIENT_SECRET[] = "client_secret";
const char OAUTH2_USERNAME[] = "username";
const char OAUTH2_PASSWORD[] = "password";
const char OAUTH2_REDIRECT_URI[] = "redirect_uri";
const char OAUTH2_SCOPE[] = "scope";
const char OAUTH2_GRANT_TYPE_CODE[] = "code";
const char OAUTH2_GRANT_TYPE_TOKEN[] = "token";
const char OAUTH2_GRANT_TYPE_PASSWORD[] = "password";
const char OAUTH2_GRANT_TYPE_DEVICE[] = "urn:ietf:params:oauth:grant-type:device_code";
const char OAUTH2_GRANT_TYPE[] = "grant_type";
const char OAUTH2_API_KEY[] = "api_key";
const char OAUTH2_STATE[] = "state";
const char OAUTH2_CODE[] = "code";
// OAuth 2 Response Parameters
const char OAUTH2_ACCESS_TOKEN[] = "access_token";
const char OAUTH2_REFRESH_TOKEN[] = "refresh_token";
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_URI_COMPLETE[] = "verification_uri_complete";
const char OAUTH2_INTERVAL[] = "interval";
// Parameter values
const char AUTHORIZATION_CODE[] = "authorization_code";
// Standard HTTP headers
const char HTTP_HTTP_HEADER[] = "HTTP";
const char HTTP_AUTHORIZATION_HEADER[] = "Authorization";
}

View File

@ -0,0 +1,233 @@
#pragma once
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QPair>
#include "Reply.h"
#include "RequestParameter.h"
#include "Bits.h"
namespace Katabasis {
class ReplyServer;
class PollServer;
/*
* FIXME: this is not as simple as it should be. it squishes 4 different grant flows into one big ball of mud
* This serves no practical purpose and simply makes the code less readable / maintainable.
*
* Therefore: Split this into the 4 different OAuth2 flows that people can use as authentication steps. Write tests/examples for all of them.
*/
/// Simple OAuth2 authenticator.
class OAuth2: public QObject
{
Q_OBJECT
public:
Q_ENUMS(GrantFlow)
public:
struct Options {
QString userAgent = QStringLiteral("Katabasis/1.0");
QString redirectionUrl = QStringLiteral("http://localhost:%1");
QString responseType = QStringLiteral("code");
QString scope;
QString clientIdentifier;
QString clientSecret;
QUrl authorizationUrl;
QUrl accessTokenUrl;
QVector<quint16> listenerPorts = { 0 };
};
/// Authorization flow types.
enum GrantFlow {
GrantFlowAuthorizationCode, ///< @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
GrantFlowImplicit, ///< @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.2
GrantFlowResourceOwnerPasswordCredentials,
GrantFlowDevice ///< @see https://tools.ietf.org/html/rfc8628#section-1
};
/// Authorization flow.
GrantFlow grantFlow();
void setGrantFlow(GrantFlow value);
public:
/// Are we authenticated?
bool linked();
/// Authentication token.
QString token();
/// Provider-specific extra tokens, available after a successful authentication
QVariantMap extraTokens();
/// Page content on local host after successful oauth.
/// Provide it in case you do not want to close the browser, but display something
QByteArray replyContent() const;
void setReplyContent(const QByteArray &value);
public:
// TODO: remove
/// Resource owner username.
/// instances with the same (username, password) share the same "linked" and "token" properties.
QString username();
void setUsername(const QString &value);
// TODO: remove
/// Resource owner password.
/// instances with the same (username, password) share the same "linked" and "token" properties.
QString password();
void setPassword(const QString &value);
// TODO: remove
/// API key.
QString apiKey();
void setApiKey(const QString &value);
// TODO: remove
/// Allow ignoring SSL errors?
/// E.g. SurveyMonkey fails on Mac due to SSL error. Ignoring the error circumvents the problem
bool ignoreSslErrors();
void setIgnoreSslErrors(bool ignoreSslErrors);
// TODO: put in `Options`
/// User-defined extra parameters to append to request URL
QVariantMap extraRequestParams();
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);
public:
/// Constructor.
/// @param parent Parent object.
explicit OAuth2(Options & opts, Token & token, QObject *parent = 0, QNetworkAccessManager *manager = 0);
/// Get refresh token.
QString refreshToken();
/// Get token expiration time
QDateTime expires();
public slots:
/// Authenticate.
virtual void link();
/// De-authenticate.
virtual void unlink();
/// Refresh token.
bool refresh();
/// Handle situation where reply server has opted to close its connection
void serverHasClosed(bool paramsfound = false);
signals:
/// Emitted when a token refresh has been completed or failed.
void refreshFinished(QNetworkReply::NetworkError error);
/// Emitted when client needs to open a web browser window, with the given 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);
/// Emitted when authentication/deauthentication succeeded.
void linkingSucceeded();
/// Emitted when authentication/deauthentication failed.
void linkingFailed();
void activityChanged(Activity activity);
public slots:
/// Handle verification response.
virtual void onVerificationReceived(QMap<QString, QString>);
protected slots:
/// Handle completion of a token request.
virtual void onTokenReplyFinished();
/// Handle failure of a token request.
virtual void onTokenReplyError(QNetworkReply::NetworkError error);
/// Handle completion of a refresh request.
virtual void onRefreshFinished();
/// Handle failure of a refresh request.
virtual void onRefreshError(QNetworkReply::NetworkError error);
/// Handle completion of a Device Authorization Request
virtual void onDeviceAuthReplyFinished();
protected:
/// Build HTTP request body.
QByteArray buildRequestBody(const QMap<QString, QString> &parameters);
/// Set refresh token.
void setRefreshToken(const QString &v);
/// Set token expiration time.
void setExpires(QDateTime v);
/// Start polling authorization server
void startPollServer(const QVariantMap &params);
/// Set authentication token.
void setToken(const QString &v);
/// Set the linked state
void setLinked(bool v);
/// Set extra tokens found in OAuth response
void setExtraTokens(QVariantMap extraTokens);
/// Set local reply server
void setReplyServer(ReplyServer *server);
ReplyServer * replyServer() const;
/// Set local poll server
void setPollServer(PollServer *server);
PollServer * pollServer() const;
void updateActivity(Activity activity);
protected:
QString username_;
QString password_;
Options options_;
QVariantMap extraReqParams_;
QString apiKey_;
QNetworkAccessManager *manager_ = nullptr;
ReplyList timedReplies_;
GrantFlow grantFlow_;
QString grantType_;
protected:
QString redirectUri_;
Token &token_;
// this should be part of the reply server impl
QByteArray replyContent_;
private:
ReplyServer *replyServer_ = nullptr;
PollServer *pollServer_ = nullptr;
Activity activity_ = Activity::Idle;
};
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <QByteArray>
#include <QMap>
#include <QNetworkRequest>
#include <QObject>
#include <QString>
#include <QTimer>
class QNetworkAccessManager;
namespace Katabasis {
/// Poll an authorization server for token
class PollServer : public QObject
{
Q_OBJECT
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:
void verificationReceived(QMap<QString, QString>);
void serverClosed(bool); // whether it has found parameters
public slots:
void startPolling();
protected slots:
void onPollTimeout();
void onExpiration();
void onReplyFinished();
protected:
QNetworkAccessManager *manager_;
const QNetworkRequest request_;
const QByteArray payload_;
const int expiresIn_;
QTimer expirationTimer;
QTimer pollTimer;
};
}

View File

@ -0,0 +1,60 @@
#pragma once
#include <QList>
#include <QTimer>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QByteArray>
namespace Katabasis {
/// A network request/reply pair that can time out.
class Reply: public QTimer {
Q_OBJECT
public:
Reply(QNetworkReply *reply, int timeOut = 60 * 1000, QObject *parent = 0);
signals:
void error(QNetworkReply::NetworkError);
public slots:
/// When time out occurs, the QNetworkReply's error() signal is triggered.
void onTimeOut();
public:
QNetworkReply *reply;
};
/// List of O2Replies.
class ReplyList {
public:
ReplyList() { ignoreSslErrors_ = false; }
/// Destructor.
/// Deletes all O2Reply instances in the list.
virtual ~ReplyList();
/// Create a new O2Reply from a QNetworkReply, and add it to this list.
void add(QNetworkReply *reply);
/// Add an O2Reply to the list, while taking ownership of it.
void add(Reply *reply);
/// Remove item from the list that corresponds to a QNetworkReply.
void remove(QNetworkReply *reply);
/// Find an O2Reply in the list, corresponding to a QNetworkReply.
/// @return Matching O2Reply or NULL.
Reply *find(QNetworkReply *reply);
bool ignoreSslErrors();
void setIgnoreSslErrors(bool ignoreSslErrors);
protected:
QList<Reply *> replies_;
bool ignoreSslErrors_;
};
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <QTcpServer>
#include <QMap>
#include <QByteArray>
#include <QString>
namespace Katabasis {
/// HTTP server to process authentication response.
class ReplyServer: public QTcpServer {
Q_OBJECT
public:
explicit ReplyServer(QObject *parent = 0);
/// Page content on local host after successful oauth - in case you do not want to close the browser, but display something
Q_PROPERTY(QByteArray replyContent READ replyContent WRITE setReplyContent)
QByteArray replyContent();
void setReplyContent(const QByteArray &value);
/// Seconds to keep listening *after* first response for a callback with token content
Q_PROPERTY(int timeout READ timeout WRITE setTimeout)
int timeout();
void setTimeout(int timeout);
/// Maximum number of callback tries to accept, in case some don't have token content (favicons, etc.)
Q_PROPERTY(int callbackTries READ callbackTries WRITE setCallbackTries)
int callbackTries();
void setCallbackTries(int maxtries);
QString uniqueState();
void setUniqueState(const QString &state);
signals:
void verificationReceived(QMap<QString, QString>);
void serverClosed(bool); // whether it has found parameters
public slots:
void onIncomingConnection();
void onBytesReady();
QMap<QString, QString> parseQueryParams(QByteArray *data);
void closeServer(QTcpSocket *socket = 0, bool hasparameters = false);
protected:
QByteArray replyContent_;
int timeout_;
int maxtries_;
int tries_;
QString uniqueState_;
};
}

View File

@ -0,0 +1,15 @@
#pragma once
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);
}
QByteArray name;
QByteArray value;
};
}

View File

@ -0,0 +1,116 @@
#pragma once
#include <QObject>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QByteArray>
#include <QHttpMultiPart>
#include "Reply.h"
namespace Katabasis {
class OAuth2;
/// Makes authenticated requests.
class Requestor: public QObject {
Q_OBJECT
public:
explicit Requestor(QNetworkAccessManager *manager, OAuth2 *authenticator, QObject *parent = 0);
~Requestor();
/// Some services require the access token to be sent as a Authentication HTTP header
/// and refuse requests with the access token in the query.
/// This function allows to use or ignore the access token in the query.
/// The default value of `true` means that the query will contain the access token.
/// By setting the value to false, the query will not contain the access token.
/// See:
/// https://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-4.3
/// https://tools.ietf.org/html/rfc6750#section-2.3
void setAddAccessTokenInQuery(bool value);
/// Some services require the access token to be sent as a Authentication HTTP header.
/// This is the case for Twitch and Mixer.
/// When the access token expires and is refreshed, O2Requestor::retry() needs to update the Authentication HTTP header.
/// In order to do so, O2Requestor needs to know the format of the Authentication HTTP header.
void setAccessTokenInAuthenticationHTTPHeaderFormat(const QString &value);
public slots:
/// Make a GET request.
/// @return Request ID or -1 if there are too many requests in the queue.
int get(const QNetworkRequest &req, int timeout = 60*1000);
/// Make a POST request.
/// @return Request ID or -1 if there are too many requests in the queue.
int post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
int post(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000);
/// Make a PUT request.
/// @return Request ID or -1 if there are too many requests in the queue.
int put(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
int put(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000);
/// Make a HEAD request.
/// @return Request ID or -1 if there are too many requests in the queue.
int head(const QNetworkRequest &req, int timeout = 60*1000);
/// Make a custom request.
/// @return Request ID or -1 if there are too many requests in the queue.
int customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, int timeout = 60*1000);
signals:
/// Emitted when a request has been completed or failed.
void finished(int id, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
/// Emitted when an upload has progressed.
void uploadProgress(int id, qint64 bytesSent, qint64 bytesTotal);
protected slots:
/// Handle refresh completion.
void onRefreshFinished(QNetworkReply::NetworkError error);
/// Handle request finished.
void onRequestFinished();
/// Handle request error.
void onRequestError(QNetworkReply::NetworkError error);
/// Re-try request (after successful token refresh).
void retry();
/// Finish the request, emit finished() signal.
void finish();
/// Handle upload progress.
void onUploadProgress(qint64 uploaded, qint64 total);
protected:
int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
enum Status {
Idle, Requesting, ReRequesting
};
QNetworkAccessManager *manager_;
OAuth2 *authenticator_;
QNetworkRequest request_;
QByteArray data_;
QHttpMultiPart* multipartData_;
QNetworkReply *reply_;
Status status_;
int id_;
QNetworkAccessManager::Operation operation_;
QUrl url_;
ReplyList timedReplies_;
QNetworkReply::NetworkError error_;
bool addAccessTokenInQuery_;
QString accessTokenInAuthenticationHTTPHeaderFormat_;
bool rawData_;
};
}