Merge lp:~mardy/ubuntuone-credentials/signon-plugin into lp:ubuntuone-credentials

Proposed by Alberto Mardegan
Status: Work in progress
Proposed branch: lp:~mardy/ubuntuone-credentials/signon-plugin
Merge into: lp:ubuntuone-credentials
Diff against target: 3370 lines (+2886/-118)
23 files modified
data/ubuntuone.provider (+2/-2)
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/accountmanager.cpp (+44/-0)
libubuntuoneauth/accountmanager.h (+43/-0)
libubuntuoneauth/authenticator.cpp (+176/-0)
libubuntuoneauth/authenticator.h (+71/-0)
libubuntuoneauth/common.h (+30/-0)
libubuntuoneauth/keyring.cpp (+34/-60)
libubuntuoneauth/libubuntuoneauth.symbols (+2/-1)
libubuntuoneauth/ssoservice.cpp (+44/-13)
libubuntuoneauth/token.cpp (+54/-5)
libubuntuoneauth/token.h (+4/-0)
signon-plugin/CMakeLists.txt (+9/-2)
signon-plugin/i18n.cpp (+37/-0)
signon-plugin/i18n.h (+31/-0)
signon-plugin/tests/CMakeLists.txt (+52/-0)
signon-plugin/tests/tst_plugin.cpp (+1840/-0)
signon-plugin/ubuntuone-plugin.cpp (+327/-19)
signon-plugin/ubuntuone-plugin.h (+45/-7)
signon-plugin/ubuntuonedata.h (+20/-3)
To merge this branch: bzr merge lp:~mardy/ubuntuone-credentials/signon-plugin
Reviewer Review Type Date Requested Status
dobey Pending
Review via email: mp+287132@code.launchpad.net

Commit message

WIP Implement SignOn authentication plugin, use it libubuntuoneauth.

Description of the change

WIP Implement SignOn authentication plugin, use it libubuntuoneauth.

To post a comment you must log in.
237. By Alberto Mardegan

Force export of symbol

238. By Alberto Mardegan

Allow authentication with no account

239. By Alberto Mardegan

No undefined symbols

240. By Alberto Mardegan

Test plugin loading

241. By Alberto Mardegan

Properly export symbols

242. By Alberto Mardegan

debugging

243. By Alberto Mardegan

Return specific error codes

244. By Alberto Mardegan

Tweak on application name

245. By Alberto Mardegan

missing include

246. By Alberto Mardegan

Explain hack

247. By Alberto Mardegan

Never remove accounts

248. By Alberto Mardegan

Plugin: don't delete the reply while using it

249. By Alberto Mardegan

Handle invalid email error

250. By Alberto Mardegan

Allow UI interactions when retrieving token

251. By Alberto Mardegan

Fix dialog title and handling of 2fa reply

252. By Alberto Mardegan

Avoid double free

253. By Alberto Mardegan

Update method name in .provider file

254. By Alberto Mardegan

Merge trunk

