Merge lp:~dobey/ubuntuone-credentials/signon-plugin-part2 into lp:ubuntuone-credentials

Proposed by dobey
Status: Rejected
Rejected by: Natalia Bidart
Proposed branch: lp:~dobey/ubuntuone-credentials/signon-plugin-part2
Merge into: lp:ubuntuone-credentials
Prerequisite: lp:~dobey/ubuntuone-credentials/default-token-name
Diff against target: 1267 lines (+501/-370)
15 files modified
data/ubuntuone.provider (+2/-2)
debian/control (+2/-1)
debian/libubuntuoneauth-2.0-0.symbols (+1/-0)
libubuntuoneauth/CMakeLists.txt (+4/-0)
libubuntuoneauth/authenticator.cpp (+211/-0)
libubuntuoneauth/authenticator.h (+78/-0)
libubuntuoneauth/keyring.cpp (+50/-44)
libubuntuoneauth/keyring.h (+7/-5)
libubuntuoneauth/libubuntuoneauth.symbols (+2/-1)
libubuntuoneauth/ssoservice.cpp (+53/-18)
libubuntuoneauth/ssoservice.h (+2/-2)
signon-plugin/tests/test_plugin.cpp (+40/-125)
signon-plugin/ubuntuone-plugin.cpp (+42/-168)
signon-plugin/ubuntuone-plugin.h (+2/-4)
signon-plugin/ubuntuonedata.h (+5/-0)
To merge this branch: bzr merge lp:~dobey/ubuntuone-credentials/signon-plugin-part2
Reviewer Review Type Date Requested Status
unity-api-1-bot continuous-integration Approve
Alberto Mardegan (community) Needs Fixing
PS Jenkins bot continuous-integration Pending
Review via email: mp+296716@code.launchpad.net

Commit message

Final pieces for switching to the new signon plug-in.

To post a comment you must log in.
Revision history for this message
Alberto Mardegan (mardy) wrote :

Couple of inline commits.

review: Needs Fixing
Revision history for this message
dobey (dobey) wrote :

Replied inline.

Revision history for this message
Alberto Mardegan (mardy) wrote :

Replied inline, added one comment.

266. By dobey

Refactor so createSession/process are called after identity is stored.
Fix invalidateCredentials to actually invalidate the credentials.

267. By dobey

Emit tokenNotFound immediately if no account is found.

268. By dobey

Need to depend on signond, since apparently nothing else does.

269. By dobey

Try to handle process() calls better.
Fix a few small typos.

Revision history for this message
Alberto Mardegan (mardy) wrote :

A couple of inline comments, it looks 99.9% good :-)

270. By dobey

Set the m_invalidate flag properly.

271. By dobey

Refactor token clearing to separate method.
Handle existing secret separately from invalidation request.

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:271
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntuone-credentials-ci/8/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/99
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/106
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/63
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial/63
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/40/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial/40/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/40/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial/40/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/40/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial/40
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial/40/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntuone-credentials-ci/8/rebuild

review: Approve (continuous-integration)
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Started a massive cleanup of old MPs, closing this given its age, please update and re-open if still valid.

Unmerged revisions

271. By dobey

Refactor token clearing to separate method.
Handle existing secret separately from invalidation request.

270. By dobey

Set the m_invalidate flag properly.

269. By dobey

Try to handle process() calls better.
Fix a few small typos.

268. By dobey

Need to depend on signond, since apparently nothing else does.

267. By dobey

Emit tokenNotFound immediately if no account is found.

266. By dobey

Refactor so createSession/process are called after identity is stored.
Fix invalidateCredentials to actually invalidate the credentials.

265. By dobey

The login() call should never emit credentialsNotFound.
Don't emit an error in existingCredentialsId if there is no account.

264. By dobey

Must use 'unsigned int' in symbols file for quint32.

263. By dobey

Hopefully store the token correctly now too.

262. By dobey

