Merge lp:~mardy/ubuntuone-credentials/default-token-name into lp:ubuntuone-credentials
- default-token-name
- Merge into trunk
Proposed by
Alberto Mardegan
Status: | Superseded |
---|---|
Proposed branch: | lp:~mardy/ubuntuone-credentials/default-token-name |
Merge into: | lp:ubuntuone-credentials |
Diff against target: |
1403 lines (+1071/-53) 15 files modified
CMakeLists.txt (+3/-3) debian/control (+3/-2) debian/libubuntuoneauth-2.0-0.symbols (+4/-0) debian/signon-plugin-ubuntuone.install (+1/-1) libubuntuoneauth/CMakeLists.txt (+13/-3) libubuntuoneauth/tests/test_token.cpp (+7/-0) libubuntuoneauth/tests/test_token.h (+1/-0) libubuntuoneauth/token.cpp (+58/-11) libubuntuoneauth/token.h (+4/-0) signon-plugin/CMakeLists.txt (+14/-4) signon-plugin/tests/CMakeLists.txt (+51/-0) signon-plugin/tests/test_plugin.cpp (+550/-0) signon-plugin/ubuntuone-plugin.cpp (+297/-19) signon-plugin/ubuntuone-plugin.h (+41/-7) signon-plugin/ubuntuonedata.h (+24/-3) |
To merge this branch: | bzr merge lp:~mardy/ubuntuone-credentials/default-token-name |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Unity API Team | Pending | ||
Review via email: mp+293233@code.launchpad.net |
Commit message
If no token name is given, use a default one
Description of the change
If no token name is given, use a default one
If we don't do something like this, then we need to accept this:
https:/
(which would be my recommendation, actually).
To post a comment you must log in.
- 241. By Launchpad Translations on behalf of ubuntuone-control-tower
-
Launchpad automatic translations update.
- 242. By dobey
-
Remove the superfluous validateInput method.
Use the same taken name always.
Store the token name as part of the token data. - 243. By Alberto Mardegan
-
Fix stored token response
Unmerged revisions
- 243. By Alberto Mardegan
-
Fix stored token response
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2015-05-11 13:44:59 +0000 |
3 | +++ CMakeLists.txt 2016-04-28 10:10:18 +0000 |
4 | @@ -59,13 +59,13 @@ |
5 | # We use $namespace-tests[-valgrind[-leaks]] for the test names in subdirs, |
6 | # and depend on them here, so that 'make check' will run them all. |
7 | add_custom_target(check |
8 | - DEPENDS ubuntuoneauth-tests |
9 | + DEPENDS ubuntuoneauth-tests ubuntuone-plugin-tests |
10 | ) |
11 | |
12 | add_custom_target(check-valgrind |
13 | - DEPENDS ubuntuoneauth-tests-valgrind |
14 | + DEPENDS ubuntuoneauth-tests-valgrind ubuntuone-plugin-tests-valgrind |
15 | ) |
16 | |
17 | add_custom_target(check-valgrind-leaks |
18 | - DEPENDS ubuntuoneauth-tests-valgrind-leaks |
19 | + DEPENDS ubuntuoneauth-tests-valgrind-leaks ubuntuone-plugin-tests-valgrind-leaks |
20 | ) |
21 | |
22 | === modified file 'debian/control' |
23 | --- debian/control 2015-11-16 20:17:01 +0000 |
24 | +++ debian/control 2016-04-28 10:10:18 +0000 |
25 | @@ -6,6 +6,7 @@ |
26 | debhelper (>= 9), |
27 | dh-migrations, |
28 | libaccounts-qt5-dev, |
29 | + libglib2.0-dev, |
30 | liboauth-dev, |
31 | libsignon-qt5-dev, |
32 | pkg-config, |
33 | @@ -14,7 +15,7 @@ |
34 | qtdeclarative5-dev-tools, |
35 | qtdeclarative5-ubuntu-ui-toolkit-plugin, |
36 | python3-all:native, |
37 | - signon-plugins-dev, |
38 | + signon-plugins-dev (>= 8.58), |
39 | ubuntu-ui-toolkit-autopilot:native, |
40 | xvfb, |
41 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
42 | @@ -135,7 +136,7 @@ |
43 | |
44 | Package: signon-plugin-ubuntuone |
45 | Architecture: any |
46 | -Multi-Arch: foreign |
47 | +Multi-Arch: same |
48 | Pre-Depends: |
49 | multiarch-support, |
50 | ${misc:Pre-Depends}, |
51 | |
52 | === modified file 'debian/libubuntuoneauth-2.0-0.symbols' |
53 | --- debian/libubuntuoneauth-2.0-0.symbols 2015-12-07 21:38:01 +0000 |
54 | +++ debian/libubuntuoneauth-2.0-0.symbols 2016-04-28 10:10:18 +0000 |
55 | @@ -111,7 +111,11 @@ |
56 | (c++)"UbuntuOne::Token::Token(QString, QString, QString, QString)@Base" 13.08 |
57 | (c++)"UbuntuOne::Token::Token(QString, QString, QString, QString, QString, QString)@Base" 14.04+14.10.20140908 |
58 | (c++)"UbuntuOne::Token::~Token()@Base" 13.08 |
59 | + (c++)"UbuntuOne::Token::name() const@Base" 15.11+16.04.20151207.1 |
60 | (c++)"UbuntuOne::Token::consumerKey() const@Base" 15.10+15.10.20150617 |
61 | + (c++)"UbuntuOne::Token::consumerSecret() const@Base" 15.11+16.04.20151207.1 |
62 | + (c++)"UbuntuOne::Token::tokenKey() const@Base" 15.11+16.04.20151207.1 |
63 | + (c++)"UbuntuOne::Token::tokenSecret() const@Base" 15.11+16.04.20151207.1 |
64 | (c++)"UbuntuOne::Token::created() const@Base" 14.04+14.10.20140818 |
65 | (c++)"UbuntuOne::Token::updated() const@Base" 14.04+14.10.20140818 |
66 | (c++)"UbuntuOne::Token::getServerTimestamp() const@Base" 15.11+16.04.20151207.1 |
67 | |
68 | === modified file 'debian/signon-plugin-ubuntuone.install' |
69 | --- debian/signon-plugin-ubuntuone.install 2013-11-27 21:19:44 +0000 |
70 | +++ debian/signon-plugin-ubuntuone.install 2016-04-28 10:10:18 +0000 |
71 | @@ -1,1 +1,1 @@ |
72 | -usr/lib/signon/*.so |
73 | +usr/lib/*/signon/*.so |
74 | |
75 | === modified file 'libubuntuoneauth/CMakeLists.txt' |
76 | --- libubuntuoneauth/CMakeLists.txt 2013-11-22 19:17:04 +0000 |
77 | +++ libubuntuoneauth/CMakeLists.txt 2016-04-28 10:10:18 +0000 |
78 | @@ -16,7 +16,17 @@ |
79 | # The sources for building the library |
80 | FILE (GLOB SOURCES *.cpp) |
81 | # HEADERS only includes the public headers for installation. |
82 | -FILE (GLOB HEADERS *.h) |
83 | +FILE (GLOB HEADERS |
84 | + errormessages.h |
85 | + identityprovider.h |
86 | + keyring.h |
87 | + logging.h |
88 | + network.h |
89 | + requests.h |
90 | + responses.h |
91 | + ssoservice.h |
92 | + token.h |
93 | +) |
94 | |
95 | pkg_check_modules(OAUTH REQUIRED oauth) |
96 | add_definitions(${OAUTH_CFLAGS} ${OAUTH_CFLAGS_OTHER}) |
97 | @@ -29,12 +39,12 @@ |
98 | target_link_libraries (${AUTH_LIB_NAME} |
99 | ${LIBSIGNON_LDFLAGS} |
100 | ${OAUTH_LDFLAGS} |
101 | - -Wl,--version-script -Wl,${CMAKE_CURRENT_SOURCE_DIR}/libubuntuoneauth.symbols |
102 | ) |
103 | |
104 | SET_TARGET_PROPERTIES(${AUTH_LIB_NAME} PROPERTIES |
105 | VERSION ${AUTH_LIB_VERSION} |
106 | SOVERSION ${AUTH_LIB_SOVERSION} |
107 | + LINK_FLAGS "-Wl,--version-script -Wl,\"${CMAKE_CURRENT_SOURCE_DIR}/libubuntuoneauth.symbols\"" |
108 | ) |
109 | |
110 | INSTALL ( |
111 | @@ -64,4 +74,4 @@ |
112 | ) |
113 | |
114 | add_subdirectory(tests) |
115 | -add_subdirectory(examples) |
116 | \ No newline at end of file |
117 | +add_subdirectory(examples) |
118 | |
119 | === modified file 'libubuntuoneauth/tests/test_token.cpp' |
120 | --- libubuntuoneauth/tests/test_token.cpp 2015-12-07 21:17:00 +0000 |
121 | +++ libubuntuoneauth/tests/test_token.cpp 2016-04-28 10:10:18 +0000 |
122 | @@ -46,6 +46,13 @@ |
123 | delete token; |
124 | } |
125 | |
126 | +void TestToken::testTokenEmptyStrings() |
127 | +{ |
128 | + Token *token = new Token(QString(), QString(), QString(), QString()); |
129 | + QVERIFY(!token->isValid()); |
130 | + delete token; |
131 | +} |
132 | + |
133 | void TestToken::testTokenArgs() |
134 | { |
135 | Token *token = new Token("a", "b", "c", "d"); |
136 | |
137 | === modified file 'libubuntuoneauth/tests/test_token.h' |
138 | --- libubuntuoneauth/tests/test_token.h 2015-12-07 21:17:00 +0000 |
139 | +++ libubuntuoneauth/tests/test_token.h 2016-04-28 10:10:18 +0000 |
140 | @@ -39,6 +39,7 @@ |
141 | void cleanupTestCase(); |
142 | |
143 | void testEmptyToken(); |
144 | + void testTokenEmptyStrings(); |
145 | void testTokenArgs(); |
146 | void testTokenCopy(); |
147 | |
148 | |
149 | === modified file 'libubuntuoneauth/token.cpp' |
150 | --- libubuntuoneauth/token.cpp 2015-12-07 21:17:00 +0000 |
151 | +++ libubuntuoneauth/token.cpp 2016-04-28 10:10:18 +0000 |
152 | @@ -46,21 +46,25 @@ |
153 | QString consumer_key, QString consumer_secret) |
154 | { |
155 | _tokenHash[TOKEN_NAME_KEY] = buildTokenName(); |
156 | - _tokenHash[TOKEN_TOKEN_KEY] = token_key; |
157 | - _tokenHash[TOKEN_TOKEN_SEC_KEY] = token_secret; |
158 | - _tokenHash[TOKEN_CONSUMER_KEY] = consumer_key; |
159 | - _tokenHash[TOKEN_CONSUMER_SEC_KEY] = consumer_secret; |
160 | + if (!token_key.isEmpty()) { |
161 | + _tokenHash[TOKEN_TOKEN_KEY] = token_key; |
162 | + } |
163 | + if (!token_secret.isEmpty()) { |
164 | + _tokenHash[TOKEN_TOKEN_SEC_KEY] = token_secret; |
165 | + } |
166 | + if (!consumer_key.isEmpty()) { |
167 | + _tokenHash[TOKEN_CONSUMER_KEY] = consumer_key; |
168 | + } |
169 | + if (!consumer_secret.isEmpty()) { |
170 | + _tokenHash[TOKEN_CONSUMER_SEC_KEY] = consumer_secret; |
171 | + } |
172 | } |
173 | |
174 | Token::Token(QString token_key, QString token_secret, |
175 | QString consumer_key, QString consumer_secret, |
176 | - QString created_date, QString updated_date) |
177 | + QString created_date, QString updated_date): |
178 | + Token(token_key, token_secret, consumer_key, consumer_secret) |
179 | { |
180 | - _tokenHash[TOKEN_NAME_KEY] = buildTokenName(); |
181 | - _tokenHash[TOKEN_TOKEN_KEY] = token_key; |
182 | - _tokenHash[TOKEN_TOKEN_SEC_KEY] = token_secret; |
183 | - _tokenHash[TOKEN_CONSUMER_KEY] = consumer_key; |
184 | - _tokenHash[TOKEN_CONSUMER_SEC_KEY] = consumer_secret; |
185 | _tokenHash[TOKEN_CREATED_KEY] = dateStringToISO(created_date); |
186 | _tokenHash[TOKEN_UPDATED_KEY] = dateStringToISO(updated_date); |
187 | } |
188 | @@ -93,9 +97,19 @@ |
189 | } |
190 | |
191 | /** |
192 | + * \fn QString Token::name() |
193 | + * |
194 | + * Returns the token name, or empty string if not set. |
195 | + */ |
196 | + QString Token::name() const |
197 | + { |
198 | + return _tokenHash.value(TOKEN_NAME_KEY, ""); |
199 | + } |
200 | + |
201 | + /** |
202 | * \fn QString Token::consumerKey() |
203 | * |
204 | - * Retruns a consumer key for this token, or empty string if consumer key is not set. |
205 | + * Returns a consumer key for this token, or empty string if consumer key is not set. |
206 | */ |
207 | QString Token::consumerKey() const |
208 | { |
209 | @@ -103,6 +117,36 @@ |
210 | } |
211 | |
212 | /** |
213 | + * \fn QString Token::consumerSecret() |
214 | + * |
215 | + * Returns a consumer secret for this token, or empty string if not set. |
216 | + */ |
217 | + QString Token::consumerSecret() const |
218 | + { |
219 | + return _tokenHash.value(TOKEN_CONSUMER_SEC_KEY, ""); |
220 | + } |
221 | + |
222 | + /** |
223 | + * \fn QString Token::tokenKey() |
224 | + * |
225 | + * Returns a token key for this token, or empty string if token key is not set. |
226 | + */ |
227 | + QString Token::tokenKey() const |
228 | + { |
229 | + return _tokenHash.value(TOKEN_TOKEN_KEY, ""); |
230 | + } |
231 | + |
232 | + /** |
233 | + * \fn QString Token::tokenSecret() |
234 | + * |
235 | + * Returns a token secret for this token, or empty string if not set. |
236 | + */ |
237 | + QString Token::tokenSecret() const |
238 | + { |
239 | + return _tokenHash.value(TOKEN_TOKEN_SEC_KEY, ""); |
240 | + } |
241 | + |
242 | + /** |
243 | * \fn bool Token::isValid() |
244 | * |
245 | * Check that the token is valid. |
246 | @@ -291,6 +335,9 @@ |
247 | QStringList params = query.split("&"); |
248 | for (int i = 0; i < params.size(); ++i) { |
249 | QStringList pair = params.at(i).split("="); |
250 | + if (pair.count() < 2) { |
251 | + continue; |
252 | + } |
253 | if (pair.at(0) == TOKEN_NAME_KEY) { |
254 | // TODO: Need to figure out how to actually use the |
255 | // QUrl::fromPercentEncoding at this point in the code. |
256 | |
257 | === modified file 'libubuntuoneauth/token.h' |
258 | --- libubuntuoneauth/token.h 2015-12-07 21:17:00 +0000 |
259 | +++ libubuntuoneauth/token.h 2016-04-28 10:10:18 +0000 |
260 | @@ -55,7 +55,11 @@ |
261 | |
262 | static QString dateStringToISO(const QString date); |
263 | |
264 | + QString name() const; |
265 | QString consumerKey() const; |
266 | + QString consumerSecret() const; |
267 | + QString tokenKey() const; |
268 | + QString tokenSecret() const; |
269 | |
270 | private: |
271 | QHash<QString, QString> _tokenHash; |
272 | |
273 | === modified file 'signon-plugin/CMakeLists.txt' |
274 | --- signon-plugin/CMakeLists.txt 2014-07-24 13:30:19 +0000 |
275 | +++ signon-plugin/CMakeLists.txt 2016-04-28 10:10:18 +0000 |
276 | @@ -1,3 +1,8 @@ |
277 | +project(SignonPlugin) |
278 | + |
279 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") |
280 | +set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined") |
281 | + |
282 | # Qt5 bits |
283 | SET (CMAKE_INCLUDE_CURRENT_DIR ON) |
284 | SET (CMAKE_AUTOMOC ON) |
285 | @@ -8,14 +13,17 @@ |
286 | # HEADERS only includes the public headers for installation. |
287 | FILE (GLOB HEADERS *.h) |
288 | |
289 | -pkg_check_modules(SIGNON REQUIRED signon-plugins) |
290 | +pkg_check_modules(SIGNON REQUIRED signon-plugins glib-2.0) |
291 | add_definitions(${SIGNON_CFLAGS} ${SIGNON_CFLAGS_OTHER}) |
292 | |
293 | # Workaround for cmake not adding these to automoc properly |
294 | SET (CMAKE_AUTOMOC_MOC_OPTIONS "${SIGNON_CFLAGS} ${CMAKE_AUTOMOC_MOC_OPTIONS}") |
295 | |
296 | # Need the project version here |
297 | -add_definitions("-DPROJECT_VERSION=\"${PROJECT_VERSION}\"") |
298 | +add_definitions( |
299 | + "-DPROJECT_VERSION=\"${PROJECT_VERSION}\"" |
300 | + "-DGETTEXT_PACKAGE=\"ubuntuone-credentials\"" |
301 | +) |
302 | |
303 | add_library (${SIGNON_PLUGIN_NAME} MODULE ${SOURCES}) |
304 | qt5_use_modules (${SIGNON_PLUGIN_NAME} Core Xml Network) |
305 | @@ -24,13 +32,15 @@ |
306 | -L${CMAKE_BINARY_DIR}/libubuntuoneauth |
307 | ${AUTH_LIB_NAME} |
308 | ${SIGNON_PLUGIN_LDFLAGS} |
309 | + ${SIGNON_LDFLAGS} |
310 | + ${SIGNON_LDFLAGS_OTHER} |
311 | ) |
312 | |
313 | -SET (SIGNON_PLUGIN_INSTALL_DIR lib/signon) |
314 | +SET (SIGNON_PLUGIN_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/signon) |
315 | |
316 | INSTALL ( |
317 | TARGETS ${SIGNON_PLUGIN_NAME} |
318 | LIBRARY DESTINATION ${SIGNON_PLUGIN_INSTALL_DIR} |
319 | ) |
320 | |
321 | -#add_subdirectory(tests) |
322 | +add_subdirectory(tests) |
323 | |
324 | === added directory 'signon-plugin/tests' |
325 | === added file 'signon-plugin/tests/CMakeLists.txt' |
326 | --- signon-plugin/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
327 | +++ signon-plugin/tests/CMakeLists.txt 2016-04-28 10:10:18 +0000 |
328 | @@ -0,0 +1,51 @@ |
329 | +# The thing we're building in here |
330 | +SET (TESTS_TARGET test-ubuntuone-plugin) |
331 | + |
332 | +# Qt5 bits |
333 | +SET (CMAKE_INCLUDE_CURRENT_DIR ON) |
334 | +SET (CMAKE_AUTOMOC ON) |
335 | +find_package(Qt5Core REQUIRED) |
336 | + |
337 | +pkg_check_modules(SIGNON REQUIRED signon-plugins) |
338 | +add_definitions( |
339 | + ${SIGNON_CFLAGS} |
340 | + ${SIGNON_CFLAGS_OTHER} |
341 | + "-DPLUGIN_PATH=\"${CMAKE_CURRENT_BINARY_DIR}/../libubuntuoneplugin.so\"" |
342 | +) |
343 | + |
344 | +# Workaround for cmake not adding these to automoc properly |
345 | +SET (CMAKE_AUTOMOC_MOC_OPTIONS "${SIGNON_CFLAGS} ${CMAKE_AUTOMOC_MOC_OPTIONS}") |
346 | + |
347 | +# The sources for building the tests |
348 | +SET (SOURCES |
349 | + ${SignonPlugin_SOURCE_DIR}/ubuntuone-plugin.cpp |
350 | + test_plugin.cpp |
351 | +) |
352 | + |
353 | +include_directories( |
354 | + ${SignonPlugin_SOURCE_DIR} |
355 | +) |
356 | + |
357 | +add_executable (${TESTS_TARGET} ${SOURCES}) |
358 | +qt5_use_modules (${TESTS_TARGET} Test Network) |
359 | +target_link_libraries (${TESTS_TARGET} |
360 | + -Wl,-rpath,${CMAKE_BINARY_DIR}/libubuntuoneauth |
361 | + -L${CMAKE_BINARY_DIR}/libubuntuoneauth |
362 | + ${AUTH_LIB_NAME} |
363 | + ${SIGNON_LDFLAGS} |
364 | +) |
365 | + |
366 | +add_custom_target(ubuntuone-plugin-tests |
367 | + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET} |
368 | + DEPENDS ${TESTS_TARGET} |
369 | +) |
370 | + |
371 | +add_custom_target(ubuntuone-plugin-tests-valgrind |
372 | + COMMAND valgrind --tool=memcheck ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET} |
373 | + DEPENDS ${TESTS_TARGET} |
374 | +) |
375 | + |
376 | +add_custom_target(ubuntuone-plugin-tests-valgrind-leaks |
377 | + COMMAND valgrind --tool=memcheck --track-origins=yes --num-callers=40 --leak-resolution=high --leak-check=full ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET} |
378 | + DEPENDS ${TESTS_TARGET} |
379 | +) |
380 | |
381 | === added file 'signon-plugin/tests/test_plugin.cpp' |
382 | --- signon-plugin/tests/test_plugin.cpp 1970-01-01 00:00:00 +0000 |
383 | +++ signon-plugin/tests/test_plugin.cpp 2016-04-28 10:10:18 +0000 |
384 | @@ -0,0 +1,550 @@ |
385 | +/* |
386 | + * Copyright 2016 Canonical Ltd. |
387 | + * |
388 | + * This library is free software; you can redistribute it and/or |
389 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
390 | + * License as published by the Free Software Foundation. |
391 | + * |
392 | + * This program is distributed in the hope that it will be useful, |
393 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
394 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
395 | + * General Public License for more details. |
396 | + * |
397 | + * You should have received a copy of the GNU Lesser General Public |
398 | + * License along with this library; if not, write to the |
399 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
400 | + * Boston, MA 02110-1301, USA. |
401 | + */ |
402 | + |
403 | +#include <QJsonDocument> |
404 | +#include <QJsonObject> |
405 | +#include <QNetworkAccessManager> |
406 | +#include <QNetworkReply> |
407 | +#include <QPointer> |
408 | +#include <QRegExp> |
409 | +#include <QSignalSpy> |
410 | +#include <QTimer> |
411 | +#include <QtTest/QtTest> |
412 | + |
413 | +#include <SignOn/uisessiondata_priv.h> |
414 | + |
415 | +#include "ubuntuone-plugin.h" |
416 | + |
417 | +using namespace SignOn; |
418 | + |
419 | +namespace QTest { |
420 | +template<> |
421 | +char *toString(const QVariantMap &map) |
422 | +{ |
423 | + QJsonDocument doc(QJsonObject::fromVariantMap(map)); |
424 | + return qstrdup(doc.toJson(QJsonDocument::Compact).data()); |
425 | +} |
426 | +} // QTest namespace |
427 | + |
428 | +class TestNetworkReply: public QNetworkReply |
429 | +{ |
430 | + Q_OBJECT |
431 | + |
432 | +public: |
433 | + TestNetworkReply(QObject *parent = 0): |
434 | + QNetworkReply(parent), |
435 | + m_offset(0) |
436 | + {} |
437 | + |
438 | + void setError(NetworkError errorCode, const QString &errorString, |
439 | + int delay = -1) { |
440 | + QNetworkReply::setError(errorCode, errorString); |
441 | + if (delay > 0) { |
442 | + QTimer::singleShot(delay, this, SLOT(fail())); |
443 | + } |
444 | + } |
445 | + |
446 | + void setRawHeader(const QByteArray &headerName, const QByteArray &value) { |
447 | + QNetworkReply::setRawHeader(headerName, value); |
448 | + } |
449 | + |
450 | + void setContentType(const QString &contentType) { |
451 | + setRawHeader("Content-Type", contentType.toUtf8()); |
452 | + } |
453 | + |
454 | + void setStatusCode(int statusCode) { |
455 | + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); |
456 | + } |
457 | + |
458 | + void setContent(const QByteArray &content) { |
459 | + m_content = content; |
460 | + m_offset = 0; |
461 | + |
462 | + open(ReadOnly | Unbuffered); |
463 | + setHeader(QNetworkRequest::ContentLengthHeader, QVariant(content.size())); |
464 | + } |
465 | + |
466 | + void start() { |
467 | + QTimer::singleShot(0, this, SIGNAL(readyRead())); |
468 | + QTimer::singleShot(10, this, SLOT(finish())); |
469 | + } |
470 | + |
471 | +public Q_SLOTS: |
472 | + void finish() { setFinished(true); Q_EMIT finished(); } |
473 | + void fail() { Q_EMIT error(error()); } |
474 | + |
475 | +protected: |
476 | + void abort() Q_DECL_OVERRIDE {} |
477 | + qint64 bytesAvailable() const Q_DECL_OVERRIDE { |
478 | + return m_content.size() - m_offset + QIODevice::bytesAvailable(); |
479 | + } |
480 | + |
481 | + bool isSequential() const Q_DECL_OVERRIDE { return true; } |
482 | + qint64 readData(char *data, qint64 maxSize) Q_DECL_OVERRIDE { |
483 | + if (m_offset >= m_content.size()) { |
484 | + return -1; |
485 | + } |
486 | + qint64 number = qMin(maxSize, m_content.size() - m_offset); |
487 | + memcpy(data, m_content.constData() + m_offset, number); |
488 | + m_offset += number; |
489 | + return number; |
490 | + } |
491 | + |
492 | +private: |
493 | + QByteArray m_content; |
494 | + qint64 m_offset; |
495 | +}; |
496 | + |
497 | +class TestNetworkAccessManager: public QNetworkAccessManager |
498 | +{ |
499 | + Q_OBJECT |
500 | + |
501 | +public: |
502 | + TestNetworkAccessManager(): QNetworkAccessManager() {} |
503 | + |
504 | + void setNextReply(TestNetworkReply *reply) { m_nextReply = reply; } |
505 | + |
506 | +protected: |
507 | + QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, |
508 | + QIODevice *outgoingData = 0) Q_DECL_OVERRIDE { |
509 | + Q_UNUSED(op); |
510 | + m_lastRequest = request; |
511 | + m_lastRequestData = outgoingData->readAll(); |
512 | + m_nextReply->start(); |
513 | + return m_nextReply; |
514 | + } |
515 | + |
516 | +public: |
517 | + QPointer<TestNetworkReply> m_nextReply; |
518 | + QNetworkRequest m_lastRequest; |
519 | + QByteArray m_lastRequestData; |
520 | +}; |
521 | + |
522 | +class PluginTest: public QObject |
523 | +{ |
524 | + Q_OBJECT |
525 | + |
526 | +private Q_SLOTS: |
527 | + void initTestCase(); |
528 | + void cleanupTestCase(); |
529 | + |
530 | + void testLoading(); |
531 | + void testInitialization(); |
532 | + void testPluginType(); |
533 | + void testPluginMechanisms(); |
534 | + void testStoredToken_data(); |
535 | + void testStoredToken(); |
536 | + void testUserInteraction(); |
537 | + void testTokenCreation_data(); |
538 | + void testTokenCreation(); |
539 | + |
540 | + void init(); |
541 | + void cleanup(); |
542 | + |
543 | +private: |
544 | + UbuntuOne::SignOnPlugin *m_testPlugin; |
545 | +}; |
546 | + |
547 | +void PluginTest::initTestCase() |
548 | +{ |
549 | + qRegisterMetaType<SignOn::SessionData>(); |
550 | + qRegisterMetaType<SignOn::UiSessionData>(); |
551 | + qRegisterMetaType<SignOn::Error>(); |
552 | +} |
553 | + |
554 | +void PluginTest::cleanupTestCase() |
555 | +{ |
556 | +} |
557 | + |
558 | +// prepare each test by creating new plugin |
559 | +void PluginTest::init() |
560 | +{ |
561 | + m_testPlugin = new UbuntuOne::SignOnPlugin(); |
562 | +} |
563 | + |
564 | +// finish each test by deleting plugin |
565 | +void PluginTest::cleanup() |
566 | +{ |
567 | + delete m_testPlugin; |
568 | + m_testPlugin = 0; |
569 | +} |
570 | + |
571 | +void PluginTest::testLoading() |
572 | +{ |
573 | + QLibrary module(PLUGIN_PATH); |
574 | + if (!module.load()) { |
575 | + qDebug() << "Failed to load module:" << module.errorString(); |
576 | + } |
577 | + |
578 | + QVERIFY(module.isLoaded()); |
579 | + typedef AuthPluginInterface *(*AuthPluginInstanceF)(); |
580 | + auto instance = |
581 | + (AuthPluginInstanceF)module.resolve("auth_plugin_instance"); |
582 | + QVERIFY(instance); |
583 | + |
584 | + auto plugin = qobject_cast<AuthPluginInterface *>(instance()); |
585 | + QVERIFY(plugin); |
586 | +} |
587 | + |
588 | +void PluginTest::testInitialization() |
589 | +{ |
590 | + QVERIFY(m_testPlugin); |
591 | +} |
592 | + |
593 | +void PluginTest::testPluginType() |
594 | +{ |
595 | + QCOMPARE(m_testPlugin->type(), QString("ubuntuone")); |
596 | +} |
597 | + |
598 | +void PluginTest::testPluginMechanisms() |
599 | +{ |
600 | + QStringList mechs = m_testPlugin->mechanisms(); |
601 | + QCOMPARE(mechs.count(), 1); |
602 | + QCOMPARE(mechs[0], QString("ubuntuone")); |
603 | +} |
604 | + |
605 | +void PluginTest::testStoredToken_data() |
606 | +{ |
607 | + QTest::addColumn<QVariantMap>("sessionData"); |
608 | + QTest::addColumn<int>("expectedErrorCode"); |
609 | + QTest::addColumn<bool>("uiExpected"); |
610 | + QTest::addColumn<QVariantMap>("expectedResponse"); |
611 | + QTest::addColumn<QVariantMap>("expectedStore"); |
612 | + |
613 | + UbuntuOne::PluginData sessionData; |
614 | + UbuntuOne::PluginData response; |
615 | + UbuntuOne::PluginData stored; |
616 | + |
617 | + QTest::newRow("empty") << |
618 | + sessionData.toMap() << |
619 | + -1 << |
620 | + true << QVariantMap() << QVariantMap(); |
621 | + |
622 | + sessionData.setTokenName("helloworld"); |
623 | + sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd"); |
624 | + response.setConsumerKey("aAa"); |
625 | + response.setConsumerSecret("bBb"); |
626 | + response.setTokenKey("cCc"); |
627 | + response.setTokenSecret("dDd"); |
628 | + QVariantMap storedData; |
629 | + storedData[sessionData.TokenName()] = response.toMap(); |
630 | + stored.setStoredData(storedData); |
631 | + response.setTokenName(sessionData.TokenName()); |
632 | + QTest::newRow("in secret, valid") << |
633 | + sessionData.toMap() << |
634 | + -1 << |
635 | + false << response.toMap() << stored.toMap(); |
636 | + sessionData = UbuntuOne::PluginData(); |
637 | + response = UbuntuOne::PluginData(); |
638 | + stored = UbuntuOne::PluginData(); |
639 | + storedData.clear(); |
640 | +} |
641 | + |
642 | +void PluginTest::testStoredToken() |
643 | +{ |
644 | + QFETCH(QVariantMap, sessionData); |
645 | + QFETCH(int, expectedErrorCode); |
646 | + QFETCH(bool, uiExpected); |
647 | + QFETCH(QVariantMap, expectedResponse); |
648 | + QFETCH(QVariantMap, expectedStore); |
649 | + |
650 | + QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&))); |
651 | + QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &))); |
652 | + QSignalSpy userActionRequired(m_testPlugin, |
653 | + SIGNAL(userActionRequired(const SignOn::UiSessionData&))); |
654 | + QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&))); |
655 | + |
656 | + m_testPlugin->process(sessionData, "ubuntuone"); |
657 | + if (expectedErrorCode < 0) { |
658 | + QCOMPARE(error.count(), 0); |
659 | + QTRY_COMPARE(userActionRequired.count(), uiExpected ? 1 : 0); |
660 | + if (!expectedResponse.isEmpty()) { |
661 | + QTRY_COMPARE(result.count(), 1); |
662 | + QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap(); |
663 | + QCOMPARE(resp, expectedResponse); |
664 | + } else { |
665 | + QCOMPARE(result.count(), 0); |
666 | + } |
667 | + |
668 | + if (!expectedStore.isEmpty()) { |
669 | + QCOMPARE(store.count(), 1); |
670 | + QVariantMap storedData = |
671 | + store.at(0).at(0).value<SessionData>().toMap(); |
672 | + QCOMPARE(storedData, expectedStore); |
673 | + } else { |
674 | + QCOMPARE(store.count(), 0); |
675 | + } |
676 | + } else { |
677 | + QTRY_COMPARE(error.count(), 1); |
678 | + Error err = error.at(0).at(0).value<Error>(); |
679 | + QCOMPARE(err.type(), expectedErrorCode); |
680 | + } |
681 | +} |
682 | + |
683 | +void PluginTest::testUserInteraction() |
684 | +{ |
685 | + QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&))); |
686 | + QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &))); |
687 | + QSignalSpy userActionRequired(m_testPlugin, |
688 | + SIGNAL(userActionRequired(const SignOn::UiSessionData&))); |
689 | + QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&))); |
690 | + |
691 | + TestNetworkAccessManager *nam = new TestNetworkAccessManager; |
692 | + m_testPlugin->m_networkAccessManager = nam; |
693 | + |
694 | + UbuntuOne::PluginData sessionData; |
695 | + sessionData.setTokenName("helloworld"); |
696 | + sessionData.setUserName("tom@example.com"); |
697 | + m_testPlugin->process(sessionData, "ubuntuone"); |
698 | + |
699 | + QTRY_COMPARE(userActionRequired.count(), 1); |
700 | + QVariantMap data = |
701 | + userActionRequired.at(0).at(0).value<UiSessionData>().toMap(); |
702 | + /* We want the title to be there, but we don't care about its value here */ |
703 | + QVERIFY(data.contains(SSOUI_KEY_TITLE)); |
704 | + data.remove(SSOUI_KEY_TITLE); |
705 | + QVariantMap expectedUserInteraction; |
706 | + expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com"; |
707 | + expectedUserInteraction[SSOUI_KEY_QUERYPASSWORD] = true; |
708 | + QCOMPARE(data, expectedUserInteraction); |
709 | + userActionRequired.clear(); |
710 | + |
711 | + /* Prepare network reply */ |
712 | + TestNetworkReply *reply = new TestNetworkReply(this); |
713 | + reply->setStatusCode(401); |
714 | + reply->setContent("{\n" |
715 | + " \"code\": \"TWOFACTOR_REQUIRED\",\n" |
716 | + " \"message\": \"This account requires 2-factor authentication.\",\n" |
717 | + " \"extra\": {}\n" |
718 | + "}"); |
719 | + nam->setNextReply(reply); |
720 | + |
721 | + QVariantMap userReply; |
722 | + userReply[SSOUI_KEY_USERNAME] = "tom@example.com"; |
723 | + userReply[SSOUI_KEY_PASSWORD] = "s3cr3t"; |
724 | + m_testPlugin->userActionFinished(userReply); |
725 | + |
726 | + /* Again the plugin should request user interaction, as OTP is required */ |
727 | + QTRY_COMPARE(userActionRequired.count(), 1); |
728 | + data = userActionRequired.at(0).at(0).value<UiSessionData>().toMap(); |
729 | + expectedUserInteraction.clear(); |
730 | + expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com"; |
731 | + expectedUserInteraction[SSOUI_KEY_PASSWORD] = "s3cr3t"; |
732 | + expectedUserInteraction[SSOUI_KEY_QUERY2FA] = true; |
733 | + /* We want the map to contain the SSOUI_KEY_2FA_TEXT, but we don't care |
734 | + * about the value */ |
735 | + QVERIFY(data.contains(SSOUI_KEY_2FA_TEXT)); |
736 | + data.remove(SSOUI_KEY_2FA_TEXT); |
737 | + /* Same goes for the title */ |
738 | + QVERIFY(data.contains(SSOUI_KEY_TITLE)); |
739 | + data.remove(SSOUI_KEY_TITLE); |
740 | + QCOMPARE(data, expectedUserInteraction); |
741 | +} |
742 | + |
743 | +void PluginTest::testTokenCreation_data() |
744 | +{ |
745 | + QTest::addColumn<QVariantMap>("sessionData"); |
746 | + QTest::addColumn<int>("networkError"); |
747 | + QTest::addColumn<int>("httpStatus"); |
748 | + QTest::addColumn<QString>("replyContents"); |
749 | + QTest::addColumn<int>("expectedErrorCode"); |
750 | + QTest::addColumn<QVariantMap>("expectedResponse"); |
751 | + QTest::addColumn<QVariantMap>("expectedStore"); |
752 | + QTest::addColumn<QVariantMap>("expectedUserInteraction"); |
753 | + |
754 | + UbuntuOne::PluginData sessionData; |
755 | + UbuntuOne::PluginData response; |
756 | + UbuntuOne::PluginData stored; |
757 | + QVariantMap userInteraction; |
758 | + |
759 | + // Successful creation, with password only |
760 | + sessionData.setTokenName("helloworld"); |
761 | + sessionData.setUserName("jim@example.com"); |
762 | + sessionData.setSecret("s3cr3t"); |
763 | + response.setConsumerKey("aAa"); |
764 | + response.setConsumerSecret("bBb"); |
765 | + response.setTokenKey("cCc"); |
766 | + response.setTokenSecret("dDd"); |
767 | + response.setDateUpdated("2013-01-11 12:43:23"); |
768 | + response.setDateCreated("2013-01-11 12:43:23"); |
769 | + QVariantMap storedData; |
770 | + storedData[sessionData.TokenName()] = response.toMap(); |
771 | + stored.setStoredData(storedData); |
772 | + response.setTokenName(sessionData.TokenName()); |
773 | + QTest::newRow("no OTP needed, 201") << |
774 | + sessionData.toMap() << |
775 | + -1 << |
776 | + 201 << QString("{\n" |
777 | + " \"href\": \"https://login.ubuntu.com/api/v2/tokens/oauth/the-key\",\n" |
778 | + " \"token_key\": \"cCc\",\n" |
779 | + " \"token_secret\": \"dDd\",\n" |
780 | + " \"token_name\": \"helloworld\",\n" |
781 | + " \"consumer_key\": \"aAa\",\n" |
782 | + " \"consumer_secret\": \"bBb\",\n" |
783 | + " \"date_created\": \"2013-01-11 12:43:23\",\n" |
784 | + " \"date_updated\": \"2013-01-11 12:43:23\"\n" |
785 | + "}") << |
786 | + -1 << |
787 | + response.toMap() << stored.toMap() << userInteraction; |
788 | + sessionData = UbuntuOne::PluginData(); |
789 | + response = UbuntuOne::PluginData(); |
790 | + stored = UbuntuOne::PluginData(); |
791 | + storedData.clear(); |
792 | + |
793 | + // Wrong password |
794 | + sessionData.setTokenName("helloworld"); |
795 | + sessionData.setUserName("jim@example.com"); |
796 | + sessionData.setSecret("s3cr3t"); |
797 | + userInteraction[SSOUI_KEY_USERNAME] = "jim@example.com"; |
798 | + userInteraction[SSOUI_KEY_QUERYPASSWORD] = true; |
799 | + QTest::newRow("wrong password") << |
800 | + sessionData.toMap() << |
801 | + -1 << |
802 | + 401 << QString("{\n" |
803 | + " \"code\": \"INVALID_CREDENTIALS\",\n" |
804 | + " \"message\": \"Wrong password!\",\n" |
805 | + " \"extra\": {}\n" |
806 | + "}") << |
807 | + -1 << |
808 | + response.toMap() << stored.toMap() << userInteraction; |
809 | + sessionData = UbuntuOne::PluginData(); |
810 | + userInteraction.clear(); |
811 | + |
812 | + // Empty username |
813 | + sessionData.setTokenName("helloworld"); |
814 | + sessionData.setSecret("s3cr3t"); |
815 | + userInteraction[SSOUI_KEY_QUERYUSERNAME] = true; |
816 | + userInteraction[SSOUI_KEY_USERNAME] = ""; |
817 | + userInteraction[SSOUI_KEY_QUERYPASSWORD] = true; |
818 | + QTest::newRow("empty username") << |
819 | + sessionData.toMap() << |
820 | + -1 << |
821 | + 401 << QString("{\n" |
822 | + " \"code\": \"INVALID_CREDENTIALS\",\n" |
823 | + " \"message\": \"Missing username\",\n" |
824 | + " \"extra\": {}\n" |
825 | + "}") << |
826 | + -1 << |
827 | + response.toMap() << stored.toMap() << userInteraction; |
828 | + sessionData = UbuntuOne::PluginData(); |
829 | + userInteraction.clear(); |
830 | + |
831 | + // Network error while creating token |
832 | + sessionData.setTokenName("helloworld"); |
833 | + sessionData.setUserName("jim@example.com"); |
834 | + sessionData.setSecret("s3cr3t"); |
835 | + QTest::newRow("network error") << |
836 | + sessionData.toMap() << |
837 | + int(QNetworkReply::SslHandshakeFailedError) << |
838 | + -1 << QString() << |
839 | + int(SignOn::Error::Ssl) << |
840 | + response.toMap() << stored.toMap() << userInteraction; |
841 | + sessionData = UbuntuOne::PluginData(); |
842 | + |
843 | + // Account needs reset |
844 | + sessionData.setTokenName("helloworld"); |
845 | + sessionData.setUserName("jim@example.com"); |
846 | + sessionData.setSecret("s3cr3t"); |
847 | + userInteraction[SSOUI_KEY_OPENURL] = "http://www.example.com/reset"; |
848 | + QTest::newRow("reset needed") << |
849 | + sessionData.toMap() << |
850 | + -1 << |
851 | + 403 << QString("{\n" |
852 | + " \"code\": \"PASSWORD_POLICY_ERROR\",\n" |
853 | + " \"message\": \"Password too short\",\n" |
854 | + " \"extra\": {\n" |
855 | + " \"location\": \"http://www.example.com/reset\"\n" |
856 | + " }\n" |
857 | + "}") << |
858 | + -1 << |
859 | + response.toMap() << stored.toMap() << userInteraction; |
860 | + sessionData = UbuntuOne::PluginData(); |
861 | + userInteraction.clear(); |
862 | +} |
863 | + |
864 | +void PluginTest::testTokenCreation() |
865 | +{ |
866 | + QFETCH(QVariantMap, sessionData); |
867 | + QFETCH(int, httpStatus); |
868 | + QFETCH(int, networkError); |
869 | + QFETCH(QString, replyContents); |
870 | + QFETCH(int, expectedErrorCode); |
871 | + QFETCH(QVariantMap, expectedResponse); |
872 | + QFETCH(QVariantMap, expectedStore); |
873 | + QFETCH(QVariantMap, expectedUserInteraction); |
874 | + |
875 | + QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&))); |
876 | + QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &))); |
877 | + QSignalSpy userActionRequired(m_testPlugin, |
878 | + SIGNAL(userActionRequired(const SignOn::UiSessionData&))); |
879 | + QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&))); |
880 | + |
881 | + /* Prepare network reply */ |
882 | + TestNetworkAccessManager *nam = new TestNetworkAccessManager; |
883 | + m_testPlugin->m_networkAccessManager = nam; |
884 | + TestNetworkReply *reply = new TestNetworkReply(this); |
885 | + if (httpStatus > 0) { |
886 | + reply->setStatusCode(httpStatus); |
887 | + } else { |
888 | + reply->setError(QNetworkReply::NetworkError(networkError), |
889 | + "Network error"); |
890 | + } |
891 | + reply->setContent(replyContents.toUtf8()); |
892 | + nam->setNextReply(reply); |
893 | + |
894 | + |
895 | + m_testPlugin->process(sessionData, "ubuntuone"); |
896 | + if (expectedErrorCode < 0) { |
897 | + if (!expectedUserInteraction.isEmpty()) { |
898 | + QTRY_COMPARE(userActionRequired.count(), 1); |
899 | + QVariantMap data = |
900 | + userActionRequired.at(0).at(0).value<UiSessionData>().toMap(); |
901 | + /* We don't care about the title here */ |
902 | + data.remove(SSOUI_KEY_TITLE); |
903 | + QCOMPARE(data, expectedUserInteraction); |
904 | + } else { |
905 | + QCOMPARE(userActionRequired.count(), 0); |
906 | + } |
907 | + |
908 | + if (!expectedResponse.isEmpty()) { |
909 | + QTRY_COMPARE(result.count(), 1); |
910 | + QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap(); |
911 | + QCOMPARE(resp, expectedResponse); |
912 | + } else { |
913 | + QCOMPARE(result.count(), 0); |
914 | + } |
915 | + |
916 | + if (!expectedStore.isEmpty()) { |
917 | + QCOMPARE(store.count(), 1); |
918 | + QVariantMap storedData = |
919 | + store.at(0).at(0).value<SessionData>().toMap(); |
920 | + QCOMPARE(storedData, expectedStore); |
921 | + } else { |
922 | + QCOMPARE(store.count(), 0); |
923 | + } |
924 | + |
925 | + QCOMPARE(error.count(), 0); |
926 | + } else { |
927 | + QTRY_COMPARE(error.count(), 1); |
928 | + Error err = error.at(0).at(0).value<Error>(); |
929 | + QCOMPARE(err.type(), expectedErrorCode); |
930 | + } |
931 | +} |
932 | + |
933 | +QTEST_MAIN(PluginTest) |
934 | +#include "test_plugin.moc" |
935 | |
936 | === modified file 'signon-plugin/ubuntuone-plugin.cpp' |
937 | --- signon-plugin/ubuntuone-plugin.cpp 2013-08-12 21:16:29 +0000 |
938 | +++ signon-plugin/ubuntuone-plugin.cpp 2016-04-28 10:10:18 +0000 |
939 | @@ -15,15 +15,37 @@ |
940 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
941 | * Boston, MA 02110-1301, USA. |
942 | */ |
943 | + |
944 | +#include <QJsonDocument> |
945 | +#include <QJsonObject> |
946 | +#include <QNetworkAccessManager> |
947 | +#include <QNetworkReply> |
948 | +#include <QNetworkRequest> |
949 | + |
950 | +#include <SignOn/UiSessionData> |
951 | +#include <SignOn/uisessiondata_priv.h> |
952 | + |
953 | +#include <ssoservice.h> |
954 | #include <token.h> |
955 | |
956 | +#include <glib/gi18n-lib.h> |
957 | + |
958 | #include "ubuntuone-plugin.h" |
959 | |
960 | +#define ERR_INVALID_CREDENTIALS QLatin1String("INVALID_CREDENTIALS") |
961 | +#define ERR_INVALID_DATA QLatin1String("INVALID_DATA") |
962 | +#define ERR_TWOFACTOR_REQUIRED QLatin1String("TWOFACTOR_REQUIRED") |
963 | +#define ERR_TWOFACTOR_FAILURE QLatin1String("TWOFACTOR_FAILURE") |
964 | +#define ERR_PASSWORD_POLICY_ERROR QLatin1String("PASSWORD_POLICY_ERROR") |
965 | |
966 | namespace UbuntuOne { |
967 | |
968 | - SignOnPlugin::SignOnPlugin(QObject *parent) |
969 | - : AuthPluginInterface(parent) |
970 | + SignOnPlugin::SignOnPlugin(QObject *parent): |
971 | + AuthPluginInterface(parent), |
972 | + m_networkAccessManager(0), |
973 | + m_reply(0), |
974 | + m_didAskForPassword(false), |
975 | + m_needsOtp(false) |
976 | { |
977 | } |
978 | |
979 | @@ -47,33 +69,289 @@ |
980 | { |
981 | } |
982 | |
983 | + bool SignOnPlugin::validateInput(PluginData &data, |
984 | + const QString &mechanism) |
985 | + { |
986 | + Q_UNUSED(mechanism); |
987 | + |
988 | + if (data.TokenName().isEmpty()) { |
989 | + data.setTokenName(Token::buildTokenName()); |
990 | + } |
991 | + |
992 | + return true; |
993 | + } |
994 | + |
995 | + bool SignOnPlugin::respondWithStoredData() |
996 | + { |
997 | + QVariantMap storedData = m_data.StoredData(); |
998 | + |
999 | + /* When U1 was using the password plugin, it was storing the token data |
1000 | + * in the password field. So, if we don't have any data stored in the |
1001 | + * plugin's data, try to get a token from the password field. |
1002 | + */ |
1003 | + if (storedData.isEmpty() && !m_data.Secret().isEmpty()) { |
1004 | + Token *token = Token::fromQuery(m_data.Secret()); |
1005 | + if (token->isValid()) { |
1006 | + PluginData tokenData; |
1007 | + tokenData.setConsumerKey(token->consumerKey()); |
1008 | + tokenData.setConsumerSecret(token->consumerSecret()); |
1009 | + tokenData.setTokenKey(token->tokenKey()); |
1010 | + tokenData.setTokenSecret(token->tokenSecret()); |
1011 | + QDateTime time = token->updated(); |
1012 | + if (time.isValid()) { |
1013 | + tokenData.setDateUpdated(time.toString(Qt::ISODate)); |
1014 | + } |
1015 | + time = token->created(); |
1016 | + if (time.isValid()) { |
1017 | + tokenData.setDateCreated(time.toString(Qt::ISODate)); |
1018 | + } |
1019 | + storedData[token->name()] = tokenData.toMap(); |
1020 | + PluginData pluginData; |
1021 | + pluginData.setStoredData(storedData); |
1022 | + Q_EMIT store(pluginData); |
1023 | + |
1024 | + /* We know that the given secret is a valid token, so it cannot |
1025 | + * be a valid password as well: let's clear it out now, so that |
1026 | + * if it turns out that the token is no longer valid and that |
1027 | + * we need to create a new one, we won't make a useless attempt |
1028 | + * to create one with a wrong password. |
1029 | + */ |
1030 | + m_data.setSecret(QString()); |
1031 | + } |
1032 | + delete token; |
1033 | + } |
1034 | + |
1035 | + /* Check if we have stored data for this token name */ |
1036 | + PluginData tokenData(storedData[m_data.TokenName()].toMap()); |
1037 | + Token token(tokenData.TokenKey(), tokenData.TokenSecret(), |
1038 | + tokenData.ConsumerKey(), tokenData.ConsumerSecret(), |
1039 | + tokenData.DateCreated(), tokenData.DateUpdated()); |
1040 | + if (!token.isValid()) { |
1041 | + return false; |
1042 | + } |
1043 | + qDebug() << "Token is valid!" << tokenData.TokenKey(); |
1044 | + |
1045 | + tokenData.setTokenName(m_data.TokenName()); |
1046 | + Q_EMIT result(tokenData); |
1047 | + return true; |
1048 | + } |
1049 | + |
1050 | + void SignOnPlugin::emitErrorFromReply(QNetworkReply *reply) |
1051 | + { |
1052 | + int errorCode = reply->error(); |
1053 | + int type = SignOn::Error::Network; |
1054 | + if (errorCode == QNetworkReply::SslHandshakeFailedError) { |
1055 | + type = SignOn::Error::Ssl; |
1056 | + } else if (errorCode == QNetworkReply::ServiceUnavailableError) { |
1057 | + type = SignOn::Error::ServiceNotAvailable; |
1058 | + } else if (errorCode == QNetworkReply::AuthenticationRequiredError) { |
1059 | + type = SignOn::Error::NotAuthorized; |
1060 | + } else if (errorCode <= QNetworkReply::UnknownNetworkError) { |
1061 | + type = SignOn::Error::NoConnection; |
1062 | + } |
1063 | + |
1064 | + qDebug() << "Got error:" << reply->errorString(); |
1065 | + Q_EMIT error(SignOn::Error(type, reply->errorString())); |
1066 | + } |
1067 | + |
1068 | void SignOnPlugin::process(const SignOn::SessionData &inData, |
1069 | const QString &mechanism) |
1070 | { |
1071 | - Q_UNUSED(mechanism); |
1072 | + if (!m_networkAccessManager) { |
1073 | + m_networkAccessManager = new QNetworkAccessManager(this); |
1074 | + } |
1075 | + |
1076 | + bindtextdomain(GETTEXT_PACKAGE, NULL); |
1077 | + textdomain(GETTEXT_PACKAGE); |
1078 | + |
1079 | PluginData response; |
1080 | m_data = inData.data<PluginData>(); |
1081 | |
1082 | - if (!inData.Secret().isEmpty()) { |
1083 | - response.setConsumer(m_data.Consumer()); |
1084 | - response.setConsumerSecret(m_data.ConsumerSecret()); |
1085 | - response.setToken(m_data.Token()); |
1086 | - response.setTokenSecret(m_data.TokenSecret()); |
1087 | - |
1088 | - response.setName(Token::buildTokenName()); |
1089 | - |
1090 | - emit result(response); |
1091 | - return; |
1092 | - } |
1093 | - |
1094 | - SignOn::UiSessionData data; |
1095 | - data.setRealm(inData.Realm()); |
1096 | - data.setShowRealm(!data.Realm().isEmpty()); |
1097 | - emit userActionRequired(data); |
1098 | + if (!validateInput(m_data, mechanism)) { |
1099 | + qWarning() << "Invalid parameters passed"; |
1100 | + Q_EMIT error(SignOn::Error(SignOn::Error::MissingData)); |
1101 | + return; |
1102 | + } |
1103 | + |
1104 | + /* It may be that the stored token is valid; however, do the check only |
1105 | + * if no OTP was provided (since the presence of an OTP is a clear |
1106 | + * signal that the caller wants to get a new token). */ |
1107 | + if (m_data.OneTimePassword().isEmpty() && |
1108 | + respondWithStoredData()) { |
1109 | + return; |
1110 | + } |
1111 | + |
1112 | + getCredentialsAndCreateNewToken(); |
1113 | + } |
1114 | + |
1115 | + void SignOnPlugin::onCreationFinished() |
1116 | + { |
1117 | + QNetworkReply *reply = m_reply; |
1118 | + m_reply->deleteLater(); |
1119 | + m_reply = 0; |
1120 | + |
1121 | + QByteArray data = reply->readAll(); |
1122 | + qDebug() << "Received" << data; |
1123 | + QJsonDocument json = QJsonDocument::fromJson(data); |
1124 | + QJsonObject object = json.object(); |
1125 | + |
1126 | + QString error = object.value("code").toString(); |
1127 | + |
1128 | + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); |
1129 | + qDebug() << "Status code:" << statusCode; |
1130 | + if (statusCode == 200 || statusCode == 201) { |
1131 | + QString tokenName = object.value("token_name").toString(); |
1132 | + PluginData token; |
1133 | + token.setConsumerKey(object.value("consumer_key").toString()); |
1134 | + token.setConsumerSecret(object.value("consumer_secret").toString()); |
1135 | + token.setTokenKey(object.value("token_key").toString()); |
1136 | + token.setTokenSecret(object.value("token_secret").toString()); |
1137 | + token.setDateCreated(Token::dateStringToISO(object.value("date_created").toString())); |
1138 | + token.setDateUpdated(Token::dateStringToISO(object.value("date_updated").toString())); |
1139 | + |
1140 | + /* Store the token */ |
1141 | + QVariantMap storedData; |
1142 | + storedData[tokenName] = token.toMap(); |
1143 | + PluginData pluginData; |
1144 | + pluginData.setStoredData(storedData); |
1145 | + Q_EMIT store(pluginData); |
1146 | + |
1147 | + token.setTokenName(tokenName); |
1148 | + Q_EMIT result(token); |
1149 | + } else if (statusCode == 401 && error == ERR_INVALID_CREDENTIALS) { |
1150 | + m_data.setSecret(QString()); |
1151 | + m_data.setOneTimePassword(QString()); |
1152 | + getCredentialsAndCreateNewToken(); |
1153 | + } else if (statusCode == 401 && error == ERR_TWOFACTOR_REQUIRED) { |
1154 | + m_needsOtp = true; |
1155 | + getCredentialsAndCreateNewToken(); |
1156 | + } else if (statusCode == 403 && error == ERR_TWOFACTOR_FAILURE) { |
1157 | + m_data.setOneTimePassword(QString()); |
1158 | + getCredentialsAndCreateNewToken(); |
1159 | + } else if (statusCode == 403 && error == ERR_PASSWORD_POLICY_ERROR) { |
1160 | + QVariantMap data; |
1161 | + QJsonObject extra = object.value("extra").toObject(); |
1162 | + data[SSOUI_KEY_OPENURL] = extra.value("location").toString(); |
1163 | + Q_EMIT userActionRequired(data); |
1164 | + } else if (error == ERR_INVALID_DATA) { |
1165 | + // This error is received when the email address is invalid |
1166 | + m_data.setUserName(QString()); |
1167 | + m_data.setSecret(QString()); |
1168 | + m_data.setOneTimePassword(QString()); |
1169 | + getCredentialsAndCreateNewToken(); |
1170 | + } else { |
1171 | + emitErrorFromReply(reply); |
1172 | + } |
1173 | + } |
1174 | + |
1175 | + void SignOnPlugin::createNewToken() |
1176 | + { |
1177 | + QNetworkRequest req(QUrl(SSOService::getAuthBaseUrl() + |
1178 | + QStringLiteral("/api/v2/tokens/oauth"))); |
1179 | + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
1180 | + |
1181 | + QJsonObject formData; |
1182 | + formData.insert("email", m_data.UserName()); |
1183 | + formData.insert("password", m_data.Secret()); |
1184 | + formData.insert("token_name", m_data.TokenName()); |
1185 | + if (!m_data.OneTimePassword().isEmpty()) { |
1186 | + formData.insert("otp", m_data.OneTimePassword()); |
1187 | + } |
1188 | + |
1189 | + qDebug() << "Sending data for token creation"; |
1190 | + m_reply = |
1191 | + m_networkAccessManager->post(req, QJsonDocument(formData).toJson()); |
1192 | + QObject::connect(m_reply, SIGNAL(finished()), |
1193 | + this, SLOT(onCreationFinished())); |
1194 | + } |
1195 | + |
1196 | + void SignOnPlugin::getCredentialsAndCreateNewToken() |
1197 | + { |
1198 | + if (!m_data.Secret().isEmpty() && |
1199 | + (!m_needsOtp || !m_data.OneTimePassword().isEmpty())) { |
1200 | + createNewToken(); |
1201 | + } else if (m_data.Secret().isEmpty()) { |
1202 | + QVariantMap data; |
1203 | + data[SSOUI_KEY_TITLE] = |
1204 | + QString::fromUtf8(_("Sign in to your Ubuntu One account")); |
1205 | + if (m_data.UserName().isEmpty()) { |
1206 | + data[SSOUI_KEY_QUERYUSERNAME] = true; |
1207 | + } |
1208 | + data[SSOUI_KEY_USERNAME] = m_data.UserName(); |
1209 | + data[SSOUI_KEY_QUERYPASSWORD] = true; |
1210 | + m_didAskForPassword = true; |
1211 | + Q_EMIT userActionRequired(data); |
1212 | + } else { |
1213 | + QVariantMap data; |
1214 | + data[SSOUI_KEY_TITLE] = |
1215 | + QString::fromUtf8(_("Sign in to your Ubuntu One account")); |
1216 | + data[SSOUI_KEY_USERNAME] = m_data.UserName(); |
1217 | + data[SSOUI_KEY_PASSWORD] = m_data.Secret(); |
1218 | + data[SSOUI_KEY_QUERY2FA] = true; |
1219 | + data[SSOUI_KEY_2FA_TEXT] = |
1220 | + QString::fromUtf8(_("2-factor device code")); |
1221 | + Q_EMIT userActionRequired(data); |
1222 | + } |
1223 | + } |
1224 | + |
1225 | + bool SignOnPlugin::handleUiError(const SignOn::UiSessionData &data) |
1226 | + { |
1227 | + using namespace SignOn; |
1228 | + |
1229 | + int code = data.QueryErrorCode(); |
1230 | + if (code == QUERY_ERROR_NONE) { |
1231 | + return false; |
1232 | + } |
1233 | + |
1234 | + qDebug() << "userActionFinished with error: " << code; |
1235 | + if (code == QUERY_ERROR_CANCELED) { |
1236 | + Q_EMIT error(Error(Error::SessionCanceled, |
1237 | + QLatin1String("Cancelled by user"))); |
1238 | + } else if (code == QUERY_ERROR_NETWORK) { |
1239 | + Q_EMIT error(Error(Error::Network, QLatin1String("Network error"))); |
1240 | + } else if (code == QUERY_ERROR_SSL) { |
1241 | + Q_EMIT error(Error(Error::Ssl, QLatin1String("SSL error"))); |
1242 | + } else { |
1243 | + QVariantMap map = data.toMap(); |
1244 | + if (map.contains(SSOUI_KEY_QUERY2FA)) { |
1245 | + PluginData reply; |
1246 | + reply.setU1ErrorCode(PluginData::OneTimePasswordRequired); |
1247 | + Q_EMIT result(reply); |
1248 | + } else if (map.contains(SSOUI_KEY_QUERYPASSWORD)) { |
1249 | + PluginData reply; |
1250 | + reply.setU1ErrorCode(PluginData::InvalidPassword); |
1251 | + Q_EMIT result(reply); |
1252 | + } else { |
1253 | + Q_EMIT error(Error(Error::UserInteraction, |
1254 | + QString("userActionFinished error: ") |
1255 | + + QString::number(data.QueryErrorCode()))); |
1256 | + } |
1257 | + } |
1258 | + return true; |
1259 | } |
1260 | |
1261 | void SignOnPlugin::userActionFinished(const SignOn::UiSessionData &data) |
1262 | { |
1263 | + if (handleUiError(data)) { |
1264 | + return; |
1265 | + } |
1266 | + |
1267 | + PluginData uiData = data.data<PluginData>(); |
1268 | + if (!uiData.UserName().isEmpty()) { |
1269 | + m_data.setUserName(uiData.UserName()); |
1270 | + } |
1271 | + |
1272 | + if (!uiData.Secret().isEmpty()) { |
1273 | + m_data.setSecret(uiData.Secret()); |
1274 | + } |
1275 | + |
1276 | + QVariantMap map = data.toMap(); |
1277 | + QString oneTimePassword = map.value(SSOUI_KEY_2FA).toString(); |
1278 | + if (!oneTimePassword.isEmpty()) { |
1279 | + m_data.setOneTimePassword(oneTimePassword); |
1280 | + } |
1281 | + |
1282 | + getCredentialsAndCreateNewToken(); |
1283 | } |
1284 | |
1285 | SIGNON_DECL_AUTH_PLUGIN(SignOnPlugin) |
1286 | |
1287 | === modified file 'signon-plugin/ubuntuone-plugin.h' |
1288 | --- signon-plugin/ubuntuone-plugin.h 2013-08-12 21:16:29 +0000 |
1289 | +++ signon-plugin/ubuntuone-plugin.h 2016-04-28 10:10:18 +0000 |
1290 | @@ -27,9 +27,15 @@ |
1291 | |
1292 | #include "ubuntuonedata.h" |
1293 | |
1294 | +class PluginTest; |
1295 | + |
1296 | +class QNetworkAccessManager; |
1297 | +class QNetworkReply; |
1298 | |
1299 | namespace UbuntuOne { |
1300 | |
1301 | + class Token; |
1302 | + |
1303 | class SignOnPlugin : public AuthPluginInterface |
1304 | { |
1305 | Q_OBJECT |
1306 | @@ -40,17 +46,45 @@ |
1307 | virtual ~SignOnPlugin(); |
1308 | |
1309 | public Q_SLOTS: |
1310 | - QString type() const; |
1311 | - QStringList mechanisms() const; |
1312 | - void cancel(); |
1313 | + QString type() const Q_DECL_OVERRIDE; |
1314 | + QStringList mechanisms() const Q_DECL_OVERRIDE; |
1315 | + void cancel() Q_DECL_OVERRIDE; |
1316 | void process(const SignOn::SessionData &inData, |
1317 | - const QString &mechanism = 0); |
1318 | - void userActionFinished(const SignOn::UiSessionData &data); |
1319 | - |
1320 | - private: |
1321 | + const QString &mechanism = 0) Q_DECL_OVERRIDE; |
1322 | + void userActionFinished(const SignOn::UiSessionData &data) Q_DECL_OVERRIDE; |
1323 | + |
1324 | + private: |
1325 | + bool validateInput(PluginData &data, const QString &mechanism); |
1326 | + bool respondWithStoredData(); |
1327 | + void emitErrorFromReply(QNetworkReply *reply); |
1328 | + void createNewToken(); |
1329 | + void getCredentialsAndCreateNewToken(); |
1330 | + bool handleUiError(const SignOn::UiSessionData &data); |
1331 | + |
1332 | + private Q_SLOTS: |
1333 | + void onCreationFinished(); |
1334 | + |
1335 | + private: |
1336 | + friend class ::PluginTest; |
1337 | PluginData m_data; |
1338 | + QNetworkAccessManager *m_networkAccessManager; |
1339 | + QNetworkReply *m_reply; |
1340 | + bool m_didAskForPassword; |
1341 | + bool m_needsOtp; |
1342 | }; |
1343 | |
1344 | } // namespace UbuntuOne |
1345 | |
1346 | +/* These fields are temporarily defined here; they'll be eventually moved to |
1347 | + * signond's include files. */ |
1348 | +#define SSOUI_KEY_USERNAME_TEXT QLatin1String("UserNameText") |
1349 | +#define SSOUI_KEY_PASSWORD_TEXT QLatin1String("PasswordText") |
1350 | +#define SSOUI_KEY_REGISTER_URL QLatin1String("RegisterUrl") |
1351 | +#define SSOUI_KEY_REGISTER_TEXT QLatin1String("RegisterText") |
1352 | +#define SSOUI_KEY_LOGIN_TEXT QLatin1String("LoginText") |
1353 | +#define SSOUI_KEY_QUERY2FA QLatin1String("Query2fa") |
1354 | +#define SSOUI_KEY_2FA QLatin1String("2fa") |
1355 | +#define SSOUI_KEY_2FA_TEXT QLatin1String("2faText") |
1356 | +#define SSOUI_KEY_ERROR_MESSAGE QLatin1String("ErrorMessage") |
1357 | + |
1358 | #endif |
1359 | |
1360 | === modified file 'signon-plugin/ubuntuonedata.h' |
1361 | --- signon-plugin/ubuntuonedata.h 2013-08-12 21:16:29 +0000 |
1362 | +++ signon-plugin/ubuntuonedata.h 2016-04-28 10:10:18 +0000 |
1363 | @@ -25,16 +25,37 @@ |
1364 | class PluginData : public SignOn::SessionData |
1365 | { |
1366 | public: |
1367 | + PluginData(const QVariantMap &data = QVariantMap()): |
1368 | + SignOn::SessionData(data) {} |
1369 | + |
1370 | // The name of the token |
1371 | - SIGNON_SESSION_DECLARE_PROPERTY(QString, Name); |
1372 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenName); |
1373 | + |
1374 | + // The one-time password (optional) |
1375 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, OneTimePassword); |
1376 | |
1377 | // The consumer key and secret for signing |
1378 | - SIGNON_SESSION_DECLARE_PROPERTY(QString, Consumer); |
1379 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, ConsumerKey); |
1380 | SIGNON_SESSION_DECLARE_PROPERTY(QString, ConsumerSecret); |
1381 | |
1382 | // The access token and secret for signing |
1383 | - SIGNON_SESSION_DECLARE_PROPERTY(QString, Token); |
1384 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenKey); |
1385 | SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenSecret); |
1386 | + |
1387 | + // Token creation and update time |
1388 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, DateCreated); |
1389 | + SIGNON_SESSION_DECLARE_PROPERTY(QString, DateUpdated); |
1390 | + |
1391 | + // Error code |
1392 | + enum ErrorCode { |
1393 | + NoError = 0, |
1394 | + OneTimePasswordRequired, |
1395 | + InvalidPassword, |
1396 | + }; |
1397 | + SIGNON_SESSION_DECLARE_PROPERTY(int, U1ErrorCode); |
1398 | + |
1399 | + // Data which the plugin has stored into signond |
1400 | + SIGNON_SESSION_DECLARE_PROPERTY(QVariantMap, StoredData); |
1401 | }; |
1402 | |
1403 | } // namespace UbuntuOne |