1 /*
2 xoauth2provider.cpp - X-OAuth2 provider for QCA
3
4 Copyright (c) 2016 by Pali Rohár <pali.rohar@gmail.com>
5
6 *************************************************************************
7 * *
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2 of the License, or (at your option) any later version. *
12 * *
13 *************************************************************************
14 */
15
16 #include "xoauth2provider.h"
17
18 #include <QNetworkAccessManager>
19 #include <QNetworkRequest>
20 #include <QNetworkReply>
21 #include <qca.h>
22
23 #include <QJsonDocument>
24 #include <QUrlQuery>
25
26 class XOAuth2SASLContext : public QCA::SASLContext
27 {
28 Q_OBJECT
29
30 private:
31 QString user;
32 QString clientId;
33 QString requestUrl;
34 QCA::SecureArray clientSecretKey;
35 QCA::SecureArray refreshToken;
36 QCA::SecureArray accessToken;
37
38 QByteArray data;
39 QByteArray result_to_net;
40 QByteArray result_to_app;
41 QCA::SASLContext::Result result_;
42 QCA::SASL::AuthCondition authCondition_;
43
44 QNetworkAccessManager *manager;
45
46 public:
XOAuth2SASLContext(QCA::Provider * p)47 XOAuth2SASLContext(QCA::Provider *p) : QCA::SASLContext(p)
48 {
49 manager = new QNetworkAccessManager(this);
50 reset();
51 }
52
~XOAuth2SASLContext()53 virtual ~XOAuth2SASLContext()
54 {
55 reset();
56 }
57
clone() const58 QCA::Provider::Context *clone() const Q_DECL_OVERRIDE
59 {
60 XOAuth2SASLContext *s = new XOAuth2SASLContext(provider());
61 s->user = user;
62 s->clientId = clientId;
63 s->clientSecretKey = clientSecretKey;
64 s->refreshToken = refreshToken;
65 s->accessToken = accessToken;
66 s->requestUrl = requestUrl;
67 return s;
68 }
69
reset()70 void reset() Q_DECL_OVERRIDE
71 {
72 user.clear();
73 clientId.clear();
74 clientSecretKey.clear();
75 refreshToken.clear();
76 accessToken.clear();
77 requestUrl.clear();
78 data.clear();
79 authCondition_ = QCA::SASL::AuthFail;
80 }
81
setup(const QString &,const QString &,const QCA::SASLContext::HostPort *,const QCA::SASLContext::HostPort *,const QString &,int)82 void setup(const QString &, const QString &, const QCA::SASLContext::HostPort *, const QCA::SASLContext::HostPort *, const QString &, int) Q_DECL_OVERRIDE
83 {
84 }
85
setConstraints(QCA::SASL::AuthFlags,int,int)86 void setConstraints(QCA::SASL::AuthFlags, int, int) Q_DECL_OVERRIDE
87 {
88 }
89
startClient(const QStringList & mechlist,bool)90 void startClient(const QStringList &mechlist, bool) Q_DECL_OVERRIDE
91 {
92 if (!mechlist.contains(QStringLiteral("X-OAUTH2"))) {
93 qWarning("No X-OAUTH2 auth method");
94 authCondition_ = QCA::SASL::NoMechanism;
95 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
96 return;
97 }
98 authCondition_ = QCA::SASL::AuthFail;
99 result_ = QCA::SASLContext::Continue;
100 data.clear();
101 tryAgain();
102 }
103
startServer(const QString &,bool)104 void startServer(const QString &, bool) Q_DECL_OVERRIDE
105 {
106 result_ = QCA::SASLContext::Error;
107 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
108 }
109
serverFirstStep(const QString &,const QByteArray *)110 void serverFirstStep(const QString &, const QByteArray *) Q_DECL_OVERRIDE
111 {
112 result_ = QCA::SASLContext::Error;
113 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
114 }
115
nextStep(const QByteArray &)116 void nextStep(const QByteArray &) Q_DECL_OVERRIDE
117 {
118 tryAgain();
119 }
120
tryAgain()121 void tryAgain() Q_DECL_OVERRIDE
122 {
123 if (user.isEmpty() || (accessToken.isEmpty() && (clientId.isEmpty() || clientSecretKey.isEmpty() || requestUrl.isEmpty() || refreshToken.isEmpty()))) {
124 result_ = QCA::SASLContext::Params;
125 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
126 return;
127 }
128 if (accessToken.isEmpty()) {
129 requestAccessToken();
130 return;
131 }
132 sendAuth();
133 }
134
update(const QByteArray & from_net,const QByteArray & from_app)135 void update(const QByteArray &from_net, const QByteArray &from_app) Q_DECL_OVERRIDE
136 {
137 result_to_app = from_net;
138 result_to_net = from_app;
139 result_ = QCA::SASLContext::Success;
140 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
141 }
142
waitForResultsReady(int)143 bool waitForResultsReady(int) Q_DECL_OVERRIDE
144 {
145 return true;
146 }
147
result() const148 QCA::SASLContext::Result result() const Q_DECL_OVERRIDE
149 {
150 return result_;
151 }
152
mechlist() const153 QStringList mechlist() const Q_DECL_OVERRIDE
154 {
155 return QStringList();
156 }
157
mech() const158 QString mech() const Q_DECL_OVERRIDE
159 {
160 return QStringLiteral("X-OAUTH2");
161 }
162
haveClientInit() const163 bool haveClientInit() const Q_DECL_OVERRIDE
164 {
165 return false;
166 }
167
stepData() const168 QByteArray stepData() const Q_DECL_OVERRIDE
169 {
170 return data;
171 }
172
to_net()173 QByteArray to_net() Q_DECL_OVERRIDE
174 {
175 return result_to_net;
176 }
177
encoded() const178 int encoded() const Q_DECL_OVERRIDE
179 {
180 return result_to_net.size();
181 }
182
to_app()183 QByteArray to_app() Q_DECL_OVERRIDE
184 {
185 return result_to_app;
186 }
187
ssf() const188 int ssf() const Q_DECL_OVERRIDE
189 {
190 return 0;
191 }
192
authCondition() const193 QCA::SASL::AuthCondition authCondition() const Q_DECL_OVERRIDE
194 {
195 return authCondition_;
196 }
197
clientParams() const198 QCA::SASL::Params clientParams() const Q_DECL_OVERRIDE
199 {
200 bool needUser = user.isEmpty();
201 bool needPass = (accessToken.isEmpty() && (clientId.isEmpty() || clientSecretKey.isEmpty() || requestUrl.isEmpty() || refreshToken.isEmpty()));
202 return QCA::SASL::Params(needUser, false, needPass, false);
203 }
204
setClientParams(const QString * userParam,const QString *,const QCA::SecureArray * passParam,const QString *)205 void setClientParams(const QString *userParam, const QString *, const QCA::SecureArray *passParam, const QString *) Q_DECL_OVERRIDE
206 {
207 if (userParam) {
208 user = *userParam;
209 }
210 if (passParam) {
211 const QList<QByteArray> ¶ms = passParam->toByteArray().split(0x7F);
212 if (params.size() == 5) {
213 clientId = QString::fromUtf8(params.at(0));
214 clientSecretKey = params.at(1);
215 refreshToken = params.at(2);
216 accessToken = params.at(3);
217 requestUrl = QString::fromUtf8(params.at(4));
218 } else {
219 clientId.clear();
220 clientSecretKey.clear();
221 refreshToken.clear();
222 requestUrl.clear();
223 if (params.size() == 1) {
224 accessToken = params.at(0);
225 } else {
226 accessToken.clear();
227 }
228 }
229 }
230 }
231
realmlist() const232 QStringList realmlist() const Q_DECL_OVERRIDE
233 {
234 return QStringList();
235 }
236
username() const237 QString username() const Q_DECL_OVERRIDE
238 {
239 return QString();
240 }
241
authzid() const242 QString authzid() const Q_DECL_OVERRIDE
243 {
244 return QString();
245 }
246
247 private:
requestAccessToken()248 void requestAccessToken()
249 {
250 QUrlQuery query;
251 query.addQueryItem(QStringLiteral("client_id"), clientId);
252 query.addQueryItem(QStringLiteral("client_secret"), QString::fromUtf8(clientSecretKey.toByteArray()));
253 query.addQueryItem(QStringLiteral("refresh_token"), QString::fromUtf8(refreshToken.toByteArray()));
254 query.addQueryItem(QStringLiteral("grant_type"), QStringLiteral("refresh_token"));
255 const QByteArray &data = query.toString(QUrl::FullyEncoded).toUtf8();
256 QNetworkRequest request(requestUrl);
257 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
258 QNetworkReply *reply = manager->post(request, data);
259 connect(reply, SIGNAL(finished()), this, SLOT(accessTokenReceived()));
260 }
261
sendAuth()262 void sendAuth()
263 {
264 if (accessToken.isEmpty()) {
265 authCondition_ = QCA::SASL::AuthFail;
266 result_ = QCA::SASLContext::Error;
267 } else {
268 data.clear();
269 data += '\0';
270 data += user.toUtf8();
271 data += '\0';
272 data += accessToken.toByteArray();
273 result_ = QCA::SASLContext::Success;
274 }
275 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
276 }
277
278 private Q_SLOTS:
accessTokenReceived()279 void accessTokenReceived()
280 {
281 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
282 const QByteArray &replyData = reply->readAll();
283 reply->deleteLater();
284 QJsonParseError error;
285 const QJsonDocument &replyJson = QJsonDocument::fromJson(replyData, &error);
286 bool ok = (error.error == QJsonParseError::NoError);
287 const QVariant &replyVariant = replyJson.toVariant();
288 if (ok && replyVariant.type() == QVariant::Map) {
289 const QVariantMap &replyMap = replyVariant.toMap();
290 if (replyMap.contains(QStringLiteral("access_token"))) {
291 const QVariant &accessTokenVariant = replyMap.value(QStringLiteral("access_token"));
292 if (accessTokenVariant.type() == QVariant::String) {
293 accessToken = accessTokenVariant.toString().toUtf8();
294 } else {
295 ok = false;
296 }
297 } else {
298 ok = false;
299 }
300 if (!ok || replyMap.contains(QStringLiteral("error"))) {
301 const QString &error = replyMap.value(QStringLiteral("error")).toString();
302 const QString &errorDescription = replyMap.value(QStringLiteral("error_description")).toString();
303 qWarning("requestAccessToken failed, error: %s, description: %s", error.toUtf8().data(), errorDescription.toUtf8().data());
304 accessToken.clear();
305 }
306 } else {
307 qWarning("requestAccessToken failed, invalid reply: %s", replyData.data());
308 accessToken.clear();
309 }
310 sendAuth();
311 }
312 };
313
314 class QCAXOAuth2SASL : public QCA::Provider
315 {
316 public:
QCAXOAuth2SASL()317 QCAXOAuth2SASL()
318 {
319 }
320
~QCAXOAuth2SASL()321 virtual ~QCAXOAuth2SASL()
322 {
323 }
324
init()325 void init() Q_DECL_OVERRIDE
326 {
327 }
328
qcaVersion() const329 int qcaVersion() const Q_DECL_OVERRIDE
330 {
331 return QCA_VERSION;
332 }
333
name() const334 QString name() const Q_DECL_OVERRIDE
335 {
336 return QStringLiteral("xoauth2sasl");
337 }
338
features() const339 QStringList features() const Q_DECL_OVERRIDE
340 {
341 return QStringList(QStringLiteral("sasl"));
342 }
343
createContext(const QString & type)344 QCA::Provider::Context *createContext(const QString &type) Q_DECL_OVERRIDE
345 {
346 if (type == QLatin1String("sasl")) {
347 return new XOAuth2SASLContext(this);
348 }
349 return NULL;
350 }
351 };
352
createProviderXOAuth2()353 QCA::Provider *createProviderXOAuth2()
354 {
355 return new QCAXOAuth2SASL();
356 }
357
358 #include "xoauth2provider.moc"
359