Try to store the credentials a different way.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'data/ubuntuone.provider'
--- data/ubuntuone.provider 2016-04-19 15:04:15 +0000
+++ data/ubuntuone.provider 2016-06-21 17:27:01 +0000
@@ -8,8 +8,8 @@
88
9 <template>9 <template>
10 <group name="auth">10 <group name="auth">
11 <setting name="method">password</setting>11 <setting name="method">ubuntuone</setting>
12 <setting name="mechanism">password</setting>12 <setting name="mechanism">ubuntuone</setting>
13 </group>13 </group>
14 </template>14 </template>
15</provider>15</provider>
1616
=== modified file 'debian/control'
--- debian/control 2016-05-19 17:41:38 +0000
+++ debian/control 2016-06-21 17:27:01 +0000
@@ -76,7 +76,7 @@
76 ${misc:Pre-Depends},76 ${misc:Pre-Depends},
77Depends:77Depends:
78 account-plugin-tools,78 account-plugin-tools,
79 signon-plugin-password,79 signon-plugin-ubuntuone (= ${source:Version}),
80 sqlite3,80 sqlite3,
81 ubuntuone-credentials-common (= ${source:Version}),81 ubuntuone-credentials-common (= ${source:Version}),
82 ${misc:Depends},82 ${misc:Depends},
@@ -142,6 +142,7 @@
142 ${misc:Pre-Depends},142 ${misc:Pre-Depends},
143Depends:143Depends:
144 libubuntuoneauth-2.0-0 (= ${binary:Version}),144 libubuntuoneauth-2.0-0 (= ${binary:Version}),
145 signond,
145 ${misc:Depends},146 ${misc:Depends},
146 ${shlibs:Depends},147 ${shlibs:Depends},
147Description: Ubuntu One authentication library - signon plug-in148Description: Ubuntu One authentication library - signon plug-in
148149
=== modified file 'debian/libubuntuoneauth-2.0-0.symbols'
--- debian/libubuntuoneauth-2.0-0.symbols 2016-04-22 08:25:24 +0000
+++ debian/libubuntuoneauth-2.0-0.symbols 2016-06-21 17:27:01 +0000
@@ -122,6 +122,7 @@
122 (c++)"UbuntuOne::Token::addOAuthTimestamp(QString) const@Base" 15.11+16.04.20151207.1122 (c++)"UbuntuOne::Token::addOAuthTimestamp(QString) const@Base" 15.11+16.04.20151207.1
123 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token)@Base" 13.08123 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token)@Base" 13.08
124 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token, QString const&)@Base" 14.04+14.10.20140802124 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token, QString const&)@Base" 14.04+14.10.20140802
125 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token, QString const&, unsigned int)@Base" 0replaceme
125 (c++)"UbuntuOne::Keyring::tokenFound(UbuntuOne::Token const&)@Base" 13.08126 (c++)"UbuntuOne::Keyring::tokenFound(UbuntuOne::Token const&)@Base" 13.08
126 (c++)"UbuntuOne::Keyring::deleteToken()@Base" 13.08127 (c++)"UbuntuOne::Keyring::deleteToken()@Base" 13.08
127 (c++)"UbuntuOne::Keyring::handleError(SignOn::Error const&)@Base" 13.08128 (c++)"UbuntuOne::Keyring::handleError(SignOn::Error const&)@Base" 13.08
128129
=== modified file 'libubuntuoneauth/CMakeLists.txt'
--- libubuntuoneauth/CMakeLists.txt 2016-04-21 09:25:58 +0000
+++ libubuntuoneauth/CMakeLists.txt 2016-06-21 17:27:01 +0000
@@ -13,6 +13,10 @@
13 SET (LIB_TYPE STATIC)13 SET (LIB_TYPE STATIC)
14ENDIF (BUILD_STATIC_LIBS)14ENDIF (BUILD_STATIC_LIBS)
1515
16# Some slots are deprecated; disable warnings on deprecations, because
17# moc-generated files are using these methods
18SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
19
16# The sources for building the library20# The sources for building the library
17FILE (GLOB SOURCES *.cpp)21FILE (GLOB SOURCES *.cpp)
18# HEADERS only includes the public headers for installation.22# HEADERS only includes the public headers for installation.
1923
=== added file 'libubuntuoneauth/authenticator.cpp'
--- libubuntuoneauth/authenticator.cpp 1970-01-01 00:00:00 +0000
+++ libubuntuoneauth/authenticator.cpp 2016-06-21 17:27:01 +0000
@@ -0,0 +1,211 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "authenticator.h"
20#include "../signon-plugin/ubuntuonedata.h"
21
22#include <Accounts/Account>
23#include <Accounts/Service>
24#include <SignOn/AuthSession>
25#include <SignOn/Identity>
26#include <SignOn/IdentityInfo>
27
28#include <QDebug>
29
30using namespace Internal;
31using namespace UbuntuOne;
32
33Authenticator::Authenticator(Accounts::Manager *manager, QObject *parent):
34 QObject(parent),
35 m_manager(manager),
36 m_invalidate(false),
37 m_uiAllowed(true),
38 m_credentialsId(0)
39{
40 if (!m_manager) {
41 m_manager = new Accounts::Manager(this);
42 }
43}
44
45void Authenticator::handleError(const SignOn::Error &e)
46{
47 qCritical() << "Authentication error:" << e.message();
48 Q_EMIT error(AuthenticationError, e.message());
49}
50
51void Authenticator::handleSessionData(const SignOn::SessionData &data)
52{
53 PluginData reply = data.data<PluginData>();
54
55 auto errorCode = PluginData::ErrorCode(reply.U1ErrorCode());
56 auto message = reply.U1ErrorMessage();
57 if (errorCode != PluginData::NoError) {
58 switch (errorCode) {
59 case PluginData::OneTimePasswordRequired:
60 qDebug() << "Error: OTP required";
61 Q_EMIT error(OneTimePasswordRequired, message);
62 break;
63 case PluginData::InvalidPassword:
64 qDebug() << "Error: invalid password";
65 Q_EMIT error(InvalidPassword, message);
66 break;
67 default:
68 qWarning() << "Unknown error:" << message;
69 Q_EMIT error(AuthenticationError, message);
70 }
71 return;
72 }
73
74 Token token(reply.TokenKey(), reply.TokenSecret(),
75 reply.ConsumerKey(), reply.ConsumerSecret(),
76 reply.DateCreated(), reply.DateUpdated());
77 Q_EMIT authenticated(token);
78}
79
80quint32 Authenticator::credentialsId()
81{
82 return m_credentialsId;
83}
84
85quint32 Authenticator::existingCredentialsId()
86{
87 QString providerId("ubuntuone");
88 Accounts::AccountIdList accountIds = m_manager->accountList(providerId);
89
90 if (accountIds.isEmpty()) {
91 qDebug() << "authenticate(): No UbuntuOne accounts found";
92 return 0;
93 }
94
95 if (Q_UNLIKELY(accountIds.count() > 1)) {
96 qWarning() << "authenticate(): Found '" << accountIds.count() <<
97 "' accounts. Using first.";
98 }
99
100 qDebug() << "authenticate(): Using account '" << accountIds[0] << "'.";
101
102 auto account = m_manager->account(accountIds[0]);
103 if (Q_UNLIKELY(!account)) {
104 qDebug() << "Couldn't load account";
105 /* This could either happen because the account was deleted right while
106 * we were loading it, or because the accounts DB was locked by another
107 * app. Let's just return an authentication error here, so the client
108 * can retry.
109 */
110 Q_EMIT error(AuthenticationError, "");
111 return 0;
112 }
113
114 /* Here we should check that the account service is enabled; but since the
115 * old code was not doing this check, and that from the API there is no way
116 * of knowing which service we are interested in, let's leave it as a TODO.
117 */
118
119 return account->credentialsId();
120}
121
122void Authenticator::process(SignOn::Identity *identity,
123 const QString &tokenName,
124 const QString &userName,
125 const QString &password,
126 const QString &otp)
127{
128 auto session = identity->createSession(QStringLiteral("ubuntuone"));
129 if (Q_UNLIKELY(!session)) {
130 qCritical() << "Unable to create AuthSession.";
131 Q_EMIT error(AuthenticationError, "");
132 return;
133 }
134
135 connect(session, SIGNAL(response(const SignOn::SessionData&)),
136 this, SLOT(handleSessionData(const SignOn::SessionData&)));
137 connect(session, SIGNAL(error(const SignOn::Error&)),
138 this, SLOT(handleError(const SignOn::Error&)));
139
140 PluginData data;
141 data.setTokenName(tokenName);
142 data.setUserName(userName);
143 data.setSecret(password);
144 data.setOneTimePassword(otp);
145 int uiPolicy = m_uiAllowed ?
146 SignOn::DefaultPolicy : SignOn::NoUserInteractionPolicy;
147 data.setUiPolicy(uiPolicy);
148 if (m_invalidate) {
149 data.setInvalidateToken(true);
150 m_invalidate = false;
151 }
152
153 session->process(data, QStringLiteral("ubuntuone"));
154}
155
156void Authenticator::authenticate(const QString &tokenName,
157 const QString &userName,
158 const QString &password,
159 const QString &otp)
160{
161 SignOn::Identity *identity;
162 m_credentialsId = existingCredentialsId();
163
164 if (m_credentialsId == 0) {
165 SignOn::IdentityInfo info = SignOn::IdentityInfo();
166
167 info.setAccessControlList(QStringList() << "unconfined");
168 info.setStoreSecret(false);
169
170 identity = SignOn::Identity::newIdentity(info, this);
171 connect(identity, &SignOn::Identity::credentialsStored,
172 [=](const quint32 id) {
173 m_credentialsId = id;
174 process(identity, tokenName, userName, password, otp);
175 });
176 connect(identity, &SignOn::Identity::error,
177 [=](const SignOn::Error &err) {
178 qCritical() << "authenticate(): unable to store identity";
179 Q_EMIT error(AccountNotFound, "");
180 });
181 identity->storeCredentials(info);
182 } else {
183 identity = SignOn::Identity::existingIdentity(m_credentialsId, this);
184 if (Q_UNLIKELY(!identity)) {
185 qCritical() << "authenticate(): unable to load credentials" << m_credentialsId;
186 Q_EMIT error(AccountNotFound, "");
187 return;
188 }
189 process(identity, tokenName, userName, password, otp);
190 }
191
192}
193
194void Authenticator::invalidateCredentials()
195{
196 m_credentialsId = existingCredentialsId();
197 if (m_credentialsId == 0) {
198 qCritical() << "invalidateCredentials(): no existing credentials.";
199 Q_EMIT error(AccountNotFound, "");
200 return;
201 }
202
203 m_invalidate = true;
204 auto identity = SignOn::Identity::existingIdentity(m_credentialsId, this);
205 process(identity, Token::buildTokenName());
206}
207
208void Authenticator::setUiAllowed(bool allowed)
209{
210 m_uiAllowed = allowed;
211}
0212
=== added file 'libubuntuoneauth/authenticator.h'
--- libubuntuoneauth/authenticator.h 1970-01-01 00:00:00 +0000
+++ libubuntuoneauth/authenticator.h 2016-06-21 17:27:01 +0000
@@ -0,0 +1,78 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18#ifndef _U1_AUTHENTICATOR_H_
19#define _U1_AUTHENTICATOR_H_
20
21#include <Accounts/Manager>
22#include <SignOn/Identity>
23
24#include <QObject>
25
26#include "token.h"
27
28namespace Internal {
29
30class Authenticator : public QObject
31{
32 Q_OBJECT
33
34public:
35 enum ErrorCode {
36 NoError = 0,
37 AccountNotFound,
38 OneTimePasswordRequired,
39 InvalidPassword,
40 AuthenticationError, // will create more specific codes if needed
41 };
42
43 explicit Authenticator(Accounts::Manager *manager = 0, QObject *parent = 0);
44
45 void process(SignOn::Identity *identity,
46 const QString &tokenName,
47 const QString &userName = QString(),
48 const QString &password = QString(),
49 const QString &otp = QString());
50 void authenticate(const QString &tokenName,
51 const QString &userName = QString(),
52 const QString &password = QString(),
53 const QString &otp = QString());
54 void invalidateCredentials();
55 void setUiAllowed(bool allowed);
56 quint32 credentialsId();
57
58Q_SIGNALS:
59 void authenticated(const UbuntuOne::Token& token);
60 void error(Internal::Authenticator::ErrorCode code, const QString& message);
61
62private:
63 quint32 existingCredentialsId();
64
65private Q_SLOTS:
66 void handleError(const SignOn::Error &error);
67 void handleSessionData(const SignOn::SessionData &data);
68
69private:
70 Accounts::Manager *m_manager;
71 bool m_invalidate;
72 bool m_uiAllowed;
73 quint32 m_credentialsId;
74};
75
76} /* namespace */
77
78#endif /* _U1_AUTHENTICATOR_H_ */
079
=== modified file 'libubuntuoneauth/keyring.cpp'
--- libubuntuoneauth/keyring.cpp 2016-04-19 15:04:15 +0000
+++ libubuntuoneauth/keyring.cpp 2016-06-21 17:27:01 +0000
@@ -23,7 +23,9 @@
2323
24#include <QDebug>24#include <QDebug>
2525
26#include "authenticator.h"
26#include "keyring.h"27#include "keyring.h"
28#include "../signon-plugin/ubuntuonedata.h"
2729
28using namespace Accounts;30using namespace Accounts;
29using namespace SignOn;31using namespace SignOn;
@@ -68,36 +70,35 @@
6870
69 void Keyring::findToken()71 void Keyring::findToken()
70 {72 {
73 using namespace Internal;
74
71 QString _acctName("ubuntuone");75 QString _acctName("ubuntuone");
72 AccountIdList _ids = _manager.accountList(_acctName);76 AccountIdList _ids = _manager.accountList(_acctName);
73 Identity *identity;
74 Account *account;
7577
76 if (_ids.length() > 0) {78 if (_ids.isEmpty()) {
77 if (_ids.length() > 1) {79 qDebug() << "authenticate(): No UbuntuOne accounts found";
78 qDebug() << "findToken(): Found '" << _ids.length() << "' accounts. Using first.";80 Q_EMIT tokenNotFound();
79 }81 return;
80 account = _manager.account(_ids[0]);
81 qDebug() << "findToken(): Using Ubuntu One account '" << _ids[0] << "'.";
82 identity = Identity::existingIdentity(account->credentialsId());
83 if (identity == NULL) {
84 qCritical() << "findToken(): disabled account " << _acctName << _ids[0];
85 emit tokenNotFound();
86 return;
87 }
88 AuthSession *session = identity->createSession(QStringLiteral("password"));
89 if (session != NULL) {
90 connect(session, SIGNAL(response(const SignOn::SessionData&)),
91 this, SLOT(handleSessionData(const SignOn::SessionData&)));
92 connect(session, SIGNAL(error(const SignOn::Error&)),
93 this, SLOT(handleError(const SignOn::Error&)));
94 session->process(SessionData(), QStringLiteral("password"));
95 return;
96 }
97 qCritical() << "Unable to create AuthSession.";
98 }82 }
99 qDebug() << "findToken(): No accounts found matching " << _acctName;83
100 emit tokenNotFound();84 auto authenticator = new Authenticator(&_manager);
85 authenticator->setUiAllowed(false);
86
87 connect(authenticator, &Authenticator::authenticated,
88 [=](const Token &token) {
89 Q_EMIT tokenFound(token);
90 authenticator->deleteLater();
91 });
92 connect(authenticator, &Authenticator::error,
93 [=](Authenticator::ErrorCode code) {
94 if (code == Authenticator::AccountNotFound) {
95 Q_EMIT tokenNotFound();
96 } else {
97 Q_EMIT keyringError("Authentication failed");
98 }
99 authenticator->deleteLater();
100 });
101 authenticator->authenticate(Token::buildTokenName());
101 }102 }
102103
103 void Keyring::handleCredentialsStored(const quint32 id)104 void Keyring::handleCredentialsStored(const quint32 id)
@@ -123,6 +124,11 @@
123124
124 void Keyring::storeToken(Token token, const QString& displayName)125 void Keyring::storeToken(Token token, const QString& displayName)
125 {126 {
127 storeToken(token, displayName, 0);
128 }
129
130 void Keyring::storeToken(Token token, const QString& displayName, quint32 credentialsId)
131 {
126 QString _acctName("ubuntuone");132 QString _acctName("ubuntuone");
127 AccountIdList _ids = _manager.accountList(_acctName);133 AccountIdList _ids = _manager.accountList(_acctName);
128 Identity *identity = NULL;134 Identity *identity = NULL;
@@ -143,26 +149,26 @@
143 _account->setDisplayName(displayName);149 _account->setDisplayName(displayName);
144 }150 }
145151
146 if(_account->credentialsId() == 0) {152 if (credentialsId == 0) {
147 qDebug() << "storeToken() : creating new Identity for account " << _account->id() ;153 if(_account->credentialsId() == 0) {
148 identity = Identity::newIdentity();154 qDebug() << "storeToken() : creating new Identity for account " << _account->id() ;
155 identity = Identity::newIdentity();
156 } else {
157 qDebug() << "storeToken(): identity found.";
158 identity = Identity::existingIdentity(_account->credentialsId());
159 }
160
161 Q_ASSERT(identity != NULL);
162
163 connect(identity, SIGNAL(error(const SignOn::Error&)),
164 this, SLOT(handleError(const SignOn::Error&)));
165 connect(identity, SIGNAL(credentialsStored(const quint32)),
166 this, SLOT(handleCredentialsStored(const quint32)));
167
168 identity->storeCredentials();
149 } else {169 } else {
150 qDebug() << "storeToken(): identity found.";170 handleCredentialsStored(credentialsId);
151 identity = Identity::existingIdentity(_account->credentialsId());
152 }171 }
153
154 Q_ASSERT(identity != NULL);
155
156 connect(identity, SIGNAL(error(const SignOn::Error&)),
157 this, SLOT(handleError(const SignOn::Error&)));
158 connect(identity, SIGNAL(credentialsStored(const quint32)),
159 this, SLOT(handleCredentialsStored(const quint32)));
160
161 IdentityInfo info = IdentityInfo();
162
163 info.setSecret(token.toQuery(), true);
164 info.setAccessControlList(QStringList() << "unconfined");
165 identity->storeCredentials(info);
166 }172 }
167173
168 void Keyring::handleAccountRemoved()174 void Keyring::handleAccountRemoved()
169175
=== modified file 'libubuntuoneauth/keyring.h'
--- libubuntuoneauth/keyring.h 2016-04-21 09:25:58 +0000
+++ libubuntuoneauth/keyring.h 2016-06-21 17:27:01 +0000
@@ -38,22 +38,24 @@
38 void findToken();38 void findToken();
39 void storeToken(Token token);39 void storeToken(Token token);
40 void storeToken(Token token, const QString& displayName);40 void storeToken(Token token, const QString& displayName);
41 void deleteToken();41 void storeToken(Token token, const QString& displayName,
42 quint32 credentialsId);
43 Q_DECL_DEPRECATED void deleteToken();
4244
43 Q_SIGNALS:45 Q_SIGNALS:
44 void tokenFound(const Token& token);46 void tokenFound(const Token& token);
45 void tokenNotFound();47 void tokenNotFound();
46 void tokenStored();48 void tokenStored();
47 void tokenDeleted();49 Q_DECL_DEPRECATED void tokenDeleted();
4850
49 void keyringError(QString message);51 void keyringError(QString message);
5052
51 private Q_SLOTS:53 private Q_SLOTS:
52 void handleError(const SignOn::Error &error);54 void handleError(const SignOn::Error &error);
53 void handleSessionData(const SignOn::SessionData &data);55 Q_DECL_DEPRECATED void handleSessionData(const SignOn::SessionData &data);
54 void handleCredentialsStored(const quint32 id);56 void handleCredentialsStored(const quint32 id);
55 void handleAccountRemoved();57 Q_DECL_DEPRECATED void handleAccountRemoved();
56 void handleDeleteError(const SignOn::Error &error);58 Q_DECL_DEPRECATED void handleDeleteError(const SignOn::Error &error);
5759
58 private:60 private:
59 Accounts::Manager _manager;61 Accounts::Manager _manager;
6062
=== modified file 'libubuntuoneauth/libubuntuoneauth.symbols'
--- libubuntuoneauth/libubuntuoneauth.symbols 2013-07-22 15:54:02 +0000
+++ libubuntuoneauth/libubuntuoneauth.symbols 2016-06-21 17:27:01 +0000
@@ -1,7 +1,8 @@
1{1{
2 global:2 global:
3 extern "C++" {3 extern "C++" {
4 *UbuntuOne::*;4 UbuntuOne::*;
5 *for?UbuntuOne::*;
5 };6 };
6 qt_*;7 qt_*;
7 local:8 local:
89
=== modified file 'libubuntuoneauth/ssoservice.cpp'
--- libubuntuoneauth/ssoservice.cpp 2016-04-19 15:04:15 +0000
+++ libubuntuoneauth/ssoservice.cpp 2016-06-21 17:27:01 +0000
@@ -22,6 +22,7 @@
22#include <QNetworkRequest>22#include <QNetworkRequest>
23#include <QUrlQuery>23#include <QUrlQuery>
2424
25#include "authenticator.h"
25#include "logging.h"26#include "logging.h"
26#include "ssoservice.h"27#include "ssoservice.h"
27#include "requests.h"28#include "requests.h"
@@ -45,8 +46,6 @@
4546
46 // create the keyring that will be used to store and retrieve the different tokens47 // create the keyring that will be used to store and retrieve the different tokens
47 _keyring = new Keyring(this);48 _keyring = new Keyring(this);
48 _nam = new QNetworkAccessManager(this);
49
50 connect(_keyring, SIGNAL(tokenFound(const Token&)),49 connect(_keyring, SIGNAL(tokenFound(const Token&)),
51 this, SLOT(handleCredentialsFound(const Token&)));50 this, SLOT(handleCredentialsFound(const Token&)));
52 connect(_keyring, SIGNAL(tokenNotFound()),51 connect(_keyring, SIGNAL(tokenNotFound()),
@@ -54,18 +53,9 @@
5453
55 connect(_keyring, SIGNAL(tokenStored()),54 connect(_keyring, SIGNAL(tokenStored()),
56 this, SLOT(handleTokenStored()));55 this, SLOT(handleTokenStored()));
57 connect(_keyring, SIGNAL(tokenDeleted()),
58 this, SLOT(handleTokenDeleted()));
59
60 connect(_keyring, SIGNAL(keyringError(QString)),56 connect(_keyring, SIGNAL(keyringError(QString)),
61 this, SLOT(handleKeyringError(QString)));57 this, SLOT(handleKeyringError(QString)));
6258
63 connect(_nam, SIGNAL(finished(QNetworkReply*)),
64 this, SLOT(accountPinged(QNetworkReply*)));
65
66 connect(&(_provider),
67 SIGNAL(OAuthTokenGranted(const OAuthTokenResponse&)),
68 this, SLOT(tokenReceived(const OAuthTokenResponse&)));
69 connect(&(_provider),59 connect(&(_provider),
70 SIGNAL(AccountGranted(const AccountResponse&)),60 SIGNAL(AccountGranted(const AccountResponse&)),
71 this, SLOT(accountRegistered(const AccountResponse&)));61 this, SLOT(accountRegistered(const AccountResponse&)));
@@ -116,12 +106,35 @@
116106
117 void SSOService::login(QString email, QString password, QString twoFactorCode)107 void SSOService::login(QString email, QString password, QString twoFactorCode)
118 {108 {
119 OAuthTokenRequest request(getAuthBaseUrl(),109 using namespace Internal;
120 email, password,110
121 Token::buildTokenName(), twoFactorCode);111 // Keep this here for now to ensure ABI compat.
122 _tempEmail = email;112 OAuthTokenRequest request;
123113
124 _provider.GetOAuthToken(request);114 auto authenticator = new Authenticator;
115 /* The caller of this API is assumed to have his own UI, so no support
116 * from SignOn UI is needed or even desired. */
117 authenticator->setUiAllowed(false);
118
119 connect(authenticator, &Authenticator::authenticated,
120 [=](const Token &token) {
121 _keyring->storeToken(token, email, authenticator->credentialsId());
122 authenticator->deleteLater();
123 });
124 connect(authenticator, &Authenticator::error,
125 [=](Authenticator::ErrorCode code, const QString& message) {
126 if (code == Authenticator::AccountNotFound) {
127 Q_EMIT requestFailed(ErrorResponse(0, "", "LOGIN_FAILED", message));
128 } else if (code == Authenticator::OneTimePasswordRequired) {
129 Q_EMIT twoFactorAuthRequired();
130 } else {
131 /* TODO: deliver a proper error response. */
132 Q_EMIT requestFailed(ErrorResponse(400, "", "LOGIN_FAILED", message));
133 }
134 authenticator->deleteLater();
135 });
136 authenticator->authenticate(Token::buildTokenName(),
137 email, password, twoFactorCode);
125 }138 }
126139
127 void SSOService::handleTwoFactorAuthRequired()140 void SSOService::handleTwoFactorAuthRequired()
@@ -160,7 +173,29 @@
160173
161 void SSOService::invalidateCredentials()174 void SSOService::invalidateCredentials()
162 {175 {
163 _keyring->deleteToken();176 using namespace Internal;
177
178 auto authenticator = new Authenticator;
179 /* The caller of this API is assumed to have his own UI, so no support
180 * from SignOn UI is needed or even desired. */
181 authenticator->setUiAllowed(false);
182
183 connect(authenticator, &Authenticator::authenticated,
184 [=](const Token &token) {
185 Q_EMIT credentialsFound(token);
186 authenticator->deleteLater();
187 });
188 connect(authenticator, &Authenticator::error,
189 [=](Authenticator::ErrorCode code, const QString& message) {
190 if (code == Authenticator::AccountNotFound) {
191 Q_EMIT credentialsNotFound();
192 } else {
193 /* TODO: deliver a proper error response. */
194 Q_EMIT requestFailed(ErrorResponse(400, "", "LOGIN_FAILED", message));
195 }
196 authenticator->deleteLater();
197 });
198 authenticator->invalidateCredentials();
164 }199 }
165200
166 void SSOService::errorOccurred(const ErrorResponse& error)201 void SSOService::errorOccurred(const ErrorResponse& error)
167202
=== modified file 'libubuntuoneauth/ssoservice.h'
--- libubuntuoneauth/ssoservice.h 2016-04-21 09:25:58 +0000
+++ libubuntuoneauth/ssoservice.h 2016-06-21 17:27:01 +0000
@@ -58,10 +58,10 @@
58 private slots:58 private slots:
59 void accountPinged(QNetworkReply*);59 void accountPinged(QNetworkReply*);
60 void handleTokenStored();60 void handleTokenStored();
61 void handleTokenDeleted() { emit credentialsDeleted(); };61 Q_DECL_DEPRECATED void handleTokenDeleted() { emit credentialsDeleted(); };
62 void handleCredentialsFound(const Token& token);62 void handleCredentialsFound(const Token& token);
63 void handleCredentialsNotFound();63 void handleCredentialsNotFound();
64 void tokenReceived(const OAuthTokenResponse& token);64 Q_DECL_DEPRECATED void tokenReceived(const OAuthTokenResponse& token);
65 void accountRegistered(const AccountResponse& account);65 void accountRegistered(const AccountResponse& account);
66 void errorOccurred(const ErrorResponse&);66 void errorOccurred(const ErrorResponse&);
67 void handleTwoFactorAuthRequired();67 void handleTwoFactorAuthRequired();
6868
=== modified file 'signon-plugin/tests/test_plugin.cpp'
--- signon-plugin/tests/test_plugin.cpp 2016-05-27 18:14:38 +0000
+++ signon-plugin/tests/test_plugin.cpp 2016-06-21 17:27:01 +0000
@@ -150,7 +150,6 @@
150 void testPluginMechanisms();150 void testPluginMechanisms();
151 void testStoredToken_data();151 void testStoredToken_data();
152 void testStoredToken();152 void testStoredToken();
153 void testUserInteraction();
154 void testTokenCreation_data();153 void testTokenCreation_data();
155 void testTokenCreation();154 void testTokenCreation();
156155
@@ -223,7 +222,6 @@
223{222{
224 QTest::addColumn<QVariantMap>("sessionData");223 QTest::addColumn<QVariantMap>("sessionData");
225 QTest::addColumn<int>("expectedErrorCode");224 QTest::addColumn<int>("expectedErrorCode");
226 QTest::addColumn<bool>("uiExpected");
227 QTest::addColumn<QVariantMap>("expectedResponse");225 QTest::addColumn<QVariantMap>("expectedResponse");
228 QTest::addColumn<QVariantMap>("expectedStore");226 QTest::addColumn<QVariantMap>("expectedStore");
229227
@@ -233,23 +231,8 @@
233231
234 QTest::newRow("empty") <<232 QTest::newRow("empty") <<
235 sessionData.toMap() <<233 sessionData.toMap() <<
236 -1 <<234 103 <<
237 true << QVariantMap() << QVariantMap();235 QVariantMap() << QVariantMap();
238
239 sessionData.setTokenName("helloworld");
240 sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd");
241 response.setConsumerKey("aAa");
242 response.setConsumerSecret("bBb");
243 response.setTokenKey("cCc");
244 response.setTokenSecret("dDd");
245 QVariantMap storedData;
246 storedData[sessionData.TokenName()] = response.toMap();
247 stored.setStoredData(storedData);
248 response.setTokenName(sessionData.TokenName());
249 QTest::newRow("in secret, valid") <<
250 sessionData.toMap() <<
251 -1 <<
252 false << response.toMap() << stored.toMap();
253236
254 sessionData = UbuntuOne::PluginData();237 sessionData = UbuntuOne::PluginData();
255 QString tokenName = UbuntuOne::Token::buildTokenName();238 QString tokenName = UbuntuOne::Token::buildTokenName();
@@ -268,18 +251,46 @@
268 response.setTokenSecret("ts");251 response.setTokenSecret("ts");
269 response.setTokenName(tokenName);252 response.setTokenName(tokenName);
270 stored = UbuntuOne::PluginData();253 stored = UbuntuOne::PluginData();
271 storedData.clear();
272 QTest::newRow("stored, valid") <<254 QTest::newRow("stored, valid") <<
273 sessionData.toMap() <<255 sessionData.toMap() <<
274 -1 <<256 -1 <<
275 false << response.toMap() << stored.toMap();257 response.toMap() << stored.toMap();
258
259 sessionData = UbuntuOne::PluginData();
260 sessionData.setStoredData(QVariantMap {
261 { tokenName, QVariantMap {
262 { "ConsumerKey", "ck" },
263 { "ConsumerSecret", "cs" },
264 { "TokenKey", "tk" },
265 { "TokenSecret", "ts" },
266 }},
267 });
268 sessionData.setInvalidateToken(true);
269 response = UbuntuOne::PluginData();
270 stored = UbuntuOne::PluginData();
271 QVariantMap storedData;
272 stored.setStoredData(storedData);
273 QTest::newRow("clearing token") <<
274 sessionData.toMap() <<
275 -1 <<
276 response.toMap() << stored.toMap();
277
278 sessionData = UbuntuOne::PluginData();
279 sessionData.setSecret("consumer_key=aAa&consumer_secret=bBb&name=helloworld&token=cCc&token_secret=dDd");
280 response = UbuntuOne::PluginData();
281 response.setTokenName(tokenName);
282 stored = UbuntuOne::PluginData();
283 stored.setStoredData(QVariantMap());
284 QTest::newRow("in secret, clearing") <<
285 sessionData.toMap() <<
286 -1 <<
287 response.toMap() << stored.toMap();
276}288}
277289
278void PluginTest::testStoredToken()290void PluginTest::testStoredToken()
279{291{
280 QFETCH(QVariantMap, sessionData);292 QFETCH(QVariantMap, sessionData);
281 QFETCH(int, expectedErrorCode);293 QFETCH(int, expectedErrorCode);
282 QFETCH(bool, uiExpected);
283 QFETCH(QVariantMap, expectedResponse);294 QFETCH(QVariantMap, expectedResponse);
284 QFETCH(QVariantMap, expectedStore);295 QFETCH(QVariantMap, expectedStore);
285296
@@ -292,7 +303,7 @@
292 m_testPlugin->process(sessionData, "ubuntuone");303 m_testPlugin->process(sessionData, "ubuntuone");
293 if (expectedErrorCode < 0) {304 if (expectedErrorCode < 0) {
294 QCOMPARE(error.count(), 0);305 QCOMPARE(error.count(), 0);
295 QTRY_COMPARE(userActionRequired.count(), uiExpected ? 1 : 0);306 QTRY_COMPARE(userActionRequired.count(), 0);
296 if (!expectedResponse.isEmpty()) {307 if (!expectedResponse.isEmpty()) {
297 QTRY_COMPARE(result.count(), 1);308 QTRY_COMPARE(result.count(), 1);
298 QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap();309 QVariantMap resp = result.at(0).at(0).value<SessionData>().toMap();
@@ -316,66 +327,6 @@
316 }327 }
317}328}
318329
319void PluginTest::testUserInteraction()
320{
321 QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
322 QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
323 QSignalSpy userActionRequired(m_testPlugin,
324 SIGNAL(userActionRequired(const SignOn::UiSessionData&)));
325 QSignalSpy store(m_testPlugin, SIGNAL(store(const SignOn::SessionData&)));
326
327 TestNetworkAccessManager *nam = new TestNetworkAccessManager;
328 m_testPlugin->m_networkAccessManager = nam;
329
330 UbuntuOne::PluginData sessionData;
331 sessionData.setTokenName("helloworld");
332 sessionData.setUserName("tom@example.com");
333 m_testPlugin->process(sessionData, "ubuntuone");
334
335 QTRY_COMPARE(userActionRequired.count(), 1);
336 QVariantMap data =
337 userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
338 /* We want the title to be there, but we don't care about its value here */
339 QVERIFY(data.contains(SSOUI_KEY_TITLE));
340 data.remove(SSOUI_KEY_TITLE);
341 QVariantMap expectedUserInteraction;
342 expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com";
343 expectedUserInteraction[SSOUI_KEY_QUERYPASSWORD] = true;
344 QCOMPARE(data, expectedUserInteraction);
345 userActionRequired.clear();
346
347 /* Prepare network reply */
348 TestNetworkReply *reply = new TestNetworkReply(this);
349 reply->setStatusCode(401);
350 reply->setContent("{\n"
351 " \"code\": \"TWOFACTOR_REQUIRED\",\n"
352 " \"message\": \"This account requires 2-factor authentication.\",\n"
353 " \"extra\": {}\n"
354 "}");
355 nam->setNextReply(reply);
356
357 QVariantMap userReply;
358 userReply[SSOUI_KEY_USERNAME] = "tom@example.com";
359 userReply[SSOUI_KEY_PASSWORD] = "s3cr3t";
360 m_testPlugin->userActionFinished(userReply);
361
362 /* Again the plugin should request user interaction, as OTP is required */
363 QTRY_COMPARE(userActionRequired.count(), 1);
364 data = userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
365 expectedUserInteraction.clear();
366 expectedUserInteraction[SSOUI_KEY_USERNAME] = "tom@example.com";
367 expectedUserInteraction[SSOUI_KEY_PASSWORD] = "s3cr3t";
368 expectedUserInteraction[SSOUI_KEY_QUERY2FA] = true;
369 /* We want the map to contain the SSOUI_KEY_2FA_TEXT, but we don't care
370 * about the value */
371 QVERIFY(data.contains(SSOUI_KEY_2FA_TEXT));
372 data.remove(SSOUI_KEY_2FA_TEXT);
373 /* Same goes for the title */
374 QVERIFY(data.contains(SSOUI_KEY_TITLE));
375 data.remove(SSOUI_KEY_TITLE);
376 QCOMPARE(data, expectedUserInteraction);
377}
378
379void PluginTest::testTokenCreation_data()330void PluginTest::testTokenCreation_data()
380{331{
381 QTest::addColumn<QVariantMap>("sessionData");332 QTest::addColumn<QVariantMap>("sessionData");
@@ -385,12 +336,10 @@
385 QTest::addColumn<int>("expectedErrorCode");336 QTest::addColumn<int>("expectedErrorCode");
386 QTest::addColumn<QVariantMap>("expectedResponse");337 QTest::addColumn<QVariantMap>("expectedResponse");
387 QTest::addColumn<QVariantMap>("expectedStore");338 QTest::addColumn<QVariantMap>("expectedStore");
388 QTest::addColumn<QVariantMap>("expectedUserInteraction");
389339
390 UbuntuOne::PluginData sessionData;340 UbuntuOne::PluginData sessionData;
391 UbuntuOne::PluginData response;341 UbuntuOne::PluginData response;
392 UbuntuOne::PluginData stored;342 UbuntuOne::PluginData stored;
393 QVariantMap userInteraction;
394343
395 // Successful creation, with password only344 // Successful creation, with password only
396 sessionData.setTokenName("helloworld");345 sessionData.setTokenName("helloworld");
@@ -420,7 +369,7 @@
420 " \"date_updated\": \"2013-01-11 12:43:23\"\n"369 " \"date_updated\": \"2013-01-11 12:43:23\"\n"
421 "}") <<370 "}") <<
422 -1 <<371 -1 <<
423 response.toMap() << stored.toMap() << userInteraction;372 response.toMap() << stored.toMap();
424 sessionData = UbuntuOne::PluginData();373 sessionData = UbuntuOne::PluginData();
425 response = UbuntuOne::PluginData();374 response = UbuntuOne::PluginData();
426 stored = UbuntuOne::PluginData();375 stored = UbuntuOne::PluginData();
@@ -430,8 +379,6 @@
430 sessionData.setTokenName("helloworld");379 sessionData.setTokenName("helloworld");
431 sessionData.setUserName("jim@example.com");380 sessionData.setUserName("jim@example.com");
432 sessionData.setSecret("s3cr3t");381 sessionData.setSecret("s3cr3t");
433 userInteraction[SSOUI_KEY_USERNAME] = "jim@example.com";
434 userInteraction[SSOUI_KEY_QUERYPASSWORD] = true;
435 QTest::newRow("wrong password") <<382 QTest::newRow("wrong password") <<
436 sessionData.toMap() <<383 sessionData.toMap() <<
437 -1 <<384 -1 <<
@@ -441,28 +388,8 @@
441 " \"extra\": {}\n"388 " \"extra\": {}\n"
442 "}") <<389 "}") <<
443 -1 <<390 -1 <<
444 response.toMap() << stored.toMap() << userInteraction;391 response.toMap() << stored.toMap();
445 sessionData = UbuntuOne::PluginData();392 sessionData = UbuntuOne::PluginData();
446 userInteraction.clear();
447
448 // Empty username
449 sessionData.setTokenName("helloworld");
450 sessionData.setSecret("s3cr3t");
451 userInteraction[SSOUI_KEY_QUERYUSERNAME] = true;
452 userInteraction[SSOUI_KEY_USERNAME] = "";
453 userInteraction[SSOUI_KEY_QUERYPASSWORD] = true;
454 QTest::newRow("empty username") <<
455 sessionData.toMap() <<
456 -1 <<
457 401 << QString("{\n"
458 " \"code\": \"INVALID_CREDENTIALS\",\n"
459 " \"message\": \"Missing username\",\n"
460 " \"extra\": {}\n"
461 "}") <<
462 -1 <<
463 response.toMap() << stored.toMap() << userInteraction;
464 sessionData = UbuntuOne::PluginData();
465 userInteraction.clear();
466393
467 // Network error while creating token394 // Network error while creating token
468 sessionData.setTokenName("helloworld");395 sessionData.setTokenName("helloworld");
@@ -473,14 +400,13 @@
473 int(QNetworkReply::SslHandshakeFailedError) <<400 int(QNetworkReply::SslHandshakeFailedError) <<
474 -1 << QString() <<401 -1 << QString() <<
475 int(SignOn::Error::Ssl) <<402 int(SignOn::Error::Ssl) <<
476 response.toMap() << stored.toMap() << userInteraction;403 response.toMap() << stored.toMap();
477 sessionData = UbuntuOne::PluginData();404 sessionData = UbuntuOne::PluginData();
478405
479 // Account needs reset406 // Account needs reset
480 sessionData.setTokenName("helloworld");407 sessionData.setTokenName("helloworld");
481 sessionData.setUserName("jim@example.com");408 sessionData.setUserName("jim@example.com");
482 sessionData.setSecret("s3cr3t");409 sessionData.setSecret("s3cr3t");
483 userInteraction[SSOUI_KEY_OPENURL] = "http://www.example.com/reset";
484 QTest::newRow("reset needed") <<410 QTest::newRow("reset needed") <<
485 sessionData.toMap() <<411 sessionData.toMap() <<
486 -1 <<412 -1 <<
@@ -492,9 +418,8 @@
492 " }\n"418 " }\n"
493 "}") <<419 "}") <<
494 -1 <<420 -1 <<
495 response.toMap() << stored.toMap() << userInteraction;421 response.toMap() << stored.toMap();
496 sessionData = UbuntuOne::PluginData();422 sessionData = UbuntuOne::PluginData();
497 userInteraction.clear();
498}423}
499424
500void PluginTest::testTokenCreation()425void PluginTest::testTokenCreation()
@@ -506,7 +431,6 @@
506 QFETCH(int, expectedErrorCode);431 QFETCH(int, expectedErrorCode);
507 QFETCH(QVariantMap, expectedResponse);432 QFETCH(QVariantMap, expectedResponse);
508 QFETCH(QVariantMap, expectedStore);433 QFETCH(QVariantMap, expectedStore);
509 QFETCH(QVariantMap, expectedUserInteraction);
510434
511 QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));435 QSignalSpy result(m_testPlugin, SIGNAL(result(const SignOn::SessionData&)));
512 QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));436 QSignalSpy error(m_testPlugin, SIGNAL(error(const SignOn::Error &)));
@@ -530,16 +454,7 @@
530454
531 m_testPlugin->process(sessionData, "ubuntuone");455 m_testPlugin->process(sessionData, "ubuntuone");
532 if (expectedErrorCode < 0) {456 if (expectedErrorCode < 0) {
533 if (!expectedUserInteraction.isEmpty()) {457 QCOMPARE(userActionRequired.count(), 0);
534 QTRY_COMPARE(userActionRequired.count(), 1);
535 QVariantMap data =
536 userActionRequired.at(0).at(0).value<UiSessionData>().toMap();
537 /* We don't care about the title here */
538 data.remove(SSOUI_KEY_TITLE);
539 QCOMPARE(data, expectedUserInteraction);
540 } else {
541 QCOMPARE(userActionRequired.count(), 0);
542 }
543458
544 if (!expectedResponse.isEmpty()) {459 if (!expectedResponse.isEmpty()) {
545 QTRY_COMPARE(result.count(), 1);460 QTRY_COMPARE(result.count(), 1);
546461
=== modified file 'signon-plugin/ubuntuone-plugin.cpp'
--- signon-plugin/ubuntuone-plugin.cpp 2016-05-27 15:36:40 +0000
+++ signon-plugin/ubuntuone-plugin.cpp 2016-06-21 17:27:01 +0000
@@ -69,62 +69,17 @@
69 {69 {
70 }70 }
7171
72 bool SignOnPlugin::respondWithStoredData()72 void SignOnPlugin::respondWithStoredData()
73 {73 {
74 QVariantMap storedData = m_data.StoredData();74 QVariantMap storedData = m_data.StoredData();
7575
76 /* When U1 was using the password plugin, it was storing the token data76 /* Always use the same token name for now */
77 * in the password field. So, if we don't have any data stored in the77 m_data.setTokenName(Token::buildTokenName());
78 * plugin's data, try to get a token from the password field.
79 */
80 if (storedData.isEmpty() && !m_data.Secret().isEmpty()) {
81 Token *token = Token::fromQuery(m_data.Secret());
82 if (token->isValid()) {
83 PluginData tokenData;
84 tokenData.setConsumerKey(token->consumerKey());
85 tokenData.setConsumerSecret(token->consumerSecret());
86 tokenData.setTokenKey(token->tokenKey());
87 tokenData.setTokenSecret(token->tokenSecret());
88 QDateTime time = token->updated();
89 if (time.isValid()) {
90 tokenData.setDateUpdated(time.toString(Qt::ISODate));
91 }
92 time = token->created();
93 if (time.isValid()) {
94 tokenData.setDateCreated(time.toString(Qt::ISODate));
95 }
96 storedData[token->name()] = tokenData.toMap();
97 PluginData pluginData;
98 pluginData.setStoredData(storedData);
99 Q_EMIT store(pluginData);
100
101 /* We know that the given secret is a valid token, so it cannot
102 * be a valid password as well: let's clear it out now, so that
103 * if it turns out that the token is no longer valid and that
104 * we need to create a new one, we won't make a useless attempt
105 * to create one with a wrong password.
106 */
107 m_data.setSecret(QString());
108 }
109 delete token;
110 } else {
111 /* Always use the same token name for now */
112 m_data.setTokenName(Token::buildTokenName());
113 }
11478
115 /* Check if we have stored data for this token name */79 /* Check if we have stored data for this token name */
116 PluginData tokenData(storedData[m_data.TokenName()].toMap());80 PluginData tokenData(storedData[m_data.TokenName()].toMap());
117 Token token(tokenData.TokenKey(), tokenData.TokenSecret(),
118 tokenData.ConsumerKey(), tokenData.ConsumerSecret(),
119 tokenData.DateCreated(), tokenData.DateUpdated());
120 if (!token.isValid()) {
121 return false;
122 }
123 qDebug() << "Token is valid!" << tokenData.TokenKey();
124
125 tokenData.setTokenName(m_data.TokenName());81 tokenData.setTokenName(m_data.TokenName());
126 Q_EMIT result(tokenData);82 Q_EMIT result(tokenData);
127 return true;
128 }83 }
12984
130 void SignOnPlugin::emitErrorFromReply(QNetworkReply *reply)85 void SignOnPlugin::emitErrorFromReply(QNetworkReply *reply)
@@ -145,6 +100,13 @@
145 Q_EMIT error(SignOn::Error(type, reply->errorString()));100 Q_EMIT error(SignOn::Error(type, reply->errorString()));
146 }101 }
147102
103 void SignOnPlugin::clearToken() {
104 qDebug() << "Clearing stored token";
105 PluginData pluginData;
106 pluginData.setStoredData(QVariantMap());
107 Q_EMIT store(pluginData);
108 }
109
148 void SignOnPlugin::process(const SignOn::SessionData &inData,110 void SignOnPlugin::process(const SignOn::SessionData &inData,
149 const QString &mechanism)111 const QString &mechanism)
150 {112 {
@@ -158,15 +120,23 @@
158 PluginData response;120 PluginData response;
159 m_data = inData.data<PluginData>();121 m_data = inData.data<PluginData>();
160122
161 /* It may be that the stored token is valid; however, do the check only123 if (!m_data.UserName().isEmpty() && !m_data.Secret().isEmpty()) {
162 * if no OTP was provided (since the presence of an OTP is a clear124 createNewToken();
163 * signal that the caller wants to get a new token). */125 } else if (m_data.InvalidateToken()) {
164 if (m_data.OneTimePassword().isEmpty() &&126 clearToken();
165 respondWithStoredData()) {127 } else if (m_data.StoredData().isEmpty() && m_data.UserName().isEmpty() && !m_data.Secret().isEmpty()) {
166 return;128 /* If there's a secret stored in password field, clear it, and
129 * respond with the empty token result, as if it's invalid.
130 */
131 m_data.setSecret(QString());
132 clearToken();
133 respondWithStoredData();
134 } else if (!m_data.StoredData().isEmpty()) {
135 respondWithStoredData();
136 } else {
137 Q_EMIT error(SignOn::Error(SignOn::Error::InvalidQuery,
138 "invalid query parameters"));
167 }139 }
168
169 getCredentialsAndCreateNewToken();
170 }140 }
171141
172 void SignOnPlugin::onCreationFinished()142 void SignOnPlugin::onCreationFinished()
@@ -180,8 +150,6 @@
180 QJsonDocument json = QJsonDocument::fromJson(data);150 QJsonDocument json = QJsonDocument::fromJson(data);
181 QJsonObject object = json.object();151 QJsonObject object = json.object();
182152
183 QString error = object.value("code").toString();
184
185 int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();153 int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
186 qDebug() << "Status code:" << statusCode;154 qDebug() << "Status code:" << statusCode;
187 if (statusCode == 200 || statusCode == 201) {155 if (statusCode == 200 || statusCode == 201) {
@@ -203,27 +171,22 @@
203 Q_EMIT store(pluginData);171 Q_EMIT store(pluginData);
204172
205 Q_EMIT result(token);173 Q_EMIT result(token);
206 } else if (statusCode == 401 && error == ERR_INVALID_CREDENTIALS) {174 } else if (statusCode == 401 || statusCode == 403) {
207 m_data.setSecret(QString());175 QString error = object.value("code").toString();
208 m_data.setOneTimePassword(QString());176 QString message = object.value("message").toString();
209 getCredentialsAndCreateNewToken();177
210 } else if (statusCode == 401 && error == ERR_TWOFACTOR_REQUIRED) {178 PluginData errorData;
211 m_needsOtp = true;179 if (error == ERR_TWOFACTOR_REQUIRED) {
212 getCredentialsAndCreateNewToken();180 errorData.setU1ErrorCode(PluginData::OneTimePasswordRequired);
213 } else if (statusCode == 403 && error == ERR_TWOFACTOR_FAILURE) {181 } else {
214 m_data.setOneTimePassword(QString());182 errorData.setU1ErrorCode(PluginData::InvalidPassword);
215 getCredentialsAndCreateNewToken();183 }
216 } else if (statusCode == 403 && error == ERR_PASSWORD_POLICY_ERROR) {184 errorData.setU1ErrorMessage(message);
217 QVariantMap data;185
218 QJsonObject extra = object.value("extra").toObject();186 // Emit a result with error data since this seems to be the
219 data[SSOUI_KEY_OPENURL] = extra.value("location").toString();187 // only feasible way to get error messages from server back up
220 Q_EMIT userActionRequired(data);188 // to the UI.
221 } else if (error == ERR_INVALID_DATA) {189 Q_EMIT result(errorData);
222 // This error is received when the email address is invalid
223 m_data.setUserName(QString());
224 m_data.setSecret(QString());
225 m_data.setOneTimePassword(QString());
226 getCredentialsAndCreateNewToken();
227 } else {190 } else {
228 emitErrorFromReply(reply);191 emitErrorFromReply(reply);
229 }192 }
@@ -250,94 +213,5 @@
250 this, SLOT(onCreationFinished()));213 this, SLOT(onCreationFinished()));
251 }214 }
252215
253 void SignOnPlugin::getCredentialsAndCreateNewToken()
254 {
255 if (!m_data.Secret().isEmpty() &&
256 (!m_needsOtp || !m_data.OneTimePassword().isEmpty())) {
257 createNewToken();
258 } else if (m_data.Secret().isEmpty()) {
259 QVariantMap data;
260 data[SSOUI_KEY_TITLE] =
261 QString::fromUtf8(_("Sign in to your Ubuntu One account"));
262 if (m_data.UserName().isEmpty()) {
263 data[SSOUI_KEY_QUERYUSERNAME] = true;
264 }
265 data[SSOUI_KEY_USERNAME] = m_data.UserName();
266 data[SSOUI_KEY_QUERYPASSWORD] = true;
267 m_didAskForPassword = true;
268 Q_EMIT userActionRequired(data);
269 } else {
270 QVariantMap data;
271 data[SSOUI_KEY_TITLE] =
272 QString::fromUtf8(_("Sign in to your Ubuntu One account"));
273 data[SSOUI_KEY_USERNAME] = m_data.UserName();
274 data[SSOUI_KEY_PASSWORD] = m_data.Secret();
275 data[SSOUI_KEY_QUERY2FA] = true;
276 data[SSOUI_KEY_2FA_TEXT] =
277 QString::fromUtf8(_("2-factor device code"));
278 Q_EMIT userActionRequired(data);
279 }
280 }
281
282 bool SignOnPlugin::handleUiError(const SignOn::UiSessionData &data)
283 {
284 using namespace SignOn;
285
286 int code = data.QueryErrorCode();
287 if (code == QUERY_ERROR_NONE) {
288 return false;
289 }
290
291 qDebug() << "userActionFinished with error: " << code;
292 if (code == QUERY_ERROR_CANCELED) {
293 Q_EMIT error(Error(Error::SessionCanceled,
294 QLatin1String("Cancelled by user")));
295 } else if (code == QUERY_ERROR_NETWORK) {
296 Q_EMIT error(Error(Error::Network, QLatin1String("Network error")));
297 } else if (code == QUERY_ERROR_SSL) {
298 Q_EMIT error(Error(Error::Ssl, QLatin1String("SSL error")));
299 } else {
300 QVariantMap map = data.toMap();
301 if (map.contains(SSOUI_KEY_QUERY2FA)) {
302 PluginData reply;
303 reply.setU1ErrorCode(PluginData::OneTimePasswordRequired);
304 Q_EMIT result(reply);
305 } else if (map.contains(SSOUI_KEY_QUERYPASSWORD)) {
306 PluginData reply;
307 reply.setU1ErrorCode(PluginData::InvalidPassword);
308 Q_EMIT result(reply);
309 } else {
310 Q_EMIT error(Error(Error::UserInteraction,
311 QString("userActionFinished error: ")
312 + QString::number(data.QueryErrorCode())));
313 }
314 }
315 return true;
316 }
317
318 void SignOnPlugin::userActionFinished(const SignOn::UiSessionData &data)
319 {
320 if (handleUiError(data)) {
321 return;
322 }
323
324 PluginData uiData = data.data<PluginData>();
325 if (!uiData.UserName().isEmpty()) {
326 m_data.setUserName(uiData.UserName());
327 }
328
329 if (!uiData.Secret().isEmpty()) {
330 m_data.setSecret(uiData.Secret());
331 }
332
333 QVariantMap map = data.toMap();
334 QString oneTimePassword = map.value(SSOUI_KEY_2FA).toString();
335 if (!oneTimePassword.isEmpty()) {
336 m_data.setOneTimePassword(oneTimePassword);
337 }
338
339 getCredentialsAndCreateNewToken();
340 }
341
342 SIGNON_DECL_AUTH_PLUGIN(SignOnPlugin)216 SIGNON_DECL_AUTH_PLUGIN(SignOnPlugin)
343} // namespace UbuntuOne217} // namespace UbuntuOne
344218
=== modified file 'signon-plugin/ubuntuone-plugin.h'
--- signon-plugin/ubuntuone-plugin.h 2016-05-24 20:57:27 +0000
+++ signon-plugin/ubuntuone-plugin.h 2016-06-21 17:27:01 +0000
@@ -51,14 +51,12 @@
51 void cancel() Q_DECL_OVERRIDE;51 void cancel() Q_DECL_OVERRIDE;
52 void process(const SignOn::SessionData &inData,52 void process(const SignOn::SessionData &inData,
53 const QString &mechanism = 0) Q_DECL_OVERRIDE;53 const QString &mechanism = 0) Q_DECL_OVERRIDE;
54 void userActionFinished(const SignOn::UiSessionData &data) Q_DECL_OVERRIDE;
5554
56 private:55 private:
57 bool respondWithStoredData();56 void respondWithStoredData();
58 void emitErrorFromReply(QNetworkReply *reply);57 void emitErrorFromReply(QNetworkReply *reply);
58 void clearToken();
59 void createNewToken();59 void createNewToken();
60 void getCredentialsAndCreateNewToken();
61 bool handleUiError(const SignOn::UiSessionData &data);
6260
63 private Q_SLOTS:61 private Q_SLOTS:
64 void onCreationFinished();62 void onCreationFinished();
6563
=== modified file 'signon-plugin/ubuntuonedata.h'
--- signon-plugin/ubuntuonedata.h 2016-04-27 08:36:19 +0000
+++ signon-plugin/ubuntuonedata.h 2016-06-21 17:27:01 +0000
@@ -46,6 +46,10 @@
46 SIGNON_SESSION_DECLARE_PROPERTY(QString, DateCreated);46 SIGNON_SESSION_DECLARE_PROPERTY(QString, DateCreated);
47 SIGNON_SESSION_DECLARE_PROPERTY(QString, DateUpdated);47 SIGNON_SESSION_DECLARE_PROPERTY(QString, DateUpdated);
4848
49 // Set this to true if the token returned by the previous
50 // authentication is invalid.
51 SIGNON_SESSION_DECLARE_PROPERTY(bool, InvalidateToken);
52
49 // Error code53 // Error code
50 enum ErrorCode {54 enum ErrorCode {
51 NoError = 0,55 NoError = 0,
@@ -53,6 +57,7 @@
53 InvalidPassword,57 InvalidPassword,
54 };58 };
55 SIGNON_SESSION_DECLARE_PROPERTY(int, U1ErrorCode);59 SIGNON_SESSION_DECLARE_PROPERTY(int, U1ErrorCode);
60 SIGNON_SESSION_DECLARE_PROPERTY(QString, U1ErrorMessage);
5661
57 // Data which the plugin has stored into signond62 // Data which the plugin has stored into signond
58 SIGNON_SESSION_DECLARE_PROPERTY(QVariantMap, StoredData);63 SIGNON_SESSION_DECLARE_PROPERTY(QVariantMap, StoredData);

Subscribers

People subscribed via source and target branches

to all changes: