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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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> ¶meters);
/// Set refresh token.
void setRefreshToken(const QString &v);
/// Set token expiration time.
void setExpires(QDateTime v);
/// Start polling authorization server
void startPollServer(const QVariantMap ¶ms);
/// 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;
};
}
|