[ CI Train Bot ]
* Resync trunk.
[ Michael Zanetti ]
* update to uitk 1.3 (LP: #1560621)
[ Rodney Dawes ]
* Disable tests on powerpc as something in xenial is causing a crash.

255. By Alberto Mardegan

Use Internal namespace

Unmerged revisions

255. By Alberto Mardegan

Use Internal namespace

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/ubuntuone.provider'
2--- data/ubuntuone.provider 2013-10-16 08:21:08 +0000
3+++ data/ubuntuone.provider 2016-04-22 09:51:32 +0000
4@@ -8,8 +8,8 @@
5
6 <template>
7 <group name="auth">
8- <setting name="method">password</setting>
9- <setting name="mechanism">password</setting>
10+ <setting name="method">ubuntuone</setting>
11+ <setting name="mechanism">ubuntuone</setting>
12 </group>
13 </template>
14 </provider>
15
16=== modified file 'debian/control'
17--- debian/control 2015-11-16 20:17:01 +0000
18+++ debian/control 2016-04-22 09:51:32 +0000
19@@ -14,7 +14,7 @@
20 qtdeclarative5-dev-tools,
21 qtdeclarative5-ubuntu-ui-toolkit-plugin,
22 python3-all:native,
23- signon-plugins-dev,
24+ signon-plugins-dev (>= 8.58),
25 ubuntu-ui-toolkit-autopilot:native,
26 xvfb,
27 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
28@@ -135,7 +135,7 @@
29
30 Package: signon-plugin-ubuntuone
31 Architecture: any
32-Multi-Arch: foreign
33+Multi-Arch: same
34 Pre-Depends:
35 multiarch-support,
36 ${misc:Pre-Depends},
37@@ -159,6 +159,7 @@
38 Depends:
39 libubuntuoneauth-2.0-0 (= ${binary:Version}),
40 qml-module-ubuntuone (= ${binary:Version}),
41+ signon-plugin-ubuntuone,
42 ubuntu-system-settings-online-accounts,
43 ${misc:Depends},
44 ${shlibs:Depends},
45
46=== modified file 'debian/libubuntuoneauth-2.0-0.symbols'
47--- debian/libubuntuoneauth-2.0-0.symbols 2015-12-07 21:38:01 +0000
48+++ debian/libubuntuoneauth-2.0-0.symbols 2016-04-22 09:51:32 +0000
49@@ -111,7 +111,11 @@
50 (c++)"UbuntuOne::Token::Token(QString, QString, QString, QString)@Base" 13.08
51 (c++)"UbuntuOne::Token::Token(QString, QString, QString, QString, QString, QString)@Base" 14.04+14.10.20140908
52 (c++)"UbuntuOne::Token::~Token()@Base" 13.08
53+ (c++)"UbuntuOne::Token::name() const@Base" 15.11+16.04.20151207.1
54 (c++)"UbuntuOne::Token::consumerKey() const@Base" 15.10+15.10.20150617
55+ (c++)"UbuntuOne::Token::consumerSecret() const@Base" 15.11+16.04.20151207.1
56+ (c++)"UbuntuOne::Token::tokenKey() const@Base" 15.11+16.04.20151207.1
57+ (c++)"UbuntuOne::Token::tokenSecret() const@Base" 15.11+16.04.20151207.1
58 (c++)"UbuntuOne::Token::created() const@Base" 14.04+14.10.20140818
59 (c++)"UbuntuOne::Token::updated() const@Base" 14.04+14.10.20140818
60 (c++)"UbuntuOne::Token::getServerTimestamp() const@Base" 15.11+16.04.20151207.1
61
62=== modified file 'debian/signon-plugin-ubuntuone.install'
63--- debian/signon-plugin-ubuntuone.install 2013-11-27 21:19:44 +0000
64+++ debian/signon-plugin-ubuntuone.install 2016-04-22 09:51:32 +0000
65@@ -1,1 +1,1 @@
66-usr/lib/signon/*.so
67+usr/lib/*/signon/*.so
68
69=== modified file 'libubuntuoneauth/CMakeLists.txt'
70--- libubuntuoneauth/CMakeLists.txt 2013-11-22 19:17:04 +0000
71+++ libubuntuoneauth/CMakeLists.txt 2016-04-22 09:51:32 +0000
72@@ -16,7 +16,17 @@
73 # The sources for building the library
74 FILE (GLOB SOURCES *.cpp)
75 # HEADERS only includes the public headers for installation.
76-FILE (GLOB HEADERS *.h)
77+FILE (GLOB HEADERS
78+ errormessages.h
79+ identityprovider.h
80+ keyring.h
81+ logging.h
82+ network.h
83+ requests.h
84+ responses.h
85+ ssoservice.h
86+ token.h
87+)
88
89 pkg_check_modules(OAUTH REQUIRED oauth)
90 add_definitions(${OAUTH_CFLAGS} ${OAUTH_CFLAGS_OTHER})
91@@ -29,12 +39,12 @@
92 target_link_libraries (${AUTH_LIB_NAME}
93 ${LIBSIGNON_LDFLAGS}
94 ${OAUTH_LDFLAGS}
95- -Wl,--version-script -Wl,${CMAKE_CURRENT_SOURCE_DIR}/libubuntuoneauth.symbols
96 )
97
98 SET_TARGET_PROPERTIES(${AUTH_LIB_NAME} PROPERTIES
99 VERSION ${AUTH_LIB_VERSION}
100 SOVERSION ${AUTH_LIB_SOVERSION}
101+ LINK_FLAGS "-Wl,--version-script -Wl,\"${CMAKE_CURRENT_SOURCE_DIR}/libubuntuoneauth.symbols\""
102 )
103
104 INSTALL (
105@@ -64,4 +74,4 @@
106 )
107
108 add_subdirectory(tests)
109-add_subdirectory(examples)
110\ No newline at end of file
111+add_subdirectory(examples)
112
113=== added file 'libubuntuoneauth/accountmanager.cpp'
114--- libubuntuoneauth/accountmanager.cpp 1970-01-01 00:00:00 +0000
115+++ libubuntuoneauth/accountmanager.cpp 2016-04-22 09:51:32 +0000
116@@ -0,0 +1,44 @@
117+/*
118+ * Copyright 2016 Canonical Ltd.
119+ *
120+ * This library is free software; you can redistribute it and/or
121+ * modify it under the terms of version 3 of the GNU Lesser General Public
122+ * License as published by the Free Software Foundation.
123+ *
124+ * This program is distributed in the hope that it will be useful,
125+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
126+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
127+ * General Public License for more details.
128+ *
129+ * You should have received a copy of the GNU Lesser General Public
130+ * License along with this library; if not, write to the
131+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
132+ * Boston, MA 02110-1301, USA.
133+ */
134+
135+#include "accountmanager.h"
136+
137+namespace Internal {
138+
139+AccountManager *AccountManager::m_instance = 0;
140+
141+AccountManager::AccountManager(QObject *parent):
142+ Accounts::Manager(parent)
143+{
144+}
145+
146+AccountManager::~AccountManager()
147+{
148+ m_instance = 0;
149+}
150+
151+AccountManager *AccountManager::instance()
152+{
153+ if (!m_instance) {
154+ m_instance = new AccountManager;
155+ }
156+
157+ return m_instance;
158+}
159+
160+} // namespace
161
162=== added file 'libubuntuoneauth/accountmanager.h'
163--- libubuntuoneauth/accountmanager.h 1970-01-01 00:00:00 +0000
164+++ libubuntuoneauth/accountmanager.h 2016-04-22 09:51:32 +0000
165@@ -0,0 +1,43 @@
166+/*
167+ * Copyright 2016 Canonical Ltd.
168+ *
169+ * This library is free software; you can redistribute it and/or
170+ * modify it under the terms of version 3 of the GNU Lesser General Public
171+ * License as published by the Free Software Foundation.
172+ *
173+ * This program is distributed in the hope that it will be useful,
174+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
175+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
176+ * General Public License for more details.
177+ *
178+ * You should have received a copy of the GNU Lesser General Public
179+ * License along with this library; if not, write to the
180+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
181+ * Boston, MA 02110-1301, USA.
182+ */
183+#ifndef _U1_ACCOUNTMANAGER_H_
184+#define _U1_ACCOUNTMANAGER_H_
185+
186+#include <Accounts/Manager>
187+#include <QObject>
188+
189+namespace Internal {
190+
191+class AccountManager : public Accounts::Manager
192+{
193+ Q_OBJECT
194+
195+public:
196+ static AccountManager *instance();
197+ ~AccountManager();
198+
199+protected:
200+ explicit AccountManager(QObject *parent = 0);
201+
202+private:
203+ static AccountManager *m_instance;
204+};
205+
206+} /* namespace */
207+
208+#endif /* _U1_ACCOUNTMANAGER_H_ */
209
210=== added file 'libubuntuoneauth/authenticator.cpp'
211--- libubuntuoneauth/authenticator.cpp 1970-01-01 00:00:00 +0000
212+++ libubuntuoneauth/authenticator.cpp 2016-04-22 09:51:32 +0000
213@@ -0,0 +1,176 @@
214+/*
215+ * Copyright 2016 Canonical Ltd.
216+ *
217+ * This library is free software; you can redistribute it and/or
218+ * modify it under the terms of version 3 of the GNU Lesser General Public
219+ * License as published by the Free Software Foundation.
220+ *
221+ * This program is distributed in the hope that it will be useful,
222+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
223+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
224+ * General Public License for more details.
225+ *
226+ * You should have received a copy of the GNU Lesser General Public
227+ * License along with this library; if not, write to the
228+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
229+ * Boston, MA 02110-1301, USA.
230+ */
231+
232+#include <Accounts/Account>
233+#include <Accounts/Service>
234+#include <SignOn/AuthSession>
235+#include <SignOn/Identity>
236+#include <SignOn/IdentityInfo>
237+
238+#include <QDebug>
239+
240+#include "accountmanager.h"
241+#include "authenticator.h"
242+#include "../signon-plugin/ubuntuonedata.h"
243+
244+using namespace Internal;
245+using namespace UbuntuOne;
246+
247+Authenticator::Authenticator(QObject *parent):
248+ QObject(parent),
249+ m_manager(AccountManager::instance()),
250+ m_invalidate(false),
251+ m_uiAllowed(true)
252+{
253+}
254+
255+void Authenticator::handleError(const SignOn::Error &e)
256+{
257+ qCritical() << "Authentication error:" << e.message();
258+ Q_EMIT error(AuthenticationError);
259+}
260+
261+void Authenticator::handleSessionData(const SignOn::SessionData &data)
262+{
263+ PluginData reply = data.data<PluginData>();
264+
265+ auto errorCode = PluginData::ErrorCode(reply.U1ErrorCode());
266+ if (errorCode != PluginData::NoError) {
267+ switch (errorCode) {
268+ case PluginData::OneTimePasswordRequired:
269+ qDebug() << "Error: OTP required";
270+ Q_EMIT error(OneTimePasswordRequired);
271+ break;
272+ case PluginData::InvalidPassword:
273+ qDebug() << "Error: invalid password";
274+ Q_EMIT error(InvalidPassword);
275+ break;
276+ default:
277+ qWarning() << "Unknown error code" << errorCode;
278+ Q_EMIT error(AuthenticationError);
279+ }
280+ return;
281+ }
282+
283+ Token token(reply.TokenKey(), reply.TokenSecret(),
284+ reply.ConsumerKey(), reply.ConsumerSecret());
285+ if (token.isValid()) {
286+ Q_EMIT authenticated(token);
287+ } else {
288+ QString message("Failed to convert result to Token object.");
289+ qCritical() << message;
290+ Q_EMIT error(AuthenticationError);
291+ }
292+}
293+
294+quint32 Authenticator::credentialsId()
295+{
296+ QString providerId("ubuntuone");
297+ Accounts::AccountIdList accountIds = m_manager->accountList(providerId);
298+
299+ if (accountIds.isEmpty()) {
300+ qDebug() << "authenticate(): No UbuntuOne accounts found";
301+ Q_EMIT error(AccountNotFound);
302+ return 0;
303+ }
304+
305+ if (accountIds.count() > 1) {
306+ qWarning() << "authenticate(): Found '" << accountIds.count() <<
307+ "' accounts. Using first.";
308+ }
309+
310+ qDebug() << "authenticate(): Using account '" << accountIds[0] << "'.";
311+
312+ auto account = m_manager->account(accountIds[0]);
313+ if (Q_UNLIKELY(!account)) {
314+ qDebug() << "Couldn't load account";
315+ /* This could either happen because the account was deleted right while
316+ * we were loading it, or because the accounts DB was locked by another
317+ * app. Let's just return an authentication error here, so the client
318+ * can retry.
319+ */
320+ Q_EMIT error(AuthenticationError);
321+ return 0;
322+ }
323+
324+ /* Here we should check that the account service is enabled; but since the
325+ * old code was not doing this check, and that from the API there is no way
326+ * of knowing which service we are interested in, let's leave it as a TODO.
327+ */
328+
329+ return account->credentialsId();
330+}
331+
332+void Authenticator::authenticate(const QString &tokenName,
333+ const QString &userName,
334+ const QString &password,
335+ const QString &otp)
336+{
337+ SignOn::Identity *identity;
338+ if (userName.isEmpty()) {
339+ // Use existing account
340+ quint32 id = credentialsId();
341+ if (Q_UNLIKELY(!id)) return;
342+
343+ identity = SignOn::Identity::existingIdentity(id, this);
344+ if (Q_UNLIKELY(!identity)) {
345+ qCritical() << "authenticate(): unable to load credentials" << id;
346+ Q_EMIT error(AccountNotFound);
347+ return;
348+ }
349+ } else {
350+ identity = SignOn::Identity::newIdentity(SignOn::IdentityInfo(), this);
351+ }
352+
353+ auto session = identity->createSession(QStringLiteral("ubuntuone"));
354+ if (Q_UNLIKELY(!session)) {
355+ qCritical() << "Unable to create AuthSession.";
356+ Q_EMIT error(AuthenticationError);
357+ return;
358+ }
359+
360+ connect(session, SIGNAL(response(const SignOn::SessionData&)),
361+ this, SLOT(handleSessionData(const SignOn::SessionData&)));
362+ connect(session, SIGNAL(error(const SignOn::Error&)),
363+ this, SLOT(handleError(const SignOn::Error&)));
364+
365+ PluginData data;
366+ data.setTokenName(tokenName);
367+ data.setUserName(userName);
368+ data.setSecret(password);
369+ data.setOneTimePassword(otp);
370+ int uiPolicy = m_uiAllowed ?
371+ SignOn::DefaultPolicy : SignOn::NoUserInteractionPolicy;
372+ if (m_invalidate) {
373+ uiPolicy |= SignOn::RequestPasswordPolicy;
374+ m_invalidate = false;
375+ }
376+ data.setUiPolicy(uiPolicy);
377+
378+ session->process(data, QStringLiteral("ubuntuone"));
379+}
380+
381+void Authenticator::invalidateCredentials()
382+{
383+ m_invalidate = true;
384+}
385+
386+void Authenticator::setUiAllowed(bool allowed)
387+{
388+ m_uiAllowed = allowed;
389+}
390
391=== added file 'libubuntuoneauth/authenticator.h'
392--- libubuntuoneauth/authenticator.h 1970-01-01 00:00:00 +0000
393+++ libubuntuoneauth/authenticator.h 2016-04-22 09:51:32 +0000
394@@ -0,0 +1,71 @@
395+/*
396+ * Copyright 2016 Canonical Ltd.
397+ *
398+ * This library is free software; you can redistribute it and/or
399+ * modify it under the terms of version 3 of the GNU Lesser General Public
400+ * License as published by the Free Software Foundation.
401+ *
402+ * This program is distributed in the hope that it will be useful,
403+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
404+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
405+ * General Public License for more details.
406+ *
407+ * You should have received a copy of the GNU Lesser General Public
408+ * License along with this library; if not, write to the
409+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
410+ * Boston, MA 02110-1301, USA.
411+ */
412+#ifndef _U1_AUTHENTICATOR_H_
413+#define _U1_AUTHENTICATOR_H_
414+
415+#include <Accounts/Manager>
416+#include <SignOn/Identity>
417+
418+#include <QObject>
419+
420+#include "token.h"
421+
422+namespace Internal {
423+
424+class Authenticator : public QObject
425+{
426+ Q_OBJECT
427+
428+public:
429+ enum ErrorCode {
430+ NoError = 0,
431+ AccountNotFound,
432+ OneTimePasswordRequired,
433+ InvalidPassword,
434+ AuthenticationError, // will create more specific codes if needed
435+ };
436+
437+ explicit Authenticator(QObject *parent = 0);
438+
439+ void authenticate(const QString &tokenName,
440+ const QString &userName = QString(),
441+ const QString &password = QString(),
442+ const QString &otp = QString());
443+ void invalidateCredentials();
444+ void setUiAllowed(bool allowed);
445+
446+Q_SIGNALS:
447+ void authenticated(const UbuntuOne::Token& token);
448+ void error(Internal::Authenticator::ErrorCode code);
449+
450+private:
451+ quint32 credentialsId();
452+
453+private Q_SLOTS:
454+ void handleError(const SignOn::Error &error);
455+ void handleSessionData(const SignOn::SessionData &data);
456+
457+private:
458+ Accounts::Manager *m_manager;
459+ bool m_invalidate;
460+ bool m_uiAllowed;
461+};
462+
463+} /* namespace */
464+
465+#endif /* _U1_AUTHENTICATOR_H_ */
466
467=== added file 'libubuntuoneauth/common.h'
468--- libubuntuoneauth/common.h 1970-01-01 00:00:00 +0000
469+++ libubuntuoneauth/common.h 2016-04-22 09:51:32 +0000
470@@ -0,0 +1,30 @@
471+/*
472+ * Copyright 2016 Canonical Ltd.
473+ *
474+ * This library is free software; you can redistribute it and/or
475+ * modify it under the terms of version 3 of the GNU Lesser General Public
476+ * License as published by the Free Software Foundation.
477+ *
478+ * This program is distributed in the hope that it will be useful,
479+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
480+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
481+ * General Public License for more details.
482+ *
483+ * You should have received a copy of the GNU Lesser General Public
484+ * License along with this library; if not, write to the
485+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
486+ * Boston, MA 02110-1301, USA.
487+ */
488+
489+#ifndef U1_COMMON_H
490+#define U1_COMMON_H
491+
492+#include <QtGlobal>
493+
494+#if defined(BUILDING_LIBU1AUTH)
495+# define U1_EXPORT Q_DECL_EXPORT
496+#else
497+# define U1_EXPORT Q_DECL_IMPORT
498+#endif
499+
500+#endif // U1_COMMON_H
501
502=== modified file 'libubuntuoneauth/keyring.cpp'
503--- libubuntuoneauth/keyring.cpp 2015-03-12 13:13:46 +0000
504+++ libubuntuoneauth/keyring.cpp 2016-04-22 09:51:32 +0000
505@@ -23,7 +23,9 @@
506
507 #include <QDebug>
508
509+#include "authenticator.h"
510 #include "keyring.h"
511+#include "../signon-plugin/ubuntuonedata.h"
512
513 using namespace Accounts;
514 using namespace SignOn;
515@@ -46,58 +48,41 @@
516
517 void Keyring::handleSessionData(const SignOn::SessionData &data)
518 {
519- QString secret = data.Secret();
520-
521- if (secret.length() == 0) {
522- QString msg("Could not read credentials secret value.");
523- qCritical() << msg;
524- emit keyringError(msg);
525- return;
526- }
527-
528- Token *token = Token::fromQuery(secret);
529- if (token->isValid()) {
530- emit tokenFound(*token);
531+ PluginData reply = data.data<PluginData>();
532+
533+ Token token(reply.TokenKey(), reply.TokenSecret(),
534+ reply.ConsumerKey(), reply.ConsumerSecret());
535+ if (token.isValid()) {
536+ emit tokenFound(token);
537 } else {
538 QString message("Failed to convert result to Token object.");
539 qCritical() << message;
540 emit keyringError(message);
541 }
542- delete token;
543 }
544
545 void Keyring::findToken()
546 {
547- QString _acctName("ubuntuone");
548- AccountIdList _ids = _manager.accountList(_acctName);
549- Identity *identity;
550- Account *account;
551-
552- if (_ids.length() > 0) {
553- if (_ids.length() > 1) {
554- qDebug() << "findToken(): Found '" << _ids.length() << "' accounts. Using first.";
555- }
556- account = _manager.account(_ids[0]);
557- qDebug() << "findToken(): Using Ubuntu One account '" << _ids[0] << "'.";
558- identity = Identity::existingIdentity(account->credentialsId());
559- if (identity == NULL) {
560- qCritical() << "findToken(): disabled account " << _acctName << _ids[0];
561- emit tokenNotFound();
562- return;
563- }
564- AuthSession *session = identity->createSession(QStringLiteral("password"));
565- if (session != NULL) {
566- connect(session, SIGNAL(response(const SignOn::SessionData&)),
567- this, SLOT(handleSessionData(const SignOn::SessionData&)));
568- connect(session, SIGNAL(error(const SignOn::Error&)),
569- this, SLOT(handleError(const SignOn::Error&)));
570- session->process(SessionData(), QStringLiteral("password"));
571- return;
572- }
573- qCritical() << "Unable to create AuthSession.";
574- }
575- qDebug() << "findToken(): No accounts found matching " << _acctName;
576- emit tokenNotFound();
577+ using namespace Internal;
578+
579+ auto authenticator = new Authenticator;
580+ authenticator->setUiAllowed(true);
581+
582+ connect(authenticator, &Authenticator::authenticated,
583+ [=](const Token &token) {
584+ Q_EMIT tokenFound(token);
585+ authenticator->deleteLater();
586+ });
587+ connect(authenticator, &Authenticator::error,
588+ [=](Authenticator::ErrorCode code) {
589+ if (code == Authenticator::AccountNotFound) {
590+ Q_EMIT tokenNotFound();
591+ } else {
592+ Q_EMIT keyringError("Authentication failed");
593+ }
594+ authenticator->deleteLater();
595+ });
596+ authenticator->authenticate(Token::buildTokenName());
597 }
598
599 void Keyring::handleCredentialsStored(const quint32 id)
600@@ -167,11 +152,13 @@
601
602 void Keyring::handleAccountRemoved()
603 {
604+ /* DEPRECATED, UNUSED */
605 emit tokenDeleted();
606 }
607
608 void Keyring::handleDeleteError(const SignOn::Error &error)
609 {
610+ /* DEPRECATED, UNUSED */
611 // Just log the error here, as we don't want to infinite loop.
612 qWarning() << "Error deleting token:" << error.message();
613 }
614@@ -180,24 +167,11 @@
615 {
616 QString _acctName("ubuntuone");
617 AccountIdList _ids = _manager.accountList(_acctName);
618- if (_ids.length() > 0) {
619- if (_ids.length() > 1) {
620- qDebug() << "deleteToken(): Found '" << _ids.length() << "' accounts. Using first.";
621- }
622- Account *account = _manager.account(_ids[0]);
623- qDebug() << "deleteToken(): Using Ubuntu One account '" << _ids[0] << "'.";
624- Identity *identity = Identity::existingIdentity(account->credentialsId());
625- connect(account, SIGNAL(removed()),
626- this, SLOT(handleAccountRemoved()));
627- connect(identity, SIGNAL(error(const SignOn::Error&)),
628- this, SLOT(handleDeleteError(const SignOn::Error&)));
629-
630- identity->remove();
631- account->remove();
632- account->sync();
633- return;
634+ if (_ids.isEmpty()) {
635+ emit tokenNotFound();
636 }
637- emit tokenNotFound();
638+
639+ /* We don't remove accounts anymore */
640 }
641
642 } // namespace UbuntuOne
643
644=== modified file 'libubuntuoneauth/libubuntuoneauth.symbols'
645--- libubuntuoneauth/libubuntuoneauth.symbols 2013-07-22 15:54:02 +0000
646+++ libubuntuoneauth/libubuntuoneauth.symbols 2016-04-22 09:51:32 +0000
647@@ -1,7 +1,8 @@
648 {
649 global:
650 extern "C++" {
651- *UbuntuOne::*;
652+ UbuntuOne::*;
653+ *for?UbuntuOne::*;
654 };
655 qt_*;
656 local:
657
658=== modified file 'libubuntuoneauth/ssoservice.cpp'
659--- libubuntuoneauth/ssoservice.cpp 2015-01-15 21:12:35 +0000
660+++ libubuntuoneauth/ssoservice.cpp 2016-04-22 09:51:32 +0000
661@@ -17,11 +17,13 @@
662 */
663 #include <sys/utsname.h>
664
665+#include <QCoreApplication>
666 #include <QDebug>
667 #include <QtGlobal>
668 #include <QNetworkRequest>
669 #include <QUrlQuery>
670
671+#include "authenticator.h"
672 #include "logging.h"
673 #include "ssoservice.h"
674 #include "requests.h"
675@@ -64,9 +66,6 @@
676 this, SLOT(accountPinged(QNetworkReply*)));
677
678 connect(&(_provider),
679- SIGNAL(OAuthTokenGranted(const OAuthTokenResponse&)),
680- this, SLOT(tokenReceived(const OAuthTokenResponse&)));
681- connect(&(_provider),
682 SIGNAL(AccountGranted(const AccountResponse&)),
683 this, SLOT(accountRegistered(const AccountResponse&)));
684 connect(&(_provider),
685@@ -116,12 +115,40 @@
686
687 void SSOService::login(QString email, QString password, QString twoFactorCode)
688 {
689- OAuthTokenRequest request(getAuthBaseUrl(),
690- email, password,
691- Token::buildTokenName(), twoFactorCode);
692- _tempEmail = email;
693-
694- _provider.GetOAuthToken(request);
695+ using namespace Internal;
696+
697+ auto authenticator = new Authenticator;
698+ /* This is a hack: there should be a public API to decide whether UI
699+ * interactions are allowed.
700+ * For the time being, allow them everywhere except from the account
701+ * plugin (which has its own UI to request all the needed info).
702+ */
703+ if (QCoreApplication::applicationName() == "online-accounts-ui") {
704+ qDebug() << "In account plugin: disabling UI interactions";
705+ authenticator->setUiAllowed(false);
706+ } else {
707+ authenticator->setUiAllowed(true);
708+ }
709+
710+ connect(authenticator, &Authenticator::authenticated,
711+ [=](const Token &token) {
712+ _keyring->storeToken(token, email);
713+ authenticator->deleteLater();
714+ });
715+ connect(authenticator, &Authenticator::error,
716+ [=](Authenticator::ErrorCode code) {
717+ if (code == Authenticator::AccountNotFound) {
718+ Q_EMIT credentialsNotFound();
719+ } else if (code == Authenticator::OneTimePasswordRequired) {
720+ Q_EMIT twoFactorAuthRequired();
721+ } else {
722+ /* TODO: deliver a proper error response. */
723+ Q_EMIT requestFailed(ErrorResponse());
724+ }
725+ authenticator->deleteLater();
726+ });
727+ authenticator->authenticate(Token::buildTokenName(),
728+ email, password, twoFactorCode);
729 }
730
731 void SSOService::handleTwoFactorAuthRequired()
732@@ -147,10 +174,14 @@
733
734 void SSOService::tokenReceived(const OAuthTokenResponse& token)
735 {
736- Token realToken = Token(token.token_key(), token.token_secret(),
737- token.consumer_key(), token.consumer_secret(),
738- token.date_created(), token.date_updated());
739- _keyring->storeToken(realToken, _tempEmail);
740+ // Not used anymore
741+
742+ /* The following two lines are needed to ensure that the
743+ * OAuthTokenRequest::~OAuthTokenRequest() symbol is exported by the
744+ * library.
745+ */
746+ OAuthTokenRequest request;
747+ qDebug() << request.serialize();
748 }
749
750 void SSOService::accountPinged(QNetworkReply*)
751
752=== modified file 'libubuntuoneauth/token.cpp'
753--- libubuntuoneauth/token.cpp 2015-12-07 21:17:00 +0000
754+++ libubuntuoneauth/token.cpp 2016-04-22 09:51:32 +0000
755@@ -46,10 +46,18 @@
756 QString consumer_key, QString consumer_secret)
757 {
758 _tokenHash[TOKEN_NAME_KEY] = buildTokenName();
759- _tokenHash[TOKEN_TOKEN_KEY] = token_key;
760- _tokenHash[TOKEN_TOKEN_SEC_KEY] = token_secret;
761- _tokenHash[TOKEN_CONSUMER_KEY] = consumer_key;
762- _tokenHash[TOKEN_CONSUMER_SEC_KEY] = consumer_secret;
763+ if (!token_key.isEmpty()) {
764+ _tokenHash[TOKEN_TOKEN_KEY] = token_key;
765+ }
766+ if (!token_secret.isEmpty()) {
767+ _tokenHash[TOKEN_TOKEN_SEC_KEY] = token_secret;
768+ }
769+ if (!consumer_key.isEmpty()) {
770+ _tokenHash[TOKEN_CONSUMER_KEY] = consumer_key;
771+ }
772+ if (!consumer_secret.isEmpty()) {
773+ _tokenHash[TOKEN_CONSUMER_SEC_KEY] = consumer_secret;
774+ }
775 }
776
777 Token::Token(QString token_key, QString token_secret,
778@@ -93,9 +101,19 @@
779 }
780
781 /**
782+ * \fn QString Token::name()
783+ *
784+ * Returns the token name, or empty string if not set.
785+ */
786+ QString Token::name() const
787+ {
788+ return _tokenHash.value(TOKEN_NAME_KEY, "");
789+ }
790+
791+ /**
792 * \fn QString Token::consumerKey()
793 *
794- * Retruns a consumer key for this token, or empty string if consumer key is not set.
795+ * Returns a consumer key for this token, or empty string if consumer key is not set.
796 */
797 QString Token::consumerKey() const
798 {
799@@ -103,6 +121,36 @@
800 }
801
802 /**
803+ * \fn QString Token::consumerSecret()
804+ *
805+ * Returns a consumer secret for this token, or empty string if not set.
806+ */
807+ QString Token::consumerSecret() const
808+ {
809+ return _tokenHash.value(TOKEN_CONSUMER_SEC_KEY, "");
810+ }
811+
812+ /**
813+ * \fn QString Token::tokenKey()
814+ *
815+ * Returns a token key for this token, or empty string if token key is not set.
816+ */
817+ QString Token::tokenKey() const
818+ {
819+ return _tokenHash.value(TOKEN_TOKEN_KEY, "");
820+ }
821+
822+ /**
823+ * \fn QString Token::tokenSecret()
824+ *
825+ * Returns a token secret for this token, or empty string if not set.
826+ */
827+ QString Token::tokenSecret() const
828+ {
829+ return _tokenHash.value(TOKEN_TOKEN_SEC_KEY, "");
830+ }
831+
832+ /**
833 * \fn bool Token::isValid()
834 *
835 * Check that the token is valid.
836@@ -291,6 +339,7 @@
837 QStringList params = query.split("&");
838 for (int i = 0; i < params.size(); ++i) {
839 QStringList pair = params.at(i).split("=");
840+ if (pair.count() < 2) continue;
841 if (pair.at(0) == TOKEN_NAME_KEY) {
842 // TODO: Need to figure out how to actually use the
843 // QUrl::fromPercentEncoding at this point in the code.
844
845=== modified file 'libubuntuoneauth/token.h'
846--- libubuntuoneauth/token.h 2015-12-07 21:17:00 +0000
847+++ libubuntuoneauth/token.h 2016-04-22 09:51:32 +0000
848@@ -55,7 +55,11 @@
849
850 static QString dateStringToISO(const QString date);
851
852+ QString name() const;
853 QString consumerKey() const;
854+ QString consumerSecret() const;
855+ QString tokenKey() const;
856+ QString tokenSecret() const;
857
858 private:
859 QHash<QString, QString> _tokenHash;
860
861=== modified file 'signon-plugin/CMakeLists.txt'
862--- signon-plugin/CMakeLists.txt 2014-07-24 13:30:19 +0000
863+++ signon-plugin/CMakeLists.txt 2016-04-22 09:51:32 +0000
864@@ -1,3 +1,8 @@
865+project(SignonPlugin)
866+
867+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
868+set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined")
869+
870 # Qt5 bits
871 SET (CMAKE_INCLUDE_CURRENT_DIR ON)
872 SET (CMAKE_AUTOMOC ON)
873@@ -24,13 +29,15 @@
874 -L${CMAKE_BINARY_DIR}/libubuntuoneauth
875 ${AUTH_LIB_NAME}
876 ${SIGNON_PLUGIN_LDFLAGS}
877+ ${SIGNON_LDFLAGS}
878+ ${SIGNON_LDFLAGS_OTHER}
879 )
880
881-SET (SIGNON_PLUGIN_INSTALL_DIR lib/signon)
882+SET (SIGNON_PLUGIN_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/signon)
883
884 INSTALL (
885 TARGETS ${SIGNON_PLUGIN_NAME}
886 LIBRARY DESTINATION ${SIGNON_PLUGIN_INSTALL_DIR}
887 )
888
889-#add_subdirectory(tests)
890+add_subdirectory(tests)
891
892=== added file 'signon-plugin/i18n.cpp'
893--- signon-plugin/i18n.cpp 1970-01-01 00:00:00 +0000
894+++ signon-plugin/i18n.cpp 2016-04-22 09:51:32 +0000
895@@ -0,0 +1,37 @@
896+/*
897+ * Copyright 2016 Canonical Ltd.
898+ *
899+ * This library is free software; you can redistribute it and/or
900+ * modify it under the terms of version 3 of the GNU Lesser General Public
901+ * License as published by the Free Software Foundation.
902+ *
903+ * This program is distributed in the hope that it will be useful,
904+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
905+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
906+ * General Public License for more details.
907+ *
908+ * You should have received a copy of the GNU Lesser General Public
909+ * License along with this library; if not, write to the
910+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
911+ * Boston, MA 02110-1301, USA.
912+ */
913+
914+#define NO_TR_OVERRIDE
915+#include "i18n.h"
916+
917+#include <libintl.h>
918+
919+namespace UbuntuOne {
920+
921+void initTr(const char *domain, const char *localeDir)
922+{
923+ bindtextdomain(domain, localeDir);
924+ textdomain(domain);
925+}
926+
927+QString _(const char *text, const char *domain)
928+{
929+ return QString::fromUtf8(dgettext(domain, text));
930+}
931+
932+} // namespace
933
934=== added file 'signon-plugin/i18n.h'
935--- signon-plugin/i18n.h 1970-01-01 00:00:00 +0000
936+++ signon-plugin/i18n.h 2016-04-22 09:51:32 +0000
937@@ -0,0 +1,31 @@
938+/*
939+ * Copyright 2016 Canonical Ltd.
940+ *
941+ * This library is free software; you can redistribute it and/or
942+ * modify it under the terms of version 3 of the GNU Lesser General Public
943+ * License as published by the Free Software Foundation.
944+ *
945+ * This program is distributed in the hope that it will be useful,
946+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
947+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
948+ * General Public License for more details.
949+ *
950+ * You should have received a copy of the GNU Lesser General Public
951+ * License along with this library; if not, write to the
952+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
953+ * Boston, MA 02110-1301, USA.
954+ */
955+
956+#ifndef UBUNTUONE_I18N_H
957+#define UBUNTUONE_I18N_H
958+
959+#include <QString>
960+
961+namespace UbuntuOne {
962+
963+void initTr(const char *domain, const char *localeDir);
964+QString _(const char *text, const char *domain = 0);
965+
966+} // namespace
967+
968+#endif // UBUNTUONE_I18N_H
969
970=== added directory 'signon-plugin/tests'
971=== added file 'signon-plugin/tests/CMakeLists.txt'
972--- signon-plugin/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
973+++ signon-plugin/tests/CMakeLists.txt 2016-04-22 09:51:32 +0000
974@@ -0,0 +1,52 @@
975+# The thing we're building in here
976+SET (TESTS_TARGET test-ubuntuone-plugin)
977+
978+# Qt5 bits
979+SET (CMAKE_INCLUDE_CURRENT_DIR ON)
980+SET (CMAKE_AUTOMOC ON)
981+find_package(Qt5Core REQUIRED)
982+
983+pkg_check_modules(SIGNON REQUIRED signon-plugins)
984+add_definitions(
985+ ${SIGNON_CFLAGS}
986+ ${SIGNON_CFLAGS_OTHER}
987+ "-DPLUGIN_PATH=\"${CMAKE_CURRENT_BINARY_DIR}/../libubuntuoneplugin.so\""
988+)
989+
990+# Workaround for cmake not adding these to automoc properly
991+SET (CMAKE_AUTOMOC_MOC_OPTIONS "${SIGNON_CFLAGS} ${CMAKE_AUTOMOC_MOC_OPTIONS}")
992+
993+# The sources for building the tests
994+SET (SOURCES
995+ ${SignonPlugin_SOURCE_DIR}/i18n.cpp
996+ ${SignonPlugin_SOURCE_DIR}/ubuntuone-plugin.cpp
997+ tst_plugin.cpp
998+)
999+
1000+include_directories(
1001+ ${SignonPlugin_SOURCE_DIR}
1002+)
1003+
1004+add_executable (${TESTS_TARGET} ${SOURCES})
1005+qt5_use_modules (${TESTS_TARGET} Test Network)
1006+target_link_libraries (${TESTS_TARGET}
1007+ -Wl,-rpath,${CMAKE_BINARY_DIR}/libubuntuoneauth
1008+ -L${CMAKE_BINARY_DIR}/libubuntuoneauth
1009+ ${AUTH_LIB_NAME}
1010+ ${SIGNON_LDFLAGS}
1011+)
1012+
1013+add_custom_target(ubuntuone-plugin-tests
1014+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET}
1015+ DEPENDS ${TESTS_TARGET}
1016+)
1017+
1018+add_custom_target(ubuntuone-plugin-tests-valgrind
1019+ COMMAND valgrind --tool=memcheck ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET}
1020+ DEPENDS ${TESTS_TARGET}
1021+)
1022+
1023+add_custom_target(ubuntuone-plugin-tests-valgrind-leaks
1024+ COMMAND valgrind --tool=memcheck --track-origins=yes --num-callers=40 --leak-resolution=high --leak-check=full ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET}
1025+ DEPENDS ${TESTS_TARGET}
1026+)
1027
1028=== added file 'signon-plugin/tests/tst_plugin.cpp'
1029--- signon-plugin/tests/tst_plugin.cpp 1970-01-01 00:00:00 +0000
1030+++ signon-plugin/tests/tst_plugin.cpp 2016-04-22 09:51:32 +0000
1031@@ -0,0 +1,1840 @@
1032+/*
1033+ * Copyright 2016 Canonical Ltd.
1034+ *
1035+ * This library is free software; you can redistribute it and/or
1036+ * modify it under the terms of version 3 of the GNU Lesser General Public
1037+ * License as published by the Free Software Foundation.
1038+ *
1039+ * This program is distributed in the hope that it will be useful,
1040+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1041+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1042+ * General Public License for more details.
1043+ *
1044+ * You should have received a copy of the GNU Lesser General Public
1045+ * License along with this library; if not, write to the
1046+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1047+ * Boston, MA 02110-1301, USA.
1048+ */
1049+
1050+#include <QJsonDocument>
1051+#include <QJsonObject>
1052+#include <QNetworkAccessManager>
1053+#include <QNetworkReply>
1054+#include <QPointer>
1055+#include <QRegExp>
1056+#include <QSignalSpy>
1057+#include <QTimer>
1058+#include <QtTest/QtTest>
1059+
1060+#include <SignOn/uisessiondata_priv.h>
1061+
1062+#include "ubuntuone-plugin.h"
1063+
1064+using namespace SignOn;
1065+
1066+namespace QTest {
1067+template<>
1068+char *toString(const QVariantMap &map)
1069+{
1070+ QJsonDocument doc(QJsonObject::fromVariantMap(map));
1071+ return qstrdup(doc.toJson(QJsonDocument::Compact).data());
1072+}
1073+} // QTest namespace
1074+
1075+class TestNetworkReply: public QNetworkReply
1076+{
1077+ Q_OBJECT
1078+
1079+public:
1080+ TestNetworkReply(QObject *parent = 0):
1081+ QNetworkReply(parent),
1082+ m_offset(0)
1083+ {}
1084+
1085+ void setError(NetworkError errorCode, const QString &errorString,
1086+ int delay = -1) {
1087+ QNetworkReply::setError(errorCode, errorString);
1088+ if (delay > 0) {
1089+ QTimer::singleShot(delay, this, SLOT(fail()));
1090+ }
1091+ }
1092+
1093+ void setRawHeader(const QByteArray &headerName, const QByteArray &value) {
1094+ QNetworkReply::setRawHeader(headerName, value);
1095+ }
1096+
1097+ void setContentType(const QString &contentType) {
1098+ setRawHeader("Content-Type", contentType.toUtf8());
1099+ }
1100+
1101+ void setStatusCode(int statusCode) {
1102+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1103+ }
1104+
1105+ void setContent(const QByteArray &content) {
1106+ m_content = content;
1107+ m_offset = 0;
1108+
1109+ open(ReadOnly | Unbuffered);
1110+ setHeader(QNetworkRequest::ContentLengthHeader, QVariant(content.size()));
1111+ }
1112+
1113+ void start() {
1114+ QTimer::singleShot(0, this, SIGNAL(readyRead()));
1115+ QTimer::singleShot(10, this, SLOT(finish()));
1116+ }
1117+
1118+public Q_SLOTS:
1119+ void finish() { setFinished(true); Q_EMIT finished(); }
1120+ void fail() { Q_EMIT error(error()); }
1121+
1122+protected:
1123+ void abort() Q_DECL_OVERRIDE {}
1124+ qint64 bytesAvailable() const Q_DECL_OVERRIDE {
1125+ return m_content.size() - m_offset + QIODevice::bytesAvailable();
1126+ }
1127+
1128+ bool isSequential() const Q_DECL_OVERRIDE { return true; }
1129+ qint64 readData(char *data, qint64 maxSize) Q_DECL_OVERRIDE {
1130+ if (m_offset >= m_content.size())
1131+ return -1;
1132+ qint64 number = qMin(maxSize, m_content.size() - m_offset);
1133+ memcpy(data, m_content.constData() + m_offset, number);
1134+ m_offset += number;
1135+ return number;
1136+ }
1137+
1138+private:
1139+ QByteArray m_content;
1140+ qint64 m_offset;
1141+};
1142+
1143+class TestNetworkAccessManager: public QNetworkAccessManager
1144+{
1145+ Q_OBJECT
1146+
1147+public:
1148+ TestNetworkAccessManager(): QNetworkAccessManager() {}
1149+
1150+ void setNextReply(TestNetworkReply *reply) { m_nextReply = reply; }
1151+
1152+protected:
1153+ QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
1154+ QIODevice *outgoingData = 0) Q_DECL_OVERRIDE {
1155+ Q_UNUSED(op);
1156+ m_lastRequest = request;
1157+ m_lastRequestData = outgoingData->readAll();
1158+ m_nextReply->start();
1159+ return m_nextReply;
1160+ }
1161+
1162+public:
1163+ QPointer<TestNetworkReply> m_nextReply;
1164+ QNetworkRequest m_lastRequest;
1165+ QByteArray m_lastRequestData;
1166+};
1167+
1168+class PluginTest: public QObject
1169+{
1170+ Q_OBJECT
1171+
1172+private Q_SLOTS:
1173+ void initTestCase();
1174+ void cleanupTestCase();
1175+
1176+ void testLoading();
1177+ void testInitialization();
1178+ void testPluginType();
1179+ void testPluginMechanisms();
1180+ void testStoredToken_data();
1181+ void testStoredToken();
1182+ void testUserInteraction();
1183+ void testTokenCreation_data();
1184+ void testTokenCreation();
1185+
1186+ void init();
1187+ void cleanup();
1188+
1189+private:
1190+ UbuntuOne::SignOnPlugin *m_testPlugin;
1191+};
1192+
1193+void PluginTest::initTestCase()
1194+{
1195+ qRegisterMetaType<SignOn::SessionData>();
1196+ qRegisterMetaType<SignOn::UiSessionData>();
1197+ qRegisterMetaType<SignOn::Error>();
1198+}
1199+
1200+void PluginTest::cleanupTestCase()
1201+{
1202+}
1203+
1204+// prepare each test by creating new plugin
1205+void PluginTest::init()
1206+{
1207+ m_testPlugin = new UbuntuOne::SignOnPlugin();
1208+}
1209+
1210+// finish each test by deleting plugin
1211+void PluginTest::cleanup()
1212+{
1213+ delete m_testPlugin;
1214+ m_testPlugin = 0;
1215+}
1216+
1217+void PluginTest::testLoading()
1218+{
1219+ QLibrary module(PLUGIN_PATH);
1220+ if (!module.load()) {
1221+ qDebug() << "Failed to load module:" << module.errorString();
1222+ }
1223+
1224+ QVERIFY(module.isLoaded());
1225+ typedef AuthPluginInterface *(*AuthPluginInstanceF)();
1226+ auto instance =
1227+ (AuthPluginInstanceF)module.resolve("auth_plugin_instance");
1228+ QVERIFY(instance);
1229+
1230+ auto plugin = qobject_cast<AuthPluginInterface *>(instance());
1231+ QVERIFY(plugin);
1232+}
1233+
1234+void PluginTest::testInitialization()
1235+{
1236+ QVERIFY(m_testPlugin);
1237+}
1238+
1239+void PluginTest::testPluginType()
1240+{
1241+ QCOMPARE(m_testPlugin->type(), QString("ubuntuone"));
1242+}
1243+
1244+void PluginTest::testPluginMechanisms()
1245+{
1246+ QStringList mechs = m_testPlugin->mechanisms();
1247+ QCOMPARE(mechs.count(), 1);
1248+ QCOMPARE(mechs[0], QString("ubuntuone"));
1249+}
1250+
1251+void PluginTest::testStoredToken_data()
1252+{
1253+ QTest::addColumn<QVariantMap>("sessionData");
1254+ QTest::addColumn<int>("networkError");
1255+ QTest::addColumn<int>("httpStatus");
1256+ QTest::addColumn<QString>("replyContents");
1257+ QTest::addColumn<int>("expectedErrorCode");
1258+ QTest::addColumn<bool>("uiExpected");
1259+ QTest::addColumn<QVariantMap>("expectedResponse");
1260+ QTest::addColumn<QVariantMap>("expectedStore");
1261+
1262+ UbuntuOne::PluginData sessionData;
1263+ UbuntuOne::PluginData response;
1264+ UbuntuOne::PluginData stored;
1265+
1266+ QTest::newRow("empty") <<
1267+ sessionData.toMap() <<
1268+ -1 << -1 << QString() <<
1269+ int(Error::MissingData) <<
1270+ false << QVariantMap() << QVariantMap();
1271+
1272+ sessionData.setTokenName("helloworld");
1273+ sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd");
1274+ response.setConsumerKey("aAa");
1275+ response.setConsumerSecret("bBb");
1276+ response.setTokenKey("cCc");
1277+ response.setTokenSecret("dDd");
1278+ QVariantMap storedData;
1279+ storedData[sessionData.TokenName()] = response.toMap();
1280+ stored.setStoredData(storedData);
1281+ response.setTokenName(sessionData.TokenName());
1282+ QTest::newRow("in secret, valid") <<
1283+ sessionData.toMap() <<
1284+ -1 <<
1285+ 200 << QString("{\n"
1286+ " \"is_valid\": true,\n"
1287+ " \"identifier\": \"64we8bn\",\n"
1288+ " \"account_verified\": true\n"
1289+ "}") <<
1290+ -1 <<
1291+ false << response.toMap() << stored.toMap();
1292+ sessionData = UbuntuOne::PluginData();
1293+ response = UbuntuOne::PluginData();
1294+ stored = UbuntuOne::PluginData();
1295+ storedData.clear();
1296+
1297+ sessionData.setTokenName("helloworld");
1298+ sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd");
1299+ response.setConsumerKey("aAa");
1300+ response.setConsumerSecret("bBb");
1301+ response.setTokenKey("cCc");
1302+ response.setTokenSecret("dDd");
1303+ storedData[sessionData.TokenName()] = response.toMap();
1304+ stored.setStoredData(storedData);
1305+ response = UbuntuOne::PluginData();
1306+ QTest::newRow("in secret, invalid") <<
1307+ sessionData.toMap() <<
1308+ -1 <<
1309+ 200 << QString("{\n"
1310+ " \"is_valid\": false,\n"
1311+ " \"identifier\": \"64we8bn\",\n"
1312+ " \"account_verified\": true\n"
1313+ "}") <<
1314+ -1 <<
1315+ true << response.toMap() << stored.toMap();
1316+ sessionData = UbuntuOne::PluginData();
1317+ response = UbuntuOne::PluginData();
1318+ stored = UbuntuOne::PluginData();
1319+ storedData.clear();
1320+
1321+ sessionData.setTokenName("helloworld");
1322+ sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd");
1323+ response.setConsumerKey("aAa");
1324+ response.setConsumerSecret("bBb");
1325+ response.setTokenKey("cCc");
1326+ response.setTokenSecret("dDd");
1327+ storedData[sessionData.TokenName()] = response.toMap();
1328+ stored.setStoredData(storedData);
1329+ response = UbuntuOne::PluginData();
1330+ QTest::newRow("in secret, network error") <<
1331+ sessionData.toMap() <<
1332+ int(QNetworkReply::SslHandshakeFailedError) <<
1333+ -1 << QString() <<
1334+ int(SignOn::Error::Ssl) <<
1335+ true << response.toMap() << stored.toMap();
1336+ sessionData = UbuntuOne::PluginData();
1337+ response = UbuntuOne::PluginData();
1338+ stored = UbuntuOne::PluginData();
1339+ storedData.clear();
1340+}
1341+
1342+void PluginTest::testStoredToken()
1343+{
1344+ QFETCH(QVariantMap, sessionData);
1345+ QFETCH(int, httpStatus);
1346+ QFETCH(int, networkError);
1347+ QFETCH(QString, replyContents);
1348+ QFETCH(int, expectedErrorCode);
1349+ QFETCH(bool, uiExpected);
1350+ QFETCH(QVariantMap, expectedResponse);
1351+ QFETCH(QVariantMap, expectedStore);
1352+
1353+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
1354+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
1355+ QSignalSpy userActionRequired(m_testPlugin,
1356+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
1357+ QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
1358+
1359+ /* Prepare network reply */
1360+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
1361+ m_testPlugin->m_networkAccessManager = nam;
1362+ TestNetworkReply *reply = new TestNetworkReply(this);
1363+ if (httpStatus > 0) {
1364+ reply->setStatusCode(httpStatus);
1365+ } else {
1366+ reply->setError(QNetworkReply::NetworkError(networkError),
1367+ "Network error");
1368+ }
1369+ reply->setContent(replyContents.toUtf8());
1370+ nam->setNextReply(reply);
1371+
1372+
1373+ m_testPlugin->process(sessionData, "ubuntuone");
1374+ if (expectedErrorCode < 0) {
1375+ QCOMPARE(error.count(), 0);
1376+ QTRY_COMPARE(userActionRequired.count(), uiExpected ? 1 : 0);
1377+ if (!expectedResponse.isEmpty()) {
1378+ QTRY_COMPARE(result.count(), 1);
1379+ QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap();
1380+ QCOMPARE(resp, expectedResponse);
1381+ } else {
1382+ QCOMPARE(result.count(), 0);
1383+ }
1384+
1385+ if (!expectedStore.isEmpty()) {
1386+ QCOMPARE(store.count(), 1);
1387+ QVariantMap storedData =
1388+ store.at(0).at(0).value<SessionData>().toMap();
1389+ QCOMPARE(storedData, expectedStore);
1390+ } else {
1391+ QCOMPARE(store.count(), 0);
1392+ }
1393+ } else {
1394+ QTRY_COMPARE(error.count(), 1);
1395+ Error err = error.at(0).at(0).value<Error>();
1396+ QCOMPARE(err.type(), expectedErrorCode);
1397+ }
1398+}
1399+
1400+void PluginTest::testUserInteraction()
1401+{
1402+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
1403+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
1404+ QSignalSpy userActionRequired(m_testPlugin,
1405+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
1406+ QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
1407+
1408+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
1409+ m_testPlugin->m_networkAccessManager = nam;
1410+
1411+ UbuntuOne::PluginData sessionData;
1412+ sessionData.setTokenName("helloworld");
1413+ sessionData.setUserName("tom@example.com");
1414+ m_testPlugin->process(sessionData, "ubuntuone");
1415+
1416+ QTRY_COMPARE(userActionRequired.count(), 1);
1417+ QVariantMap data =
1418+ userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
1419+ QVariantMap expectedUserInteraction;
1420+ expectedUserInteraction[SSOUI_KEY_QUERYUSERNAME] = true;
1421+ expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com";
1422+ expectedUserInteraction[SSOUI_KEY_QUERYPASSWORD] = true;
1423+ QCOMPARE(data, expectedUserInteraction);
1424+ userActionRequired.clear();
1425+
1426+ /* Prepare network reply */
1427+ TestNetworkReply *reply = new TestNetworkReply(this);
1428+ reply->setStatusCode(401);
1429+ reply->setContent("{\n"
1430+ " \"code\": \"TWOFACTOR_REQUIRED\",\n"
1431+ " \"message\": \"This account requires 2-factor authentication.\",\n"
1432+ " \"extra\": {}\n"
1433+ "}");
1434+ nam->setNextReply(reply);
1435+
1436+ QVariantMap userReply;
1437+ userReply[SSOUI_KEY_USERNAME] = "tom@example.com";
1438+ userReply[SSOUI_KEY_PASSWORD] = "s3cr3t";
1439+ m_testPlugin->userActionFinished(userReply);
1440+
1441+ /* Again the plugin should request user interaction, as OTP is required */
1442+ QTRY_COMPARE(userActionRequired.count(), 1);
1443+ data = userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
1444+ expectedUserInteraction.clear();
1445+ expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com";
1446+ expectedUserInteraction[SSOUI_KEY_PASSWORD] = "s3cr3t";
1447+ expectedUserInteraction[SSOUI_KEY_QUERY2FA] = true;
1448+ /* We want the map to contain the SSOUI_KEY_2FA_TEXT, but we don't care
1449+ * about the value */
1450+ QVERIFY(data.contains(SSOUI_KEY_2FA_TEXT));
1451+ data.remove(SSOUI_KEY_2FA_TEXT);
1452+ QCOMPARE(data, expectedUserInteraction);
1453+}
1454+
1455+void PluginTest::testTokenCreation_data()
1456+{
1457+ QTest::addColumn<QVariantMap>("sessionData");
1458+ QTest::addColumn<int>("networkError");
1459+ QTest::addColumn<int>("httpStatus");
1460+ QTest::addColumn<QString>("replyContents");
1461+ QTest::addColumn<int>("expectedErrorCode");
1462+ QTest::addColumn<QVariantMap>("expectedResponse");
1463+ QTest::addColumn<QVariantMap>("expectedStore");
1464+ QTest::addColumn<QVariantMap>("expectedUserInteraction");
1465+
1466+ UbuntuOne::PluginData sessionData;
1467+ UbuntuOne::PluginData response;
1468+ UbuntuOne::PluginData stored;
1469+ QVariantMap userInteraction;
1470+
1471+ // Successful creation, with password only
1472+ sessionData.setTokenName("helloworld");
1473+ sessionData.setUserName("jim@example.com");
1474+ sessionData.setSecret("s3cr3t");
1475+ response.setConsumerKey("aAa");
1476+ response.setConsumerSecret("bBb");
1477+ response.setTokenKey("cCc");
1478+ response.setTokenSecret("dDd");
1479+ QVariantMap storedData;
1480+ storedData[sessionData.TokenName()] = response.toMap();
1481+ stored.setStoredData(storedData);
1482+ response.setTokenName(sessionData.TokenName());
1483+ QTest::newRow("no OTP needed, 201") <<
1484+ sessionData.toMap() <<
1485+ -1 <<
1486+ 201 << QString("{\n"
1487+ " \"href\": \"https://login.ubuntu.com/api/v2/tokens/oauth/the-key\",\n"
1488+ " \"token_key\": \"cCc\",\n"
1489+ " \"token_secret\": \"dDd\",\n"
1490+ " \"token_name\": \"helloworld\",\n"
1491+ " \"consumer_key\": \"aAa\",\n"
1492+ " \"consumer_secret\": \"bBb\",\n"
1493+ " \"date_created\": \"2013-01-11 12:43:23\",\n"
1494+ " \"date_updated\": \"2013-01-11 12:43:23\"\n"
1495+ "}") <<
1496+ -1 <<
1497+ response.toMap() << stored.toMap() << userInteraction;
1498+ sessionData = UbuntuOne::PluginData();
1499+ response = UbuntuOne::PluginData();
1500+ stored = UbuntuOne::PluginData();
1501+ storedData.clear();
1502+
1503+ // Wrong password
1504+ sessionData.setTokenName("helloworld");
1505+ sessionData.setUserName("jim@example.com");
1506+ sessionData.setSecret("s3cr3t");
1507+ userInteraction[SSOUI_KEY_QUERYUSERNAME] = true;
1508+ userInteraction[SSOUI_KEY_USERNAME] = "jim@example.com";
1509+ userInteraction[SSOUI_KEY_QUERYPASSWORD] = true;
1510+ QTest::newRow("wrong password") <<
1511+ sessionData.toMap() <<
1512+ -1 <<
1513+ 401 << QString("{\n"
1514+ " \"code\": \"INVALID_CREDENTIALS\",\n"
1515+ " \"message\": \"Wrong password!\",\n"
1516+ " \"extra\": {}\n"
1517+ "}") <<
1518+ -1 <<
1519+ response.toMap() << stored.toMap() << userInteraction;
1520+ sessionData = UbuntuOne::PluginData();
1521+ userInteraction.clear();
1522+
1523+ // Network error while creating token
1524+ sessionData.setTokenName("helloworld");
1525+ sessionData.setUserName("jim@example.com");
1526+ sessionData.setSecret("s3cr3t");
1527+ QTest::newRow("network error") <<
1528+ sessionData.toMap() <<
1529+ int(QNetworkReply::SslHandshakeFailedError) <<
1530+ -1 << QString() <<
1531+ int(SignOn::Error::Ssl) <<
1532+ response.toMap() << stored.toMap() << userInteraction;
1533+ sessionData = UbuntuOne::PluginData();
1534+
1535+ // Account needs reset
1536+ sessionData.setTokenName("helloworld");
1537+ sessionData.setUserName("jim@example.com");
1538+ sessionData.setSecret("s3cr3t");
1539+ userInteraction[SSOUI_KEY_OPENURL] = "http://www.example.com/reset";
1540+ QTest::newRow("reset needed") <<
1541+ sessionData.toMap() <<
1542+ -1 <<
1543+ 403 << QString("{\n"
1544+ " \"code\": \"PASSWORD_POLICY_ERROR\",\n"
1545+ " \"message\": \"Password too short\",\n"
1546+ " \"extra\": {\n"
1547+ " \"location\": \"http://www.example.com/reset\"\n"
1548+ " }\n"
1549+ "}") <<
1550+ -1 <<
1551+ response.toMap() << stored.toMap() << userInteraction;
1552+ sessionData = UbuntuOne::PluginData();
1553+ userInteraction.clear();
1554+}
1555+
1556+void PluginTest::testTokenCreation()
1557+{
1558+ QFETCH(QVariantMap, sessionData);
1559+ QFETCH(int, httpStatus);
1560+ QFETCH(int, networkError);
1561+ QFETCH(QString, replyContents);
1562+ QFETCH(int, expectedErrorCode);
1563+ QFETCH(QVariantMap, expectedResponse);
1564+ QFETCH(QVariantMap, expectedStore);
1565+ QFETCH(QVariantMap, expectedUserInteraction);
1566+
1567+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
1568+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
1569+ QSignalSpy userActionRequired(m_testPlugin,
1570+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
1571+ QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
1572+
1573+ /* Prepare network reply */
1574+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
1575+ m_testPlugin->m_networkAccessManager = nam;
1576+ TestNetworkReply *reply = new TestNetworkReply(this);
1577+ if (httpStatus > 0) {
1578+ reply->setStatusCode(httpStatus);
1579+ } else {
1580+ reply->setError(QNetworkReply::NetworkError(networkError),
1581+ "Network error");
1582+ }
1583+ reply->setContent(replyContents.toUtf8());
1584+ nam->setNextReply(reply);
1585+
1586+
1587+ m_testPlugin->process(sessionData, "ubuntuone");
1588+ if (expectedErrorCode < 0) {
1589+ if (!expectedUserInteraction.isEmpty()) {
1590+ QTRY_COMPARE(userActionRequired.count(), 1);
1591+ QVariantMap data =
1592+ userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
1593+ QCOMPARE(data, expectedUserInteraction);
1594+ } else {
1595+ QCOMPARE(userActionRequired.count(), 0);
1596+ }
1597+
1598+ if (!expectedResponse.isEmpty()) {
1599+ QTRY_COMPARE(result.count(), 1);
1600+ QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap();
1601+ QCOMPARE(resp, expectedResponse);
1602+ } else {
1603+ QCOMPARE(result.count(), 0);
1604+ }
1605+
1606+ if (!expectedStore.isEmpty()) {
1607+ QCOMPARE(store.count(), 1);
1608+ QVariantMap storedData =
1609+ store.at(0).at(0).value<SessionData>().toMap();
1610+ QCOMPARE(storedData, expectedStore);
1611+ } else {
1612+ QCOMPARE(store.count(), 0);
1613+ }
1614+
1615+ QCOMPARE(error.count(), 0);
1616+ } else {
1617+ QTRY_COMPARE(error.count(), 1);
1618+ Error err = error.at(0).at(0).value<Error>();
1619+ QCOMPARE(err.type(), expectedErrorCode);
1620+ }
1621+}
1622+
1623+#if 0
1624+void PluginTest::testPluginHmacSha1Process_data()
1625+{
1626+ QTest::addColumn<QString>("mechanism");
1627+ QTest::addColumn<QVariantMap>("sessionData");
1628+ QTest::addColumn<int>("replyStatusCode");
1629+ QTest::addColumn<QString>("replyContentType");
1630+ QTest::addColumn<QString>("replyContents");
1631+ QTest::addColumn<int>("errorCode");
1632+ QTest::addColumn<bool>("uiExpected");
1633+ QTest::addColumn<QVariantMap>("response");
1634+ QTest::addColumn<QVariantMap>("stored");
1635+
1636+ OAuth1PluginData hmacSha1Data;
1637+ hmacSha1Data.setRequestEndpoint("https://localhost/oauth/request_token");
1638+ hmacSha1Data.setTokenEndpoint("https://localhost/oauth/access_token");
1639+ hmacSha1Data.setAuthorizationEndpoint("https://localhost/oauth/authorize");
1640+ hmacSha1Data.setCallback("https://localhost/connect/login_success.html");
1641+ hmacSha1Data.setConsumerKey("104660106251471");
1642+ hmacSha1Data.setConsumerSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
1643+ hmacSha1Data.setRealm("MyHost");
1644+
1645+ QTest::newRow("invalid mechanism") <<
1646+ "ANONYMOUS" <<
1647+ hmacSha1Data.toMap() <<
1648+ int(200) << "" << "" <<
1649+ int(Error::MechanismNotAvailable) <<
1650+ false << QVariantMap() << QVariantMap();
1651+
1652+ // Try without params
1653+ hmacSha1Data.setAuthorizationEndpoint(QString());
1654+ QTest::newRow("without params, HMAC-SHA1") <<
1655+ "HMAC-SHA1" <<
1656+ hmacSha1Data.toMap() <<
1657+ int(200) << "" << "" <<
1658+ int(Error::MissingData) <<
1659+ false << QVariantMap() << QVariantMap();
1660+
1661+ // Check for signon UI request for HMAC-SHA1
1662+ hmacSha1Data.setAuthorizationEndpoint("https://localhost/oauth/authorize");
1663+ QTest::newRow("ui-request, HMAC-SHA1") <<
1664+ "HMAC-SHA1" <<
1665+ hmacSha1Data.toMap() <<
1666+ int(200) << "text/plain" <<
1667+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1668+ -1 <<
1669+ true << QVariantMap() << QVariantMap();
1670+
1671+ QTest::newRow("ui-request, PLAINTEXT") <<
1672+ "PLAINTEXT" <<
1673+ hmacSha1Data.toMap() <<
1674+ int(200) << "text/plain" <<
1675+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1676+ -1 <<
1677+ true << QVariantMap() << QVariantMap();
1678+
1679+ /* Now store some tokens and test the responses */
1680+ hmacSha1Data.m_data.insert("UiPolicy", NoUserInteractionPolicy);
1681+ QVariantMap tokens; // ConsumerKey to Token map
1682+ QVariantMap token;
1683+ token.insert("oauth_token", QLatin1String("hmactokenfromtest"));
1684+ token.insert("oauth_token_secret", QLatin1String("hmacsecretfromtest"));
1685+ token.insert("timestamp", QDateTime::currentDateTime().toTime_t());
1686+ token.insert("Expiry", (uint)50000);
1687+ tokens.insert(QLatin1String("invalidid"), QVariant::fromValue(token));
1688+ hmacSha1Data.m_data.insert(QLatin1String("Tokens"), tokens);
1689+
1690+ // Try without cached token for our ConsumerKey
1691+ QTest::newRow("cached tokens, no ConsumerKey") <<
1692+ "HMAC-SHA1" <<
1693+ hmacSha1Data.toMap() <<
1694+ int(200) << "text/plain" <<
1695+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1696+ -1 <<
1697+ true << QVariantMap() << QVariantMap();
1698+
1699+ // Ensure that the cached token is returned as required
1700+ tokens.insert(hmacSha1Data.ConsumerKey(), QVariant::fromValue(token));
1701+ hmacSha1Data.m_data.insert(QLatin1String("Tokens"), tokens);
1702+ QVariantMap response;
1703+ response.insert("AccessToken", QLatin1String("hmactokenfromtest"));
1704+ QTest::newRow("cached tokens, with ConsumerKey") <<
1705+ "HMAC-SHA1" <<
1706+ hmacSha1Data.toMap() <<
1707+ int(200) << "" << "" <<
1708+ -1 <<
1709+ false << response << QVariantMap();
1710+
1711+ hmacSha1Data.m_data.insert("UiPolicy", RequestPasswordPolicy);
1712+ QTest::newRow("cached tokens, request password policy") <<
1713+ "HMAC-SHA1" <<
1714+ hmacSha1Data.toMap() <<
1715+ int(200) << "text/plain" <<
1716+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1717+ -1 <<
1718+ true << QVariantMap() << QVariantMap();
1719+ hmacSha1Data.m_data.remove("UiPolicy");
1720+
1721+ hmacSha1Data.setForceTokenRefresh(true);
1722+ QTest::newRow("cached tokens, force refresh") <<
1723+ "HMAC-SHA1" <<
1724+ hmacSha1Data.toMap() <<
1725+ int(200) << "text/plain" <<
1726+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1727+ -1 <<
1728+ true << QVariantMap() << QVariantMap();
1729+ hmacSha1Data.setForceTokenRefresh(false);
1730+
1731+ token.insert("timestamp", QDateTime::currentDateTime().toTime_t() - 50000);
1732+ token.insert("Expiry", (uint)100);
1733+ tokens.insert(hmacSha1Data.ConsumerKey(), QVariant::fromValue(token));
1734+ hmacSha1Data.m_data.insert(QLatin1String("Tokens"), tokens);
1735+ QTest::newRow("cached tokens, expired") <<
1736+ "HMAC-SHA1" <<
1737+ hmacSha1Data.toMap() <<
1738+ int(200) << "text/plain" <<
1739+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1740+ -1 <<
1741+ true << QVariantMap() << QVariantMap();
1742+
1743+ /* test the ProvidedTokens semantics */
1744+ OAuth1PluginData providedTokensHmacSha1Data;
1745+ providedTokensHmacSha1Data.setRequestEndpoint("https://localhost/oauth/request_token");
1746+ providedTokensHmacSha1Data.setTokenEndpoint("https://localhost/oauth/access_token");
1747+ providedTokensHmacSha1Data.setAuthorizationEndpoint("https://localhost/oauth/authorize");
1748+ providedTokensHmacSha1Data.setCallback("https://localhost/connect/login_success.html");
1749+ providedTokensHmacSha1Data.setConsumerKey("104660106251471");
1750+ providedTokensHmacSha1Data.setConsumerSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
1751+ QVariantMap providedTokens;
1752+ providedTokens.insert("AccessToken", "providedhmactokenfromtest");
1753+ providedTokens.insert("TokenSecret", "providedhmacsecretfromtest");
1754+ providedTokens.insert("ScreenName", "providedhmacscreennamefromtest");
1755+ providedTokens.insert("UserId", "providedUserId");
1756+
1757+ // try providing tokens to be stored
1758+ providedTokensHmacSha1Data.m_data.insert("ProvidedTokens", providedTokens);
1759+ QVariantMap storedTokensForKey;
1760+ storedTokensForKey.insert("oauth_token", providedTokens.value("AccessToken"));
1761+ storedTokensForKey.insert("oauth_token_secret", providedTokens.value("TokenSecret"));
1762+ QVariantMap storedTokens;
1763+ storedTokens.insert(providedTokensHmacSha1Data.ConsumerKey(), storedTokensForKey);
1764+ QVariantMap stored;
1765+ stored.insert("Tokens", storedTokens);
1766+ QTest::newRow("provided tokens") <<
1767+ "HMAC-SHA1" <<
1768+ providedTokensHmacSha1Data.toMap() <<
1769+ int(200) << "" << "" <<
1770+ -1 <<
1771+ false << providedTokens << stored;
1772+
1773+ QTest::newRow("http error 401") <<
1774+ "HMAC-SHA1" <<
1775+ hmacSha1Data.toMap() <<
1776+ int(401) << "text/plain" <<
1777+ "oauth_token=HiThere&oauth_token_secret=BigSecret" <<
1778+ int(Error::OperationFailed) <<
1779+ false << QVariantMap() << QVariantMap();
1780+
1781+ QTest::newRow("no content returned") <<
1782+ "HMAC-SHA1" <<
1783+ hmacSha1Data.toMap() <<
1784+ int(200) << "" << "" <<
1785+ int(Error::OperationFailed) <<
1786+ false << QVariantMap() << QVariantMap();
1787+
1788+ QTest::newRow("no token returned") <<
1789+ "HMAC-SHA1" <<
1790+ hmacSha1Data.toMap() <<
1791+ int(200) << "text/plain" <<
1792+ "oauth_token=HiThere" <<
1793+ int(Error::OperationFailed) <<
1794+ false << QVariantMap() << QVariantMap();
1795+
1796+ /* Test handling of oauth_problem; this is a non standard extension:
1797+ * http://wiki.oauth.net/w/page/12238543/ProblemReporting
1798+ * https://developer.yahoo.com/oauth/guide/oauth-errors.html
1799+ */
1800+ QTest::newRow("problem user_refused") <<
1801+ "HMAC-SHA1" <<
1802+ hmacSha1Data.toMap() <<
1803+ int(400) << "text/plain" <<
1804+ "oauth_problem=user_refused" <<
1805+ int(Error::PermissionDenied) <<
1806+ false << QVariantMap() << QVariantMap();
1807+ QTest::newRow("problem permission_denied") <<
1808+ "HMAC-SHA1" <<
1809+ hmacSha1Data.toMap() <<
1810+ int(400) << "text/plain" <<
1811+ "oauth_problem=permission_denied" <<
1812+ int(Error::PermissionDenied) <<
1813+ false << QVariantMap() << QVariantMap();
1814+ QTest::newRow("problem signature_invalid") <<
1815+ "HMAC-SHA1" <<
1816+ hmacSha1Data.toMap() <<
1817+ int(400) << "text/plain" <<
1818+ "oauth_problem=signature_invalid" <<
1819+ int(Error::OperationFailed) <<
1820+ false << QVariantMap() << QVariantMap();
1821+}
1822+
1823+void PluginTest::testPluginHmacSha1Process()
1824+{
1825+ QFETCH(QString, mechanism);
1826+ QFETCH(QVariantMap, sessionData);
1827+ QFETCH(int, replyStatusCode);
1828+ QFETCH(QString, replyContentType);
1829+ QFETCH(QString, replyContents);
1830+ QFETCH(int, errorCode);
1831+ QFETCH(bool, uiExpected);
1832+ QFETCH(QVariantMap, response);
1833+ QFETCH(QVariantMap, stored);
1834+
1835+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
1836+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
1837+ QSignalSpy userActionRequired(m_testPlugin,
1838+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
1839+ QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
1840+
1841+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
1842+ m_testPlugin->m_networkAccessManager = nam;
1843+ TestNetworkReply *reply = new TestNetworkReply(this);
1844+ reply->setStatusCode(replyStatusCode);
1845+ if (!replyContentType.isEmpty()) {
1846+ reply->setContentType(replyContentType);
1847+ }
1848+ reply->setContent(replyContents.toUtf8());
1849+ nam->setNextReply(reply);
1850+
1851+ m_testPlugin->process(sessionData, mechanism);
1852+
1853+ QTRY_COMPARE(result.count(), response.isEmpty() ? 0 : 1);
1854+ /* In the test data sometimes we don't specify the expected stored data,
1855+ * but this doesn't mean that store() shouldn't be emitted. */
1856+ if (!stored.isEmpty()) { QTRY_COMPARE(store.count(), 1); }
1857+ QTRY_COMPARE(userActionRequired.count(), uiExpected ? 1 : 0);
1858+ QTRY_COMPARE(error.count(), errorCode < 0 ? 0 : 1);
1859+
1860+ if (errorCode < 0) {
1861+ QCOMPARE(error.count(), 0);
1862+
1863+ QVariantMap resp = result.count() > 0 ?
1864+ result.at(0).at(0).value<SessionData>().toMap() : QVariantMap();
1865+ QVariantMap storedData = store.count() > 0 ?
1866+ store.at(0).at(0).value<SessionData>().toMap() : QVariantMap();
1867+ /* We don't check the network request if a response was received,
1868+ * because a response can only be received if a cached token was
1869+ * found -- and that doesn't cause any network request to be made. */
1870+ if (resp.isEmpty()) {
1871+ QCOMPARE(nam->m_lastRequest.url(),
1872+ sessionData.value("RequestEndpoint").toUrl());
1873+ QVERIFY(nam->m_lastRequestData.isEmpty());
1874+
1875+ /* Check the authorization header */
1876+ QString authorizationHeader =
1877+ QString::fromUtf8(nam->m_lastRequest.rawHeader("Authorization"));
1878+ QStringList authorizationHeaderParts =
1879+ authorizationHeader.split(QRegExp(",?\\s+"));
1880+ QCOMPARE(authorizationHeaderParts[0], QString("OAuth"));
1881+
1882+ /* The rest of the header should be a mapping, let's parse it */
1883+ bool ok = true;
1884+ QVariantMap authMap =
1885+ parseAuthorizationHeader(authorizationHeaderParts.mid(1), &ok);
1886+ QVERIFY(ok);
1887+ QCOMPARE(authMap.value("oauth_signature_method").toString(), mechanism);
1888+ }
1889+
1890+ QVERIFY(mapIsSubset(response, resp));
1891+ QVERIFY(mapIsSubset(stored, storedData));
1892+ } else {
1893+ Error err = error.at(0).at(0).value<Error>();
1894+ QCOMPARE(err.type(), errorCode);
1895+ }
1896+}
1897+
1898+void PluginTest::testPluginUseragentUserActionFinished()
1899+{
1900+ SignOn::UiSessionData info;
1901+ PluginData data;
1902+ data.setHost("https://localhost");
1903+ data.setAuthPath("authorize");
1904+ data.setTokenPath("access_token");
1905+ data.setClientId("104660106251471");
1906+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
1907+ data.setRedirectUri("http://localhost/connect/login_success.html");
1908+ QStringList scopes = QStringList() << "scope1" << "scope2";
1909+ data.setScope(scopes);
1910+
1911+ QSignalSpy resultSpy(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
1912+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
1913+ QSignalSpy userActionRequired(m_testPlugin,
1914+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
1915+ QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
1916+
1917+ m_testPlugin->process(data, QString("user_agent"));
1918+
1919+ QTRY_COMPARE(userActionRequired.count(), 1);
1920+ QString state = parseState(userActionRequired);
1921+
1922+ //empty data
1923+ m_testPlugin->userActionFinished(info);
1924+ QTRY_COMPARE(error.count(), 1);
1925+ QCOMPARE(error.at(0).at(0).value<Error>().type(), int(Error::NotAuthorized));
1926+ error.clear();
1927+
1928+ //invalid data
1929+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html#access_token=&expires_in=4776"));
1930+ m_testPlugin->userActionFinished(info);
1931+ QTRY_COMPARE(error.count(), 1);
1932+ QCOMPARE(error.at(0).at(0).value<Error>().type(), int(Error::NotAuthorized));
1933+ error.clear();
1934+
1935+ //Invalid data
1936+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html"));
1937+ m_testPlugin->userActionFinished(info);
1938+ QTRY_COMPARE(error.count(), 1);
1939+ QCOMPARE(error.at(0).at(0).value<Error>().type(), int(Error::NotAuthorized));
1940+ error.clear();
1941+
1942+ // Wrong state
1943+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html"
1944+ "#access_token=123&expires_in=456&state=%1").
1945+ arg(state + "Boo"));
1946+ m_testPlugin->userActionFinished(info);
1947+ QTRY_COMPARE(error.count(), 1);
1948+ QCOMPARE(error.at(0).at(0).value<Error>().type(), int(Error::NotAuthorized));
1949+ error.clear();
1950+
1951+ //valid data
1952+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html#access_token=testtoken.&expires_in=4776&state=%1").
1953+ arg(state));
1954+ m_testPlugin->userActionFinished(info);
1955+ QTRY_COMPARE(resultSpy.count(), 1);
1956+ SessionData response = resultSpy.at(0).at(0).value<SessionData>();
1957+ PluginTokenData result = response.data<PluginTokenData>();
1958+ QCOMPARE(result.AccessToken(), QString("testtoken."));
1959+ QCOMPARE(result.ExpiresIn(), 4776);
1960+ QCOMPARE(result.Scope(), QStringList() << "scope1" << "scope2");
1961+ resultSpy.clear();
1962+ QTRY_COMPARE(store.count(), 1);
1963+ SessionData storedData = store.at(0).at(0).value<SessionData>();
1964+ QVariantMap storedTokenData = storedData.data<TokenData>().Tokens();
1965+ QVariantMap storedClientData =
1966+ storedTokenData.value(data.ClientId()).toMap();
1967+ QVERIFY(!storedClientData.isEmpty());
1968+ QCOMPARE(storedClientData["Scopes"].toStringList(), scopes);
1969+ store.clear();
1970+
1971+ //valid data, got scopes
1972+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html#access_token=testtoken.&expires_in=4776&state=%1&scope=scope2").
1973+ arg(state));
1974+ m_testPlugin->userActionFinished(info);
1975+ QTRY_COMPARE(resultSpy.count(), 1);
1976+ response = resultSpy.at(0).at(0).value<SessionData>();
1977+ result = response.data<PluginTokenData>();
1978+ QCOMPARE(result.AccessToken(), QString("testtoken."));
1979+ QCOMPARE(result.ExpiresIn(), 4776);
1980+ QCOMPARE(result.Scope(), QStringList() << "scope2");
1981+ resultSpy.clear();
1982+ store.clear();
1983+
1984+ //valid data
1985+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html"
1986+ "#state=%1&access_token=testtoken.").
1987+ arg(state));
1988+ m_testPlugin->userActionFinished(info);
1989+ QTRY_COMPARE(resultSpy.count(), 1);
1990+ response = resultSpy.at(0).at(0).value<SessionData>();
1991+ result = response.data<PluginTokenData>();
1992+ QCOMPARE(result.AccessToken(), QString("testtoken."));
1993+ QCOMPARE(result.ExpiresIn(), 0);
1994+ resultSpy.clear();
1995+ /* Check that the expiration time has not been stored, since the expiration
1996+ * time was not given (https://bugs.launchpad.net/bugs/1316021)
1997+ */
1998+ QTRY_COMPARE(store.count(), 1);
1999+ storedData = store.at(0).at(0).value<SessionData>();
2000+ storedTokenData = storedData.data<TokenData>().Tokens();
2001+ storedClientData = storedTokenData.value(data.ClientId()).toMap();
2002+ QVERIFY(!storedClientData.isEmpty());
2003+ QCOMPARE(storedClientData["Token"].toString(), QString("testtoken."));
2004+ QVERIFY(!storedClientData.contains("Expiry"));
2005+ store.clear();
2006+
2007+ //Permission denied
2008+ info.setUrlResponse(QString("http://www.facebook.com/connect/login_success.html?error=user_denied"));
2009+ m_testPlugin->userActionFinished(info);
2010+ QTRY_COMPARE(error.count(), 1);
2011+ QCOMPARE(error.at(0).at(0).value<Error>().type(), int(Error::NotAuthorized));
2012+ error.clear();
2013+}
2014+
2015+void PluginTest::testPluginWebserverUserActionFinished_data()
2016+{
2017+ QTest::addColumn<QString>("urlResponse");
2018+ QTest::addColumn<int>("errorCode");
2019+ QTest::addColumn<QString>("postUrl");
2020+ QTest::addColumn<QString>("postContents");
2021+ QTest::addColumn<bool>("disableStateParameter");
2022+ QTest::addColumn<int>("replyStatusCode");
2023+ QTest::addColumn<QString>("replyContentType");
2024+ QTest::addColumn<QString>("replyContents");
2025+ QTest::addColumn<QVariantMap>("response");
2026+
2027+ QVariantMap response;
2028+
2029+ QTest::newRow("empty data") <<
2030+ "" <<
2031+ int(Error::NotAuthorized) <<
2032+ "" << "" << false << 0 << "" << "" << QVariantMap();
2033+
2034+ QTest::newRow("no query data") <<
2035+ "http://localhost/resp.html" <<
2036+ int(Error::NotAuthorized) <<
2037+ "" << "" << false << 0 << "" << "" << QVariantMap();
2038+
2039+ QTest::newRow("permission denied") <<
2040+ "http://localhost/resp.html?error=user_denied&$state" <<
2041+ int(Error::NotAuthorized) <<
2042+ "" << "" << false << 0 << "" << "" << QVariantMap();
2043+
2044+ QTest::newRow("invalid data") <<
2045+ "http://localhost/resp.html?sdsdsds=access.grant." <<
2046+ int(Error::NotAuthorized) <<
2047+ "" << "" << false << 0 << "" << "" << QVariantMap();
2048+
2049+ QTest::newRow("reply code, http error 401") <<
2050+ "http://localhost/resp.html?code=c0d3&$state" <<
2051+ int(Error::OperationFailed) <<
2052+ "https://localhost/access_token" <<
2053+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2054+ false <<
2055+ int(401) <<
2056+ "application/json" <<
2057+ "something else" <<
2058+ QVariantMap();
2059+
2060+ QTest::newRow("reply code, empty reply") <<
2061+ "http://localhost/resp.html?code=c0d3&$state" <<
2062+ int(Error::NotAuthorized) <<
2063+ "https://localhost/access_token" <<
2064+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2065+ false <<
2066+ int(200) <<
2067+ "application/json" <<
2068+ "something else" <<
2069+ QVariantMap();
2070+
2071+ QTest::newRow("reply code, no access token") <<
2072+ "http://localhost/resp.html?code=c0d3&$state" <<
2073+ int(Error::NotAuthorized) <<
2074+ "https://localhost/access_token" <<
2075+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2076+ false <<
2077+ int(200) <<
2078+ "application/json" <<
2079+ "{ \"expires_in\": 3600 }" <<
2080+ QVariantMap();
2081+
2082+ QTest::newRow("reply code, no content type") <<
2083+ "http://localhost/resp.html?code=c0d3&$state" <<
2084+ int(Error::OperationFailed) <<
2085+ "https://localhost/access_token" <<
2086+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2087+ false <<
2088+ int(200) <<
2089+ "" <<
2090+ "something else" <<
2091+ QVariantMap();
2092+
2093+ QTest::newRow("reply code, unsupported content type") <<
2094+ "http://localhost/resp.html?code=c0d3&$state" <<
2095+ int(Error::OperationFailed) <<
2096+ "https://localhost/access_token" <<
2097+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2098+ false <<
2099+ int(200) <<
2100+ "image/jpeg" <<
2101+ "something else" <<
2102+ QVariantMap();
2103+
2104+ response.clear();
2105+ response.insert("AccessToken", "t0k3n");
2106+ response.insert("ExpiresIn", int(3600));
2107+ response.insert("RefreshToken", QString());
2108+ QTest::newRow("reply code, valid token, wrong state") <<
2109+ "http://localhost/resp.html?code=c0d3&$wrongstate" <<
2110+ int(Error::NotAuthorized) <<
2111+ "" <<
2112+ "" <<
2113+ false <<
2114+ int(200) <<
2115+ "application/json" <<
2116+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }" <<
2117+ response;
2118+
2119+ response.clear();
2120+ response.insert("AccessToken", "t0k3n");
2121+ response.insert("ExpiresIn", int(3600));
2122+ response.insert("RefreshToken", QString());
2123+ response.insert("Scope", QStringList() << "one" << "two");
2124+ QTest::newRow("reply code, valid token, wrong state ignored") <<
2125+ "http://localhost/resp.html?code=c0d3&$wrongstate" <<
2126+ int(-1) <<
2127+ "https://localhost/access_token" <<
2128+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2129+ true <<
2130+ int(200) <<
2131+ "application/json" <<
2132+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600, "
2133+ "\"scope\": \"one two\" }" <<
2134+ response;
2135+
2136+ response.clear();
2137+ response.insert("AccessToken", "t0k3n");
2138+ response.insert("ExpiresIn", int(3600));
2139+ response.insert("RefreshToken", QString());
2140+ response.insert("Scope", QStringList() << "one" << "two" << "three");
2141+ QTest::newRow("reply code, valid token, no scope") <<
2142+ "http://localhost/resp.html?code=c0d3&$state" <<
2143+ int(-1) <<
2144+ "https://localhost/access_token" <<
2145+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2146+ false <<
2147+ int(200) <<
2148+ "application/json" <<
2149+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }" <<
2150+ response;
2151+
2152+ response.clear();
2153+ response.insert("AccessToken", "t0k3n");
2154+ response.insert("ExpiresIn", int(3600));
2155+ response.insert("RefreshToken", QString());
2156+ response.insert("Scope", QStringList());
2157+ QTest::newRow("reply code, valid token, empty scope") <<
2158+ "http://localhost/resp.html?code=c0d3&$state" <<
2159+ int(-1) <<
2160+ "https://localhost/access_token" <<
2161+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2162+ false <<
2163+ int(200) <<
2164+ "application/json" <<
2165+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600, \"scope\": \"\" }" <<
2166+ response;
2167+
2168+ response.clear();
2169+ response.insert("AccessToken", "t0k3n");
2170+ response.insert("ExpiresIn", int(3600));
2171+ response.insert("RefreshToken", QString());
2172+ response.insert("Scope", QStringList() << "one" << "two");
2173+ QTest::newRow("reply code, valid token, other scope") <<
2174+ "http://localhost/resp.html?code=c0d3&$state" <<
2175+ int(-1) <<
2176+ "https://localhost/access_token" <<
2177+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2178+ false <<
2179+ int(200) <<
2180+ "application/json" <<
2181+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600, "
2182+ "\"scope\": \"one two\" }" <<
2183+ response;
2184+
2185+ response.clear();
2186+ QTest::newRow("reply code, facebook, no token") <<
2187+ "http://localhost/resp.html?code=c0d3&$state" <<
2188+ int(Error::NotAuthorized) <<
2189+ "https://localhost/access_token" <<
2190+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2191+ false <<
2192+ int(200) <<
2193+ "text/plain" <<
2194+ "expires=3600" <<
2195+ response;
2196+
2197+ response.clear();
2198+ response.insert("AccessToken", "t0k3n");
2199+ response.insert("ExpiresIn", int(3600));
2200+ response.insert("RefreshToken", QString());
2201+ response.insert("Scope", QStringList() << "one" << "two" << "three");
2202+ QTest::newRow("reply code, facebook, valid token") <<
2203+ "http://localhost/resp.html?code=c0d3&$state" <<
2204+ int(-1) <<
2205+ "https://localhost/access_token" <<
2206+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2207+ false <<
2208+ int(200) <<
2209+ "text/plain" <<
2210+ "access_token=t0k3n&expires=3600" <<
2211+ response;
2212+
2213+ response.clear();
2214+ response.insert("AccessToken", "t0k3n");
2215+ response.insert("ExpiresIn", int(3600));
2216+ response.insert("RefreshToken", QString());
2217+ response.insert("Scope", QStringList() << "one" << "two" << "three");
2218+ QTest::newRow("username-password, valid token") <<
2219+ "http://localhost/resp.html?username=us3r&password=s3cr3t" <<
2220+ int(-1) <<
2221+ "https://localhost/access_token" <<
2222+ "grant_type=user_basic&username=us3r&password=s3cr3t" <<
2223+ false <<
2224+ int(200) <<
2225+ "application/json" <<
2226+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }" <<
2227+ response;
2228+
2229+ response.clear();
2230+ response.insert("AccessToken", "t0k3n");
2231+ response.insert("ExpiresIn", int(3600));
2232+ response.insert("RefreshToken", QString());
2233+ response.insert("Scope", QStringList() << "one" << "two" << "three");
2234+ QTest::newRow("assertion, valid token") <<
2235+ "http://localhost/resp.html?assertion_type=http://oauth.net/token/1.0"
2236+ "&assertion=oauth1t0k3n" <<
2237+ int(-1) <<
2238+ "https://localhost/access_token" <<
2239+ "grant_type=assertion&assertion_type=http://oauth.net/token/1.0&assertion=oauth1t0k3n" <<
2240+ false <<
2241+ int(200) <<
2242+ "application/json" <<
2243+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }" <<
2244+ response;
2245+
2246+ response.clear();
2247+ response.insert("AccessToken", "t0k3n");
2248+ response.insert("ExpiresIn", int(3600));
2249+ response.insert("RefreshToken", QString());
2250+ response.insert("Scope", QStringList() << "one" << "two" << "three");
2251+ QTest::newRow("username-password, valid token, wrong content type") <<
2252+ "http://localhost/resp.html?username=us3r&password=s3cr3t" <<
2253+ int(-1) <<
2254+ "https://localhost/access_token" <<
2255+ "grant_type=user_basic&username=us3r&password=s3cr3t" <<
2256+ false <<
2257+ int(200) <<
2258+ "text/plain" <<
2259+ "{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }" <<
2260+ response;
2261+}
2262+
2263+void PluginTest::testPluginWebserverUserActionFinished()
2264+{
2265+ QFETCH(QString, urlResponse);
2266+ QFETCH(int, errorCode);
2267+ QFETCH(QString, postUrl);
2268+ QFETCH(QString, postContents);
2269+ QFETCH(bool, disableStateParameter);
2270+ QFETCH(int, replyStatusCode);
2271+ QFETCH(QString, replyContentType);
2272+ QFETCH(QString, replyContents);
2273+ QFETCH(QVariantMap, response);
2274+
2275+ SignOn::UiSessionData info;
2276+ PluginData data;
2277+ data.setHost("localhost");
2278+ data.setAuthPath("authorize");
2279+ data.setTokenPath("access_token");
2280+ data.setClientId("104660106251471");
2281+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2282+ data.setRedirectUri("http://localhost/resp.html");
2283+ data.setScope(QStringList() << "one" << "two" << "three");
2284+ data.setDisableStateParameter(disableStateParameter);
2285+
2286+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
2287+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2288+ QSignalSpy userActionRequired(m_testPlugin,
2289+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2290+
2291+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2292+ m_testPlugin->m_networkAccessManager = nam;
2293+ TestNetworkReply *reply = new TestNetworkReply(this);
2294+ reply->setStatusCode(replyStatusCode);
2295+ if (!replyContentType.isEmpty()) {
2296+ reply->setContentType(replyContentType);
2297+ }
2298+ reply->setContent(replyContents.toUtf8());
2299+ nam->setNextReply(reply);
2300+
2301+ m_testPlugin->process(data, QString("web_server"));
2302+ QTRY_COMPARE(userActionRequired.count(), 1);
2303+ QString state = parseState(userActionRequired);
2304+
2305+ if (!urlResponse.isEmpty()) {
2306+ urlResponse.replace("$state", QString("state=") + state);
2307+ urlResponse.replace("$wrongstate", QString("state=12") + state);
2308+ info.setUrlResponse(urlResponse);
2309+ }
2310+
2311+ m_testPlugin->userActionFinished(info);
2312+
2313+ QTRY_COMPARE(error.count(), errorCode < 0 ? 0 : 1);
2314+ QTRY_COMPARE(result.count(), errorCode < 0 ? 1 : 0);
2315+ if (errorCode >= 0) {
2316+ QCOMPARE(error.at(0).at(0).value<Error>().type(), errorCode);
2317+ } else {
2318+ QCOMPARE(result.at(0).at(0).value<SessionData>().toMap(), response);
2319+ }
2320+ QCOMPARE(nam->m_lastRequest.url(), QUrl(postUrl));
2321+ QCOMPARE(QString::fromUtf8(nam->m_lastRequestData), postContents);
2322+
2323+ delete nam;
2324+}
2325+
2326+void PluginTest::testUserActionFinishedErrors_data()
2327+{
2328+ QTest::addColumn<int>("uiError");
2329+ QTest::addColumn<int>("expectedErrorCode");
2330+
2331+ QTest::newRow("user canceled") <<
2332+ int(QUERY_ERROR_CANCELED) <<
2333+ int(Error::SessionCanceled);
2334+
2335+ QTest::newRow("network error") <<
2336+ int(QUERY_ERROR_NETWORK) <<
2337+ int(Error::Network);
2338+
2339+ QTest::newRow("SSL error") <<
2340+ int(QUERY_ERROR_SSL) <<
2341+ int(Error::Ssl);
2342+
2343+ QTest::newRow("generic") <<
2344+ int(QUERY_ERROR_NOT_AVAILABLE) <<
2345+ int(Error::UserInteraction);
2346+}
2347+
2348+void PluginTest::testUserActionFinishedErrors()
2349+{
2350+ QFETCH(int, uiError);
2351+ QFETCH(int, expectedErrorCode);
2352+
2353+ SignOn::UiSessionData info;
2354+ PluginData data;
2355+ data.setHost("localhost");
2356+ data.setAuthPath("authorize");
2357+ data.setTokenPath("access_token");
2358+ data.setClientId("104660106251471");
2359+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2360+ data.setRedirectUri("http://localhost/resp.html");
2361+
2362+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2363+ QSignalSpy userActionRequired(m_testPlugin,
2364+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2365+
2366+ m_testPlugin->process(data, QString("web_server"));
2367+ QTRY_COMPARE(userActionRequired.count(), 1);
2368+
2369+ info.setQueryErrorCode(uiError);
2370+ m_testPlugin->userActionFinished(info);
2371+
2372+ QTRY_COMPARE(error.count(), 1);
2373+ QCOMPARE(error.at(0).at(0).value<Error>().type(), expectedErrorCode);
2374+}
2375+
2376+void PluginTest::testOauth1UserActionFinished_data()
2377+{
2378+ QTest::addColumn<QString>("mechanism");
2379+ QTest::addColumn<QString>("urlResponse");
2380+ QTest::addColumn<int>("errorCode");
2381+ QTest::addColumn<QVariantMap>("expectedAuthMap");
2382+ QTest::addColumn<int>("replyStatusCode");
2383+ QTest::addColumn<QString>("replyContentType");
2384+ QTest::addColumn<QString>("replyContents");
2385+ QTest::addColumn<QVariantMap>("response");
2386+
2387+ QTest::newRow("empty data") <<
2388+ "HMAC-SHA1" <<
2389+ "" <<
2390+ int(Error::NotAuthorized) <<
2391+ QVariantMap() << 0 << "" << "" << QVariantMap();
2392+
2393+ QTest::newRow("auth error") <<
2394+ "HMAC-SHA1" <<
2395+ "http://localhost/resp.html?error=permission_denied" <<
2396+ int(Error::NotAuthorized) <<
2397+ QVariantMap() << 0 << "" << "" << QVariantMap();
2398+
2399+ QTest::newRow("auth problem") <<
2400+ "HMAC-SHA1" <<
2401+ "http://localhost/resp.html?oauth_problem=permission_denied" <<
2402+ int(Error::PermissionDenied) <<
2403+ QVariantMap() << 0 << "" << "" << QVariantMap();
2404+
2405+ QVariantMap authMap;
2406+ authMap.insert("oauth_verifier", QString("v3r1f13r"));
2407+ authMap.insert("oauth_token", QString("HiThere"));
2408+ QTest::newRow("http error 401") <<
2409+ "HMAC-SHA1" <<
2410+ "http://localhost/resp.html?oauth_verifier=v3r1f13r" <<
2411+ int(Error::OperationFailed) <<
2412+ authMap <<
2413+ int(401) <<
2414+ "text/plain" <<
2415+ "something else" <<
2416+ QVariantMap();
2417+
2418+ QTest::newRow("empty reply") <<
2419+ "HMAC-SHA1" <<
2420+ "http://localhost/resp.html?oauth_verifier=v3r1f13r" <<
2421+ int(Error::OperationFailed) <<
2422+ authMap <<
2423+ int(200) <<
2424+ "text/plain" <<
2425+ "" <<
2426+ QVariantMap();
2427+
2428+ QTest::newRow("missing secret") <<
2429+ "HMAC-SHA1" <<
2430+ "http://localhost/resp.html?oauth_verifier=v3r1f13r" <<
2431+ int(Error::OperationFailed) <<
2432+ authMap <<
2433+ int(200) <<
2434+ "text/plain" <<
2435+ "oauth_token=t0k3n" <<
2436+ QVariantMap();
2437+
2438+ QVariantMap response;
2439+ response.insert("AccessToken", QString("t0k3n"));
2440+ response.insert("TokenSecret", QString("t0k3nS3cr3t"));
2441+
2442+ QTest::newRow("success") <<
2443+ "HMAC-SHA1" <<
2444+ "http://localhost/resp.html?oauth_verifier=v3r1f13r" <<
2445+ int(-1) <<
2446+ authMap <<
2447+ int(200) <<
2448+ "text/plain" <<
2449+ "oauth_token=t0k3n&oauth_token_secret=t0k3nS3cr3t" <<
2450+ response;
2451+
2452+ response.insert("ExtraField", QString("v4lu3"));
2453+ QTest::newRow("success with data") <<
2454+ "HMAC-SHA1" <<
2455+ "http://localhost/resp.html?oauth_verifier=v3r1f13r" <<
2456+ int(-1) <<
2457+ authMap <<
2458+ int(200) <<
2459+ "text/plain" <<
2460+ "oauth_token=t0k3n&oauth_token_secret=t0k3nS3cr3t"
2461+ "&ExtraField=v4lu3" <<
2462+ response;
2463+}
2464+
2465+void PluginTest::testOauth1UserActionFinished()
2466+{
2467+ QFETCH(QString, mechanism);
2468+ QFETCH(QString, urlResponse);
2469+ QFETCH(int, errorCode);
2470+ QFETCH(QVariantMap, expectedAuthMap);
2471+ QFETCH(int, replyStatusCode);
2472+ QFETCH(QString, replyContentType);
2473+ QFETCH(QString, replyContents);
2474+ QFETCH(QVariantMap, response);
2475+
2476+ SignOn::UiSessionData info;
2477+ OAuth1PluginData data;
2478+ data.setRequestEndpoint("https://localhost/oauth/request_token");
2479+ data.setTokenEndpoint("https://localhost/oauth/access_token");
2480+ data.setAuthorizationEndpoint("https://localhost/oauth/authorize");
2481+ data.setCallback("http://localhost/resp.html");
2482+ data.setConsumerKey("104660106251471");
2483+ data.setConsumerSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2484+ data.setRealm("MyHost");
2485+
2486+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
2487+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2488+ QSignalSpy userActionRequired(m_testPlugin,
2489+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2490+
2491+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2492+ m_testPlugin->m_networkAccessManager = nam;
2493+ TestNetworkReply *reply = new TestNetworkReply(this);
2494+ reply->setStatusCode(200);
2495+ reply->setContentType("text/plain");
2496+ reply->setContent("oauth_token=HiThere&oauth_token_secret=BigSecret");
2497+ nam->setNextReply(reply);
2498+
2499+ m_testPlugin->process(data, mechanism);
2500+ QTRY_COMPARE(userActionRequired.count(), 1);
2501+
2502+ nam->m_lastRequest = QNetworkRequest();
2503+ nam->m_lastRequestData = QByteArray();
2504+
2505+ reply = new TestNetworkReply(this);
2506+ reply->setStatusCode(replyStatusCode);
2507+ if (!replyContentType.isEmpty()) {
2508+ reply->setContentType(replyContentType);
2509+ }
2510+ reply->setContent(replyContents.toUtf8());
2511+ nam->setNextReply(reply);
2512+
2513+ if (!urlResponse.isEmpty()) {
2514+ info.setUrlResponse(urlResponse);
2515+ }
2516+
2517+ m_testPlugin->userActionFinished(info);
2518+ QTRY_COMPARE(error.count(), errorCode < 0 ? 0 : 1);
2519+ QTRY_COMPARE(result.count(), errorCode < 0 ? 1 : 0);
2520+ QVariantMap resp;
2521+ if (errorCode >= 0) {
2522+ QCOMPARE(error.at(0).at(0).value<Error>().type(), errorCode);
2523+ } else {
2524+ resp = result.at(0).at(0).value<SessionData>().toMap();
2525+ QVERIFY(mapIsSubset(response, resp));
2526+ }
2527+
2528+ if (!expectedAuthMap.isEmpty()) {
2529+ QCOMPARE(nam->m_lastRequest.url().toString(), data.TokenEndpoint());
2530+ QVERIFY(nam->m_lastRequestData.isEmpty());
2531+
2532+ QString authorizationHeader =
2533+ QString::fromUtf8(nam->m_lastRequest.rawHeader("Authorization"));
2534+ QStringList authorizationHeaderParts =
2535+ authorizationHeader.split(QRegExp(",?\\s+"));
2536+ QCOMPARE(authorizationHeaderParts[0], QString("OAuth"));
2537+
2538+ /* The rest of the header should be a mapping, let's parse it */
2539+ bool ok = true;
2540+ QVariantMap authMap =
2541+ parseAuthorizationHeader(authorizationHeaderParts.mid(1), &ok);
2542+ QVERIFY(ok);
2543+ QCOMPARE(authMap.value("oauth_signature_method").toString(), mechanism);
2544+ QVERIFY(mapIsSubset(expectedAuthMap, authMap));
2545+ }
2546+
2547+ delete nam;
2548+}
2549+
2550+void PluginTest::testErrors_data()
2551+{
2552+ QTest::addColumn<QString>("replyContents");
2553+ QTest::addColumn<int>("expectedErrorCode");
2554+
2555+ QTest::newRow("incorrect_client_credentials") <<
2556+ "{ \"error\": \"incorrect_client_credentials\" }" <<
2557+ int(Error::InvalidCredentials);
2558+
2559+ QTest::newRow("redirect_uri_mismatch") <<
2560+ "{ \"error\": \"redirect_uri_mismatch\" }" <<
2561+ int(Error::InvalidCredentials);
2562+
2563+ QTest::newRow("bad_authorization_code") <<
2564+ "{ \"error\": \"bad_authorization_code\" }" <<
2565+ int(Error::InvalidCredentials);
2566+
2567+ QTest::newRow("invalid_client_credentials") <<
2568+ "{ \"error\": \"invalid_client_credentials\" }" <<
2569+ int(Error::InvalidCredentials);
2570+
2571+ QTest::newRow("unauthorized_client") <<
2572+ "{ \"error\": \"unauthorized_client\" }" <<
2573+ int(Error::NotAuthorized);
2574+
2575+ QTest::newRow("invalid_assertion") <<
2576+ "{ \"error\": \"invalid_assertion\" }" <<
2577+ int(Error::InvalidCredentials);
2578+
2579+ QTest::newRow("unknown_format") <<
2580+ "{ \"error\": \"unknown_format\" }" <<
2581+ int(Error::InvalidQuery);
2582+
2583+ QTest::newRow("authorization_expired") <<
2584+ "{ \"error\": \"authorization_expired\" }" <<
2585+ int(Error::InvalidCredentials);
2586+
2587+ QTest::newRow("multiple_credentials") <<
2588+ "{ \"error\": \"multiple_credentials\" }" <<
2589+ int(Error::InvalidQuery);
2590+
2591+ QTest::newRow("invalid_user_credentials") <<
2592+ "{ \"error\": \"invalid_user_credentials\" }" <<
2593+ int(Error::InvalidCredentials);
2594+}
2595+
2596+void PluginTest::testErrors()
2597+{
2598+ QFETCH(QString, replyContents);
2599+ QFETCH(int, expectedErrorCode);
2600+
2601+ SignOn::UiSessionData info;
2602+ PluginData data;
2603+ data.setHost("localhost");
2604+ data.setAuthPath("authorize");
2605+ data.setTokenPath("access_token");
2606+ data.setClientId("104660106251471");
2607+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2608+ data.setRedirectUri("http://localhost/resp.html");
2609+
2610+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2611+ QSignalSpy userActionRequired(m_testPlugin,
2612+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2613+
2614+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2615+ m_testPlugin->m_networkAccessManager = nam;
2616+ TestNetworkReply *reply = new TestNetworkReply(this);
2617+ reply->setStatusCode(401);
2618+ reply->setContentType("application/json");
2619+ reply->setContent(replyContents.toUtf8());
2620+ nam->setNextReply(reply);
2621+
2622+ m_testPlugin->process(data, QString("web_server"));
2623+ QTRY_COMPARE(userActionRequired.count(), 1);
2624+ QString state = parseState(userActionRequired);
2625+
2626+ info.setUrlResponse("http://localhost/resp.html?code=c0d3&state=" + state);
2627+ m_testPlugin->userActionFinished(info);
2628+
2629+ QTRY_COMPARE(error.count(), 1);
2630+ QCOMPARE(error.at(0).at(0).value<Error>().type(), expectedErrorCode);
2631+
2632+ delete nam;
2633+}
2634+
2635+void PluginTest::testRefreshToken_data()
2636+{
2637+ QTest::addColumn<QVariantMap>("sessionData");
2638+ QTest::addColumn<QVariantMap>("expectedResponse");
2639+
2640+ PluginData data;
2641+ data.setHost("localhost");
2642+ data.setAuthPath("authorize");
2643+ data.setTokenPath("access_token");
2644+ data.setClientId("104660106251471");
2645+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2646+ data.setRedirectUri("http://localhost/resp.html");
2647+
2648+ QVariantMap tokens;
2649+ QVariantMap token;
2650+ token.insert("Token", QLatin1String("tokenfromtest"));
2651+ token.insert("timestamp", QDateTime::currentDateTime().toTime_t() - 10000);
2652+ token.insert("Expiry", 1000);
2653+ token.insert("refresh_token", QString("r3fr3sh"));
2654+ tokens.insert(data.ClientId(), QVariant::fromValue(token));
2655+ data.m_data.insert("Tokens", tokens);
2656+
2657+ QVariantMap response;
2658+ response.insert("AccessToken", "n3w-t0k3n");
2659+ response.insert("ExpiresIn", 3600);
2660+ response.insert("RefreshToken", QString());
2661+ response.insert("Scope", QStringList());
2662+
2663+ QTest::newRow("expired access token") << data.toMap() << response;
2664+
2665+ token.insert("timestamp", QDateTime::currentDateTime().toTime_t());
2666+ token.insert("Expiry", 50000);
2667+ tokens.insert(data.ClientId(), QVariant::fromValue(token));
2668+ data.m_data.insert("Tokens", tokens);
2669+ data.setForceTokenRefresh(true);
2670+ QTest::newRow("valid access token, force refresh") << data.toMap() << response;
2671+}
2672+
2673+void PluginTest::testRefreshToken()
2674+{
2675+ QFETCH(QVariantMap, sessionData);
2676+ QFETCH(QVariantMap, expectedResponse);
2677+
2678+ SignOn::UiSessionData info;
2679+
2680+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
2681+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2682+
2683+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2684+ m_testPlugin->m_networkAccessManager = nam;
2685+ TestNetworkReply *reply = new TestNetworkReply(this);
2686+ reply->setStatusCode(200);
2687+ reply->setContentType("application/json");
2688+ reply->setContent("{ \"access_token\":\"n3w-t0k3n\", \"expires_in\": 3600 }");
2689+ nam->setNextReply(reply);
2690+
2691+ m_testPlugin->process(sessionData, QString("web_server"));
2692+ QTRY_COMPARE(result.count(), 1);
2693+ QCOMPARE(error.count(), 0);
2694+
2695+ QCOMPARE(nam->m_lastRequest.url(), QUrl("https://localhost/access_token"));
2696+ QCOMPARE(QString::fromUtf8(nam->m_lastRequestData),
2697+ QString("grant_type=refresh_token&refresh_token=r3fr3sh"));
2698+
2699+ QCOMPARE(result.at(0).at(0).value<SessionData>().toMap(), expectedResponse);
2700+
2701+ delete nam;
2702+}
2703+
2704+void PluginTest::testRefreshTokenError_data()
2705+{
2706+ QTest::addColumn<int>("replyErrorCode");
2707+ QTest::addColumn<int>("replyStatusCode");
2708+ QTest::addColumn<QString>("replyContents");
2709+ QTest::addColumn<int>("expectedError");
2710+
2711+ QTest::newRow("invalid grant, 400") <<
2712+ int(QNetworkReply::ProtocolInvalidOperationError) <<
2713+ int(400) <<
2714+ "{ \"error\":\"invalid_grant\" }" <<
2715+ int(-1);
2716+
2717+ QTest::newRow("invalid grant, 401") <<
2718+ int(QNetworkReply::ContentAccessDenied) <<
2719+ int(401) <<
2720+ "{ \"error\":\"invalid_grant\" }" <<
2721+ int(-1);
2722+
2723+ QTest::newRow("invalid grant, 401, no error signal") <<
2724+ int(-1) <<
2725+ int(401) <<
2726+ "{ \"error\":\"invalid_grant\" }" <<
2727+ int(-1);
2728+
2729+ QTest::newRow("temporary network failure") <<
2730+ int(QNetworkReply::TemporaryNetworkFailureError) <<
2731+ int(-1) <<
2732+ "" <<
2733+ int(Error::NoConnection);
2734+}
2735+
2736+void PluginTest::testRefreshTokenError()
2737+{
2738+ QFETCH(int, replyErrorCode);
2739+ QFETCH(int, replyStatusCode);
2740+ QFETCH(QString, replyContents);
2741+ QFETCH(int, expectedError);
2742+
2743+ PluginData data;
2744+ data.setHost("localhost");
2745+ data.setAuthPath("authorize");
2746+ data.setTokenPath("access_token");
2747+ data.setClientId("104660106251471");
2748+ data.setClientSecret("fa28f40b5a1f8c1d5628963d880636fbkjkjkj");
2749+ data.setRedirectUri("http://localhost/resp.html");
2750+
2751+ QVariantMap tokens;
2752+ QVariantMap token;
2753+ token.insert("Token", QLatin1String("tokenfromtest"));
2754+ token.insert("timestamp", QDateTime::currentDateTime().toTime_t() - 10000);
2755+ token.insert("Expiry", 1000);
2756+ token.insert("refresh_token", QString("r3fr3sh"));
2757+ tokens.insert(data.ClientId(), QVariant::fromValue(token));
2758+ data.m_data.insert("Tokens", tokens);
2759+
2760+ SignOn::UiSessionData info;
2761+
2762+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2763+ QSignalSpy userActionRequired(m_testPlugin,
2764+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2765+
2766+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2767+ m_testPlugin->m_networkAccessManager = nam;
2768+ TestNetworkReply *reply = new TestNetworkReply(this);
2769+ if (replyErrorCode >= 0) {
2770+ reply->setError(QNetworkReply::NetworkError(replyErrorCode),
2771+ "Dummy error", 5);
2772+ }
2773+ reply->setStatusCode(replyStatusCode);
2774+ reply->setContentType("application/json");
2775+ reply->setContent(replyContents.toUtf8());
2776+ nam->setNextReply(reply);
2777+
2778+ m_testPlugin->process(data, QString("web_server"));
2779+
2780+ if (expectedError < 0) {
2781+ QTRY_COMPARE(userActionRequired.count(), 1);
2782+ QCOMPARE(error.count(), 0);
2783+ } else {
2784+ QTRY_COMPARE(error.count(), 1);
2785+ QCOMPARE(error.at(0).at(0).value<Error>().type(), expectedError);
2786+ }
2787+
2788+ delete nam;
2789+}
2790+
2791+void PluginTest::testClientAuthentication_data()
2792+{
2793+ QTest::addColumn<QString>("clientSecret");
2794+ QTest::addColumn<bool>("forceAuthViaRequestBody");
2795+ QTest::addColumn<QString>("postContents");
2796+ QTest::addColumn<QString>("postAuthorization");
2797+
2798+ QTest::newRow("no secret, std auth") <<
2799+ "" << false <<
2800+ "grant_type=authorization_code&code=c0d3"
2801+ "&redirect_uri=http://localhost/resp.html&client_id=104660106251471" <<
2802+ "";
2803+ QTest::newRow("no secret, auth in body") <<
2804+ "" << true <<
2805+ "grant_type=authorization_code&code=c0d3"
2806+ "&redirect_uri=http://localhost/resp.html&client_id=104660106251471" <<
2807+ "";
2808+
2809+ QTest::newRow("with secret, std auth") <<
2810+ "s3cr3t" << false <<
2811+ "grant_type=authorization_code&code=c0d3&redirect_uri=http://localhost/resp.html" <<
2812+ "Basic MTA0NjYwMTA2MjUxNDcxOnMzY3IzdA==";
2813+ QTest::newRow("with secret, auth in body") <<
2814+ "s3cr3t" << true <<
2815+ "grant_type=authorization_code&code=c0d3"
2816+ "&redirect_uri=http://localhost/resp.html"
2817+ "&client_id=104660106251471&client_secret=s3cr3t" <<
2818+ "";
2819+}
2820+
2821+void PluginTest::testClientAuthentication()
2822+{
2823+ QFETCH(QString, clientSecret);
2824+ QFETCH(bool, forceAuthViaRequestBody);
2825+ QFETCH(QString, postContents);
2826+ QFETCH(QString, postAuthorization);
2827+
2828+ SignOn::UiSessionData info;
2829+ PluginData data;
2830+ data.setHost("localhost");
2831+ data.setAuthPath("authorize");
2832+ data.setTokenPath("access_token");
2833+ data.setClientId("104660106251471");
2834+ data.setClientSecret(clientSecret);
2835+ data.setRedirectUri("http://localhost/resp.html");
2836+ data.setForceClientAuthViaRequestBody(forceAuthViaRequestBody);
2837+
2838+ QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
2839+ QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
2840+ QSignalSpy userActionRequired(m_testPlugin,
2841+ SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
2842+
2843+ TestNetworkAccessManager *nam = new TestNetworkAccessManager;
2844+ m_testPlugin->m_networkAccessManager = nam;
2845+ TestNetworkReply *reply = new TestNetworkReply(this);
2846+ reply->setStatusCode(200);
2847+ reply->setContentType("application/json");
2848+ reply->setContent("{ \"access_token\":\"t0k3n\", \"expires_in\": 3600 }");
2849+ nam->setNextReply(reply);
2850+
2851+ m_testPlugin->process(data, QString("web_server"));
2852+ QTRY_COMPARE(userActionRequired.count(), 1);
2853+ QString state = parseState(userActionRequired);
2854+
2855+ info.setUrlResponse("http://localhost/resp.html?code=c0d3&state=" + state);
2856+ m_testPlugin->userActionFinished(info);
2857+
2858+ QTRY_COMPARE(result.count(), 1);
2859+ QCOMPARE(error.count(), 0);
2860+ QCOMPARE(nam->m_lastRequest.url(), QUrl("https://localhost/access_token"));
2861+ QCOMPARE(QString::fromUtf8(nam->m_lastRequestData), postContents);
2862+ QCOMPARE(QString::fromUtf8(nam->m_lastRequest.rawHeader("Authorization")),
2863+ postAuthorization);
2864+
2865+ delete nam;
2866+}
2867+
2868+#endif
2869+
2870+QTEST_MAIN(PluginTest)
2871+#include "tst_plugin.moc"
2872
2873=== modified file 'signon-plugin/ubuntuone-plugin.cpp'
2874--- signon-plugin/ubuntuone-plugin.cpp 2013-08-12 21:16:29 +0000
2875+++ signon-plugin/ubuntuone-plugin.cpp 2016-04-22 09:51:32 +0000
2876@@ -15,15 +15,37 @@
2877 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
2878 * Boston, MA 02110-1301, USA.
2879 */
2880+
2881+#include <QJsonDocument>
2882+#include <QJsonObject>
2883+#include <QNetworkAccessManager>
2884+#include <QNetworkReply>
2885+#include <QNetworkRequest>
2886+
2887+#include <SignOn/UiSessionData>
2888+#include <SignOn/uisessiondata_priv.h>
2889+
2890 #include <token.h>
2891
2892+#include "i18n.h"
2893 #include "ubuntuone-plugin.h"
2894
2895+#define BASE_URL "https://login.ubuntu.com"
2896+
2897+#define ERR_INVALID_CREDENTIALS QLatin1String("INVALID_CREDENTIALS")
2898+#define ERR_INVALID_DATA QLatin1String("INVALID_DATA")
2899+#define ERR_TWOFACTOR_REQUIRED QLatin1String("TWOFACTOR_REQUIRED")
2900+#define ERR_TWOFACTOR_FAILURE QLatin1String("TWOFACTOR_FAILURE")
2901+#define ERR_PASSWORD_POLICY_ERROR QLatin1String("PASSWORD_POLICY_ERROR")
2902
2903 namespace UbuntuOne {
2904
2905- SignOnPlugin::SignOnPlugin(QObject *parent)
2906- : AuthPluginInterface(parent)
2907+ SignOnPlugin::SignOnPlugin(QObject *parent):
2908+ AuthPluginInterface(parent),
2909+ m_networkAccessManager(0),
2910+ m_reply(0),
2911+ m_didAskForPassword(false),
2912+ m_needsOtp(false)
2913 {
2914 }
2915
2916@@ -47,33 +69,319 @@
2917 {
2918 }
2919
2920+ bool SignOnPlugin::validateInput(const PluginData &data,
2921+ const QString &mechanism)
2922+ {
2923+ Q_UNUSED(mechanism);
2924+
2925+ if (data.TokenName().isEmpty()) {
2926+ return false;
2927+ }
2928+
2929+ return true;
2930+ }
2931+
2932+ bool SignOnPlugin::respondWithStoredData()
2933+ {
2934+ QVariantMap storedData = m_data.StoredData();
2935+
2936+ /* When U1 was using the password plugin, it was storing the token data
2937+ * in the password field. So, if we don't have any data stored in the
2938+ * plugin's data, try to get a token from the password field.
2939+ */
2940+ if (storedData.isEmpty() && !m_data.Secret().isEmpty()) {
2941+ Token *token = Token::fromQuery(m_data.Secret());
2942+ if (token->isValid()) {
2943+ PluginData tokenData;
2944+ tokenData.setConsumerKey(token->consumerKey());
2945+ tokenData.setConsumerSecret(token->consumerSecret());
2946+ tokenData.setTokenKey(token->tokenKey());
2947+ tokenData.setTokenSecret(token->tokenSecret());
2948+ storedData[token->name()] = tokenData.toMap();
2949+ PluginData pluginData;
2950+ pluginData.setStoredData(storedData);
2951+ Q_EMIT store(pluginData);
2952+
2953+ /* We know that the given secret is a valid token, so it cannot
2954+ * be a valid password as well: let's clear it out now, so that
2955+ * if it turns out that the token is no longer valid and that
2956+ * we need to create a new one, we won't make a useless attempt
2957+ * to create one with a wrong password.
2958+ */
2959+ m_data.setSecret(QString());
2960+ }
2961+ delete token;
2962+ }
2963+
2964+ /* Check if we have stored data for this token name */
2965+ PluginData tokenData(storedData[m_data.TokenName()].toMap());
2966+ Token token(tokenData.TokenKey(), tokenData.TokenSecret(),
2967+ tokenData.ConsumerKey(), tokenData.ConsumerSecret());
2968+ if (!token.isValid()) return false;
2969+ qDebug() << "Token is valid!" << tokenData.TokenKey();
2970+
2971+ tokenData.setTokenName(m_data.TokenName());
2972+ checkTokenValidity(token, tokenData);
2973+ return true;
2974+ }
2975+
2976+ void SignOnPlugin::emitErrorFromReply(QNetworkReply *reply)
2977+ {
2978+ int errorCode = reply->error();
2979+ int type = SignOn::Error::Network;
2980+ if (errorCode == QNetworkReply::SslHandshakeFailedError) {
2981+ type = SignOn::Error::Ssl;
2982+ } else if (errorCode == QNetworkReply::ServiceUnavailableError) {
2983+ type = SignOn::Error::ServiceNotAvailable;
2984+ } else if (errorCode == QNetworkReply::AuthenticationRequiredError) {
2985+ type = SignOn::Error::NotAuthorized;
2986+ } else if (errorCode <= QNetworkReply::UnknownNetworkError) {
2987+ type = SignOn::Error::NoConnection;
2988+ }
2989+
2990+ qDebug() << "Got error:" << reply->errorString();
2991+ Q_EMIT error(SignOn::Error(type, reply->errorString()));
2992+ }
2993+
2994+ void SignOnPlugin::onValidationFinished()
2995+ {
2996+ QNetworkReply *reply = m_reply;
2997+ m_reply->deleteLater();
2998+ m_reply = 0;
2999+
3000+ /* Note on error handling: we consider the token to be (in)valid only if
3001+ * we can parse the JSON response and it contains the response. If the
3002+ * validation has failed because of some other error (SSL error,
3003+ * unparsable server reply, etc.) then we report the error to the
3004+ * client.
3005+ */
3006+ QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
3007+ QJsonObject object = json.object();
3008+ QJsonValue value = object.value("is_valid");
3009+
3010+ int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
3011+ if (statusCode == 200 && value.isBool()) {
3012+ bool isValid = value.toBool();
3013+ if (isValid) {
3014+ Q_EMIT result(m_checkedToken);
3015+ } else {
3016+ qDebug() << "Server verification failed";
3017+ getCredentialsAndCreateNewToken();
3018+ }
3019+ } else {
3020+ emitErrorFromReply(reply);
3021+ }
3022+ }
3023+
3024+ void SignOnPlugin::checkTokenValidity(const Token &token,
3025+ const PluginData &tokenData)
3026+ {
3027+ m_checkedToken = tokenData;
3028+
3029+ QNetworkRequest req(QUrl(BASE_URL "/api/v2/requests/validate"));
3030+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
3031+
3032+ QString httpUrl("http://www.example.com");
3033+ QString httpMethod("GET");
3034+
3035+ QJsonObject formData;
3036+ formData.insert("http_url", httpUrl);
3037+ formData.insert("http_method", httpMethod);
3038+ formData.insert("authorization", token.signUrl(httpUrl, httpMethod));
3039+
3040+ m_reply =
3041+ m_networkAccessManager->post(req, QJsonDocument(formData).toJson());
3042+ QObject::connect(m_reply, SIGNAL(finished()),
3043+ this, SLOT(onValidationFinished()));
3044+ }
3045+
3046 void SignOnPlugin::process(const SignOn::SessionData &inData,
3047 const QString &mechanism)
3048 {
3049- Q_UNUSED(mechanism);
3050+ if (!m_networkAccessManager) {
3051+ m_networkAccessManager = new QNetworkAccessManager(this);
3052+ }
3053+
3054+ initTr("ubuntuone-credentials", NULL);
3055+
3056 PluginData response;
3057 m_data = inData.data<PluginData>();
3058
3059- if (!inData.Secret().isEmpty()) {
3060- response.setConsumer(m_data.Consumer());
3061- response.setConsumerSecret(m_data.ConsumerSecret());
3062- response.setToken(m_data.Token());
3063- response.setTokenSecret(m_data.TokenSecret());
3064-
3065- response.setName(Token::buildTokenName());
3066-
3067- emit result(response);
3068- return;
3069- }
3070-
3071- SignOn::UiSessionData data;
3072- data.setRealm(inData.Realm());
3073- data.setShowRealm(!data.Realm().isEmpty());
3074- emit userActionRequired(data);
3075+ if (!validateInput(m_data, mechanism)) {
3076+ qWarning() << "Invalid parameters passed";
3077+ Q_EMIT error(SignOn::Error(SignOn::Error::MissingData));
3078+ return;
3079+ }
3080+
3081+ /* It may be that the stored token is valid; however, do the check only
3082+ * if no OTP was provided (since the presence of an OTP is a clear
3083+ * signal that the caller wants to get a new token). */
3084+ if (m_data.OneTimePassword().isEmpty() &&
3085+ respondWithStoredData()) {
3086+ return;
3087+ }
3088+
3089+ getCredentialsAndCreateNewToken();
3090+ }
3091+
3092+ void SignOnPlugin::onCreationFinished()
3093+ {
3094+ QNetworkReply *reply = m_reply;
3095+ m_reply->deleteLater();
3096+ m_reply = 0;
3097+
3098+ QByteArray data = reply->readAll();
3099+ qDebug() << "Received" << data;
3100+ QJsonDocument json = QJsonDocument::fromJson(data);
3101+ QJsonObject object = json.object();
3102+
3103+ QString error = object.value("code").toString();
3104+
3105+ int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
3106+ qDebug() << "Status code:" << statusCode;
3107+ if (statusCode == 200 || statusCode == 201) {
3108+ QString tokenName = object.value("token_name").toString();
3109+ PluginData token;
3110+ token.setConsumerKey(object.value("consumer_key").toString());
3111+ token.setConsumerSecret(object.value("consumer_secret").toString());
3112+ token.setTokenKey(object.value("token_key").toString());
3113+ token.setTokenSecret(object.value("token_secret").toString());
3114+
3115+ /* Store the token */
3116+ QVariantMap storedData;
3117+ storedData[tokenName] = token.toMap();
3118+ PluginData pluginData;
3119+ pluginData.setStoredData(storedData);
3120+ Q_EMIT store(pluginData);
3121+
3122+ token.setTokenName(tokenName);
3123+ Q_EMIT result(token);
3124+ } else if (statusCode == 401 && error == ERR_INVALID_CREDENTIALS) {
3125+ m_data.setSecret(QString());
3126+ m_data.setOneTimePassword(QString());
3127+ getCredentialsAndCreateNewToken();
3128+ } else if (statusCode == 401 && error == ERR_TWOFACTOR_REQUIRED) {
3129+ m_needsOtp = true;
3130+ getCredentialsAndCreateNewToken();
3131+ } else if (statusCode == 403 && error == ERR_TWOFACTOR_FAILURE) {
3132+ m_data.setOneTimePassword(QString());
3133+ getCredentialsAndCreateNewToken();
3134+ } else if (statusCode == 403 && error == ERR_PASSWORD_POLICY_ERROR) {
3135+ QVariantMap data;
3136+ QJsonObject extra = object.value("extra").toObject();
3137+ data[SSOUI_KEY_OPENURL] = extra.value("location").toString();
3138+ Q_EMIT userActionRequired(data);
3139+ } else if (error == ERR_INVALID_DATA) {
3140+ // This error is received when the email address is invalid
3141+ m_data.setUserName(QString());
3142+ m_data.setSecret(QString());
3143+ m_data.setOneTimePassword(QString());
3144+ getCredentialsAndCreateNewToken();
3145+ } else {
3146+ emitErrorFromReply(reply);
3147+ }
3148+ }
3149+
3150+ void SignOnPlugin::createNewToken()
3151+ {
3152+ QNetworkRequest req(QUrl(BASE_URL "/api/v2/tokens/oauth"));
3153+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
3154+
3155+ QJsonObject formData;
3156+ formData.insert("email", m_data.UserName());
3157+ formData.insert("password", m_data.Secret());
3158+ formData.insert("token_name", m_data.TokenName());
3159+ if (!m_data.OneTimePassword().isEmpty()) {
3160+ formData.insert("otp", m_data.OneTimePassword());
3161+ }
3162+
3163+ qDebug() << "Sending data for token creation";
3164+ m_reply =
3165+ m_networkAccessManager->post(req, QJsonDocument(formData).toJson());
3166+ QObject::connect(m_reply, SIGNAL(finished()),
3167+ this, SLOT(onCreationFinished()));
3168+ }
3169+
3170+ void SignOnPlugin::getCredentialsAndCreateNewToken()
3171+ {
3172+ if (!m_data.Secret().isEmpty() &&
3173+ (!m_needsOtp || !m_data.OneTimePassword().isEmpty())) {
3174+ createNewToken();
3175+ } else if (m_data.Secret().isEmpty()) {
3176+ QVariantMap data;
3177+ data[SSOUI_KEY_TITLE] = _("UbuntuOne authentication");
3178+ data[SSOUI_KEY_QUERYUSERNAME] = true;
3179+ data[SSOUI_KEY_USERNAME] = m_data.UserName();
3180+ data[SSOUI_KEY_QUERYPASSWORD] = true;
3181+ m_didAskForPassword = true;
3182+ Q_EMIT userActionRequired(data);
3183+ } else {
3184+ QVariantMap data;
3185+ data[SSOUI_KEY_TITLE] = _("UbuntuOne authentication");
3186+ data[SSOUI_KEY_USERNAME] = m_data.UserName();
3187+ data[SSOUI_KEY_PASSWORD] = m_data.Secret();
3188+ data[SSOUI_KEY_QUERY2FA] = true;
3189+ data[SSOUI_KEY_2FA_TEXT] = _("2-factor device code");
3190+ Q_EMIT userActionRequired(data);
3191+ }
3192+ }
3193+
3194+ bool SignOnPlugin::handleUiError(const SignOn::UiSessionData &data)
3195+ {
3196+ using namespace SignOn;
3197+
3198+ int code = data.QueryErrorCode();
3199+ if (code == QUERY_ERROR_NONE) {
3200+ return false;
3201+ }
3202+
3203+ qDebug() << "userActionFinished with error: " << code;
3204+ if (code == QUERY_ERROR_CANCELED) {
3205+ Q_EMIT error(Error(Error::SessionCanceled,
3206+ QLatin1String("Cancelled by user")));
3207+ } else if (code == QUERY_ERROR_NETWORK) {
3208+ Q_EMIT error(Error(Error::Network, QLatin1String("Network error")));
3209+ } else if (code == QUERY_ERROR_SSL) {
3210+ Q_EMIT error(Error(Error::Ssl, QLatin1String("SSL error")));
3211+ } else {
3212+ QVariantMap map = data.toMap();
3213+ if (map.contains(SSOUI_KEY_QUERY2FA)) {
3214+ PluginData reply;
3215+ reply.setU1ErrorCode(PluginData::OneTimePasswordRequired);
3216+ Q_EMIT result(reply);
3217+ } else if (map.contains(SSOUI_KEY_QUERYPASSWORD)) {
3218+ PluginData reply;
3219+ reply.setU1ErrorCode(PluginData::InvalidPassword);
3220+ Q_EMIT result(reply);
3221+ } else {
3222+ Q_EMIT error(Error(Error::UserInteraction,
3223+ QString("userActionFinished error: ")
3224+ + QString::number(data.QueryErrorCode())));
3225+ }
3226+ }
3227+ return true;
3228 }
3229
3230 void SignOnPlugin::userActionFinished(const SignOn::UiSessionData &data)
3231 {
3232+ if (handleUiError(data)) return;
3233+
3234+ PluginData uiData = data.data<PluginData>();
3235+ if (!uiData.UserName().isEmpty()) {
3236+ m_data.setUserName(uiData.UserName());
3237+ }
3238+
3239+ if (!uiData.Secret().isEmpty()) {
3240+ m_data.setSecret(uiData.Secret());
3241+ }
3242+
3243+ QVariantMap map = data.toMap();
3244+ QString oneTimePassword = map.value(SSOUI_KEY_2FA).toString();
3245+ if (!oneTimePassword.isEmpty()) {
3246+ m_data.setOneTimePassword(oneTimePassword);
3247+ }
3248+
3249+ getCredentialsAndCreateNewToken();
3250 }
3251
3252 SIGNON_DECL_AUTH_PLUGIN(SignOnPlugin)
3253
3254=== modified file 'signon-plugin/ubuntuone-plugin.h'
3255--- signon-plugin/ubuntuone-plugin.h 2013-08-12 21:16:29 +0000
3256+++ signon-plugin/ubuntuone-plugin.h 2016-04-22 09:51:32 +0000
3257@@ -27,9 +27,15 @@
3258
3259 #include "ubuntuonedata.h"
3260
3261+class PluginTest;
3262+
3263+class QNetworkAccessManager;
3264+class QNetworkReply;
3265
3266 namespace UbuntuOne {
3267
3268+ class Token;
3269+
3270 class SignOnPlugin : public AuthPluginInterface
3271 {
3272 Q_OBJECT
3273@@ -40,17 +46,49 @@
3274 virtual ~SignOnPlugin();
3275
3276 public Q_SLOTS:
3277- QString type() const;
3278- QStringList mechanisms() const;
3279- void cancel();
3280+ QString type() const Q_DECL_OVERRIDE;
3281+ QStringList mechanisms() const Q_DECL_OVERRIDE;
3282+ void cancel() Q_DECL_OVERRIDE;
3283 void process(const SignOn::SessionData &inData,
3284- const QString &mechanism = 0);
3285- void userActionFinished(const SignOn::UiSessionData &data);
3286-
3287- private:
3288+ const QString &mechanism = 0) Q_DECL_OVERRIDE;
3289+ void userActionFinished(const SignOn::UiSessionData &data) Q_DECL_OVERRIDE;
3290+
3291+ private:
3292+ bool validateInput(const PluginData &data, const QString &mechanism);
3293+ bool respondWithStoredData();
3294+ void checkTokenValidity(const Token &token,
3295+ const PluginData &tokenData);
3296+ void emitErrorFromReply(QNetworkReply *reply);
3297+ void createNewToken();
3298+ void getCredentialsAndCreateNewToken();
3299+ bool handleUiError(const SignOn::UiSessionData &data);
3300+
3301+ private Q_SLOTS:
3302+ void onValidationFinished();
3303+ void onCreationFinished();
3304+
3305+ private:
3306+ friend class ::PluginTest;
3307 PluginData m_data;
3308+ PluginData m_checkedToken;
3309+ QNetworkAccessManager *m_networkAccessManager;
3310+ QNetworkReply *m_reply;
3311+ bool m_didAskForPassword;
3312+ bool m_needsOtp;
3313 };
3314
3315 } // namespace UbuntuOne
3316
3317+/* These fields are temporarily defined here; they'll be eventually moved to
3318+ * signond's include files. */
3319+#define SSOUI_KEY_USERNAME_TEXT QLatin1String("UserNameText")
3320+#define SSOUI_KEY_PASSWORD_TEXT QLatin1String("PasswordText")
3321+#define SSOUI_KEY_REGISTER_URL QLatin1String("RegisterUrl")
3322+#define SSOUI_KEY_REGISTER_TEXT QLatin1String("RegisterText")
3323+#define SSOUI_KEY_LOGIN_TEXT QLatin1String("LoginText")
3324+#define SSOUI_KEY_QUERY2FA QLatin1String("Query2fa")
3325+#define SSOUI_KEY_2FA QLatin1String("2fa")
3326+#define SSOUI_KEY_2FA_TEXT QLatin1String("2faText")
3327+#define SSOUI_KEY_ERROR_MESSAGE QLatin1String("ErrorMessage")
3328+
3329 #endif
3330
3331=== modified file 'signon-plugin/ubuntuonedata.h'
3332--- signon-plugin/ubuntuonedata.h 2013-08-12 21:16:29 +0000
3333+++ signon-plugin/ubuntuonedata.h 2016-04-22 09:51:32 +0000
3334@@ -25,16 +25,33 @@
3335 class PluginData : public SignOn::SessionData
3336 {
3337 public:
3338+ PluginData(const QVariantMap &data = QVariantMap()):
3339+ SignOn::SessionData(data) {}
3340+
3341 // The name of the token
3342- SIGNON_SESSION_DECLARE_PROPERTY(QString, Name);
3343+ SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenName);
3344+
3345+ // The one-time password (optional)
3346+ SIGNON_SESSION_DECLARE_PROPERTY(QString, OneTimePassword);
3347
3348 // The consumer key and secret for signing
3349- SIGNON_SESSION_DECLARE_PROPERTY(QString, Consumer);
3350+ SIGNON_SESSION_DECLARE_PROPERTY(QString, ConsumerKey);
3351 SIGNON_SESSION_DECLARE_PROPERTY(QString, ConsumerSecret);
3352
3353 // The access token and secret for signing
3354- SIGNON_SESSION_DECLARE_PROPERTY(QString, Token);
3355+ SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenKey);
3356 SIGNON_SESSION_DECLARE_PROPERTY(QString, TokenSecret);
3357+
3358+ // Error code
3359+ enum ErrorCode {
3360+ NoError = 0,
3361+ OneTimePasswordRequired,
3362+ InvalidPassword,
3363+ };
3364+ SIGNON_SESSION_DECLARE_PROPERTY(int, U1ErrorCode);
3365+
3366+ // Data which the plugin has stored into signond
3367+ SIGNON_SESSION_DECLARE_PROPERTY(QVariantMap, StoredData);
3368 };
3369
3370 } // namespace UbuntuOne

Subscribers

People subscribed via source and target branches