Merge lp:~mardy/ubuntuone-credentials/default-token-name into lp:ubuntuone-credentials

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
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://code.launchpad.net/~mardy/unity-scope-click/signon-plugin/+merge/288758
(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

Subscribers

People subscribed via source and target branches