Merge lp:~mterry/unity8/locking-hash into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Michał Sawicz
Approved revision: 999
Merged at revision: 1090
Proposed branch: lp:~mterry/unity8/locking-hash
Merge into: lp:unity8
Prerequisite: lp:~mterry/unity8/dialer-above
Diff against target: 845 lines (+338/-75)
20 files modified
debian/control (+1/-0)
plugins/AccountsService/AccountsService.cpp (+21/-1)
plugins/AccountsService/AccountsService.h (+13/-0)
plugins/AccountsService/plugin.cpp (+1/-0)
plugins/LightDM/plugin.cpp (+2/-0)
qml/Components/PassphraseLockscreen.qml (+3/-0)
qml/Shell.qml (+27/-21)
run.sh (+15/-6)
tests/mocks/AccountsService/AccountsService.cpp (+12/-2)
tests/mocks/AccountsService/AccountsService.h (+12/-0)
tests/mocks/AccountsService/CMakeLists.txt (+4/-1)
tests/mocks/AccountsService/plugin.cpp (+1/-0)
tests/mocks/LightDM/GreeterPrivate.h (+2/-0)
tests/mocks/LightDM/demo/CMakeLists.txt (+4/-2)
tests/mocks/LightDM/demo/GreeterPrivate.cpp (+205/-33)
tests/mocks/LightDM/demo/UsersModelPrivate.cpp (+1/-1)
tests/mocks/LightDM/full/GreeterPrivate.cpp (+1/-1)
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp (+1/-1)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp (+1/-1)
tests/qmltests/Greeter/tst_Lockscreen.qml (+11/-5)
To merge this branch: bzr merge lp:~mterry/unity8/locking-hash
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Albert Astals Cid (community) Abstain
Michał Sawicz Approve
Seth Arnold (community) Approve
Marc Deslauriers Pending
Review via email: mp+225538@code.launchpad.net

Commit message

Check user's pin/password using PAM, instead of a plaintext keyfile.

New build dependency: libpam0g-dev for phone unlock with PAM

Description of the change

Check user's pin/password using PAM, instead of a plaintext keyfile.

== Checklist ==

 * Are there any related MPs required for this MP to build/function as expected? Please list.
 - lp:~mterry/gsettings-ubuntu-touch-schemas/password-hint
 - https://code.launchpad.net/~mterry/ubuntu-system-settings/locking-hash/+merge/224346 (wip)
 - https://code.launchpad.net/~mterry/livecd-rootfs/no-password/+merge/225560

 * Did you perform an exploratory manual test run of your code change and any related functionality?
 - Yes

 * Did you make sure that your branch does not contain spurious tags?
 - Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
 - I am in that team

 * If you changed the UI, has there been a design review?
 - NA

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

Marc, this branch implements PAM password-checking. Most of the interesting-to-you changes are probably in tests/mocks/LightDM/demo/GreeterPrivate.cpp.

Revision history for this message
Stéphane Graber (stgraber) wrote :

Why the added build-dep and dep on whois? I fail to see how querying an RFC 3912 online database is even remotely related to unity8 :)

Revision history for this message
Michael Terry (mterry) wrote :

> Why the added build-dep and dep on whois?
> I fail to see how querying an RFC 3912 online
> database is even remotely related to unity8 :)

Oh whoops, I forgot to push to the branch again. That's gone now. It was there from the previous incarnation of this branch, which put shadow-style entries into a keyfile in the user's HOME.

Oddly enough, whois contains the mkpasswd executable.

Revision history for this message
Seth Arnold (seth-arnold) wrote :

This looks great. I think I found one minor bug, have some large questions about the goals and implementation, and then some further clarifying questions.

First, the minor bug:

- pamStatus isn't updated after pam_setcreds()

Next, I noticed that there's no use of PAM sessions, initgroups(), or set*id() functions; this might be consistent with single-user phones, but it would probably need extending to support multiple users. Are we aiming for multiple user support?

What are we missing by not supporting pam_open_session()? (ecryptfs and auditing at least; what else?)

Would we need to fork() a new process for the session, groups, and user change, so that the parent could still have sufficient privileges to call pam_close_session() at the end?

The PAM application developer guide mentions the PAM_USER, PAM_RUSER, and PAM_RHOST variables; I don't know how important these are for our uses but we might want to set them all the same so we provide the widest amount of compatibility with existing PAM modules.

I think we can merge this after the minor pamStatus = pam_setcreds() fixup regardless of the answers to my questions -- they'll either be bug fixes or new features that we can handle in good time.

Thanks

review: Needs Fixing
Revision history for this message
Michael Terry (mterry) wrote :

The pam flow, including not changing pamStatus after pam_setcred and not using session is all stolen from ./lockscreen/UserAuthenticatorPam.cpp in lp:unity.

Specifically about pam_setcred, even sudo ignores the return (from ./plugins/sudoers/auth/pam.c):

     * We don't worry about a failure from pam_setcred() since with
     * stacked PAM auth modules a failure from one module may override
     * PAM_SUCCESS from another. For example, given a non-local user,
     * pam_unix will fail but pam_ldap or pam_sss may succeed, but if
     * pam_unix is first in the stack, pam_setcred() will fail.

> Are we aiming for multiple user support?

Not on this screen, no. This is the equivalent of the unity7 lock screen. (When the user sees this, we've already logged them in and presented the "greeter" as a lock screen.) So no worries about ecryptfs etc either. That isn't in scope unless we have a proper split greeter, for which this branch is an interim alternative.

Revision history for this message
Seth Arnold (seth-arnold) wrote :

Michael, thanks for the detailed answers. Very nice.

Thanks!

Revision history for this message
Michał Sawicz (saviq) wrote :

In general I'm in favour of bracketing single-line if/else()s, but I won't hold it against you.

Is there any chance of stuff getting out of sync (like response coming to a different future than expected)? Should we pass around an id of the message and expect it back from the prompt?

Is there any chance of futureWatcher.result() never returning, or does pam_end() in start() ensure it will get reset every time you go start()?

I tried to run it with ./run.sh, got the password prompt, but couldn't authenticated, that expected?

Also see inline.

Really like the approach!

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

> In general I'm in favour of bracketing single-line if/else()s, but I won't hold it against you.

I added a few more brackets.

> Is there any chance of stuff getting out of sync (like response coming to a different future than expected)? Should we pass around an id of the message and expect it back from the prompt?

Shouldn't be. That's up the to the UI side of things to make sure to give back a response for each prompt in order. Order is important with PAM prompts, so we don't expect a UI component to present them out of order.

> Is there any chance of futureWatcher.result() never returning, or does pam_end() in start() ensure it will get reset every time you go start()?

No, futureWatcher.result() should always return, because it is only called after we get the "finished()" signal from it. And the thread should always finish due to pam_end, yes.

> I tried to run it with ./run.sh, got the password prompt, but couldn't authenticated, that expected?

Fixed. I'm so used to testing on the device, that I didn't notice we still hardcode the phablet user in one place.

> Also see inline.

Replies inline. Except all of a sudden LP wouldn't let me edit or make new comments. So here's a little bit extra:

Regarding whether to call it a mock, I will say that it *is* a mock for liblightdm-qt5. It's just a functional one.

And as for qRegisterMetaType, it only seems to work with that method. Note this section of the documenation for Q_DECLARE_METATYPE:

"Note that if you intend to use the type in queued signal and slot connections or in QObject's property system, you also have to call qRegisterMetaType() since the names are resolved at runtime."

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

> Replies inline. Except all of a sudden LP wouldn't let me edit or make new
> comments. So here's a little bit extra:

Sounds like you didn't press "save" on one of them, it will then prevent you from doing changes to any other.

> And as for qRegisterMetaType, it only seems to work with that method. Note
> this section of the documenation for Q_DECLARE_METATYPE:
>
> "Note that if you intend to use the type in queued signal and slot connections
> or in QObject's property system, you also have to call qRegisterMetaType()
> since the names are resolved at runtime."

Yeah, the thing is they should get registered as you register the singletons, off which you then take the enum values... Basically what I'm saying we barely have this anywhere else, and using singletons and enums everywhere.

Hmm ah-ha! plugins/LightDM/Greeter.h does not have Q_ENUMS for those. Is that by design?

=====

Can we do anything about the fact that now while developing this will generally require a password?

Should we in ./run.sh and ./run_on_device preload a passwordless LightDM mock?

=====

See inline.

review: Needs Information
Revision history for this message
Michael Terry (mterry) wrote :

> Should we in ./run.sh and ./run_on_device preload a passwordless LightDM mock?

Isn't that what -f is for?

What might be nice is once I have re-split the greeter again, by default ./run.sh can give you just the dash. But passing -G or some such will give you greeter too.

> Basically what I'm saying we barely have this anywhere else, and using singletons and enums everywhere.

OK, will give this another look.

Revision history for this message
Michael Terry (mterry) wrote :

> Hmm ah-ha! plugins/LightDM/Greeter.h does not have Q_ENUMS for those. Is that by design?

The Q_ENUMS definition for those is in tests/mocks/LightDM/Greeter.h. I tried also doing it in plugins/LightDM/Greeter.h, in case QML couldn't see them in the other place, no luck. The error I get without doing qRegisterMetaType is:

"QObject::connect: Cannot queue arguments of type 'QLightDM::Greeter::PromptType'
(Make sure 'QLightDM::Greeter::PromptType' is registered using qRegisterMetaType().)"

Which seems to coincide with the documentation comments about queued connection arguments.

Probably the reason it is necessary here is that we never actually call qmlRegisterSingletonType for the class that contains these enums. That class is not exposed to Qml, but we still use its enums.

Revision history for this message
Michael Terry (mterry) wrote :

OK, made it so that even just plain ./run.sh will skip requiring a password.

Revision history for this message
Seth Arnold (seth-arnold) wrote :

Sorry, I forgot to mark this "Approve" after an earlier review and feedback from Michael.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes.

 * Did CI run pass? If not, please explain why.
Jenkins is unhappy today.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in debian/control
1 conflicts encountered.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/locking-hash updated
998. By Michael Terry

Merge from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/locking-hash updated
999. By Michael Terry

Merge from trunk

Revision history for this message
Albert Astals Cid (aacid) wrote :

Merges cleanly now

review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-07-24 20:40:44 +0000
3+++ debian/control 2014-07-25 13:39:35 +0000
4@@ -19,6 +19,7 @@
5 libgsettings-qt-dev,
6 libhardware-dev,
7 libhud-client2-dev,
8+ libpam0g-dev,
9 libpay2-dev,
10 libpulse-dev,
11 libqmenumodel-dev (>= 0.2.8),
12
13=== modified file 'plugins/AccountsService/AccountsService.cpp'
14--- plugins/AccountsService/AccountsService.cpp 2014-06-11 15:36:51 +0000
15+++ plugins/AccountsService/AccountsService.cpp 2014-07-25 13:39:35 +0000
16@@ -26,7 +26,8 @@
17 m_service(new AccountsServiceDBusAdaptor(this)),
18 m_user(qgetenv("USER")),
19 m_demoEdges(false),
20- m_statsWelcomeScreen(false)
21+ m_statsWelcomeScreen(false),
22+ m_passwordDisplayHint(Keyboard)
23 {
24 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
25 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
26@@ -47,6 +48,7 @@
27 updateDemoEdges();
28 updateBackgroundFile();
29 updateStatsWelcomeScreen();
30+ updatePasswordDisplayHint();
31 }
32
33 bool AccountsService::demoEdges() const
34@@ -70,6 +72,11 @@
35 return m_statsWelcomeScreen;
36 }
37
38+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
39+{
40+ return m_passwordDisplayHint;
41+}
42+
43 void AccountsService::updateDemoEdges()
44 {
45 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
46@@ -97,6 +104,15 @@
47 }
48 }
49
50+void AccountsService::updatePasswordDisplayHint()
51+{
52+ PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)m_service->getUserProperty(m_user, "com.ubuntu.AccountsService.SecurityPrivacy", "PasswordDisplayHint").toInt();
53+ if (m_passwordDisplayHint != passwordDisplayHint) {
54+ m_passwordDisplayHint = passwordDisplayHint;
55+ Q_EMIT passwordDisplayHintChanged();
56+ }
57+}
58+
59 void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
60 {
61 if (m_user != user) {
62@@ -111,6 +127,10 @@
63 if (changed.contains("StatsWelcomeScreen")) {
64 updateStatsWelcomeScreen();
65 }
66+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
67+ if (changed.contains("PasswordDisplayHint")) {
68+ updatePasswordDisplayHint();
69+ }
70 }
71 }
72
73
74=== modified file 'plugins/AccountsService/AccountsService.h'
75--- plugins/AccountsService/AccountsService.h 2014-06-11 15:36:51 +0000
76+++ plugins/AccountsService/AccountsService.h 2014-07-25 13:39:35 +0000
77@@ -27,6 +27,7 @@
78 class AccountsService: public QObject
79 {
80 Q_OBJECT
81+ Q_ENUMS(PasswordDisplayHint)
82 Q_PROPERTY (QString user
83 READ user
84 WRITE setUser
85@@ -41,8 +42,16 @@
86 Q_PROPERTY (bool statsWelcomeScreen
87 READ statsWelcomeScreen
88 NOTIFY statsWelcomeScreenChanged)
89+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
90+ READ passwordDisplayHint
91+ NOTIFY passwordDisplayHintChanged)
92
93 public:
94+ enum PasswordDisplayHint {
95+ Keyboard,
96+ Numeric,
97+ };
98+
99 explicit AccountsService(QObject *parent = 0);
100
101 QString user() const;
102@@ -51,12 +60,14 @@
103 void setDemoEdges(bool demoEdges);
104 QString backgroundFile() const;
105 bool statsWelcomeScreen() const;
106+ PasswordDisplayHint passwordDisplayHint() const;
107
108 Q_SIGNALS:
109 void userChanged();
110 void demoEdgesChanged();
111 void backgroundFileChanged();
112 void statsWelcomeScreenChanged();
113+ void passwordDisplayHintChanged();
114
115 private Q_SLOTS:
116 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
117@@ -66,12 +77,14 @@
118 void updateDemoEdges();
119 void updateBackgroundFile();
120 void updateStatsWelcomeScreen();
121+ void updatePasswordDisplayHint();
122
123 AccountsServiceDBusAdaptor *m_service;
124 QString m_user;
125 bool m_demoEdges;
126 QString m_backgroundFile;
127 bool m_statsWelcomeScreen;
128+ PasswordDisplayHint m_passwordDisplayHint;
129 };
130
131 #endif
132
133=== modified file 'plugins/AccountsService/plugin.cpp'
134--- plugins/AccountsService/plugin.cpp 2013-11-19 17:01:25 +0000
135+++ plugins/AccountsService/plugin.cpp 2014-07-25 13:39:35 +0000
136@@ -33,5 +33,6 @@
137 {
138 Q_ASSERT(uri == QLatin1String("AccountsService"));
139 qDBusRegisterMetaType<QList<QVariantMap>>();
140+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
141 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
142 }
143
144=== modified file 'plugins/LightDM/plugin.cpp'
145--- plugins/LightDM/plugin.cpp 2014-06-18 17:26:30 +0000
146+++ plugins/LightDM/plugin.cpp 2014-07-25 13:39:35 +0000
147@@ -69,6 +69,8 @@
148 qmlRegisterType<UserMetricsOutput::ColorTheme>();
149
150 Q_ASSERT(uri == QLatin1String("LightDM"));
151+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
152+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
153 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
154 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
155 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
156
157=== modified file 'qml/Components/PassphraseLockscreen.qml'
158--- qml/Components/PassphraseLockscreen.qml 2014-05-28 07:17:08 +0000
159+++ qml/Components/PassphraseLockscreen.qml 2014-07-25 13:39:35 +0000
160@@ -31,8 +31,10 @@
161
162 function clear(playAnimation) {
163 pinentryField.text = "";
164+ pinentryField.enabled = true
165 if (playAnimation) {
166 wrongPasswordAnimation.start();
167+ pinentryField.forceActiveFocus();
168 } else {
169 pinentryField.focus = false
170 }
171@@ -78,6 +80,7 @@
172
173 onAccepted: {
174 if (pinentryField.text) {
175+ pinentryField.enabled = false;
176 root.entered(pinentryField.text);
177 }
178 }
179
180=== modified file 'qml/Shell.qml'
181--- qml/Shell.qml 2014-07-18 15:13:35 +0000
182+++ qml/Shell.qml 2014-07-25 13:39:35 +0000
183@@ -490,6 +490,7 @@
184 width: parent.width
185 height: parent.height - panel.panelHeight
186 background: shell.background
187+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
188 minPinLength: 4
189 maxPinLength: 4
190
191@@ -504,7 +505,7 @@
192 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
193
194 Component.onCompleted: {
195- if (LightDM.Users.count == 1) {
196+ if (greeter.narrowMode) {
197 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
198 }
199 }
200@@ -516,14 +517,17 @@
201 onShowGreeter: greeter.show()
202
203 onShowPrompt: {
204- if (LightDM.Users.count == 1) {
205- // TODO: There's no better way for now to determine if its a PIN or a passphrase.
206- if (text == "PIN") {
207- lockscreen.alphaNumeric = false
208- } else {
209- lockscreen.alphaNumeric = true
210- }
211- lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
212+ if (greeter.narrowMode) {
213+ lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
214+ lockscreen.show();
215+ }
216+ }
217+
218+ onPromptlessChanged: {
219+ if (LightDM.Greeter.promptless) {
220+ lockscreen.hide()
221+ } else {
222+ lockscreen.reset();
223 lockscreen.show();
224 }
225 }
226@@ -537,6 +541,9 @@
227 greeter.login();
228 } else {
229 lockscreen.clear(true);
230+ if (greeter.narrowMode) {
231+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
232+ }
233 }
234 }
235 }
236@@ -590,24 +597,23 @@
237
238 function login() {
239 enabled = false;
240- LightDM.Greeter.startSessionSync();
241- sessionStarted();
242- greeter.hide();
243- lockscreen.hide();
244- launcher.hide();
245+ if (LightDM.Greeter.startSessionSync()) {
246+ sessionStarted();
247+ greeter.hide();
248+ lockscreen.hide();
249+ launcher.hide();
250+ }
251 enabled = true;
252 }
253
254 onShownChanged: {
255 if (shown) {
256- lockscreen.reset();
257+ if (greeter.narrowMode) {
258+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
259+ }
260 if (!LightDM.Greeter.promptless) {
261- lockscreen.show();
262- }
263- // If there is only one user, we start authenticating with that one here.
264- // If there are more users, the Greeter will handle that
265- if (LightDM.Users.count == 1) {
266- LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
267+ lockscreen.reset();
268+ lockscreen.show();
269 }
270 greeter.fakeActiveForApp = "";
271 greeter.forceActiveFocus();
272
273=== modified file 'run.sh'
274--- run.sh 2014-06-24 17:04:11 +0000
275+++ run.sh 2014-07-25 13:39:35 +0000
276@@ -8,6 +8,7 @@
277 FAKE=false
278 PINLOCK=false
279 KEYLOCK=false
280+USE_MOCKS=false
281 MOUSE_TOUCH=true
282
283 usage() {
284@@ -31,9 +32,9 @@
285 while [ $# -gt 0 ]
286 do
287 case "$1" in
288- -f|--fake) FAKE=true;;
289- -p|--pinlock) PINLOCK=true;;
290- -k|--keylock) KEYLOCK=true;;
291+ -f|--fake) FAKE=true; USE_MOCKS=true;;
292+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
293+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
294 -g|--gdb) GDB=true;;
295 -h|--help) usage;;
296 -m|--nomousetouch) MOUSE_TOUCH=false;;
297@@ -43,20 +44,28 @@
298 done
299
300 if $FAKE; then
301- export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
302 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
303 fi
304
305 if $PINLOCK; then
306- export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
307 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
308 fi
309
310 if $KEYLOCK; then
311- export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
312 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
313 fi
314
315+if $USE_MOCKS; then
316+ rm -f $PWD/builddir/nonmirplugins/LightDM # undo symlink (from below) for cleanliness
317+ export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
318+else
319+ # Still fake no-login user for convenience (it's annoying to be prompted for your password when testing)
320+ # And in particular, just link our LightDM mock into the nonmirplugins folder. We don't want the rest of
321+ # our plugins to be used.
322+ ln -s $PWD/builddir/tests/mocks/LightDM $PWD/builddir/nonmirplugins/
323+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
324+fi
325+
326 QML_PHONE_SHELL_ARGS=""
327 if $MOUSE_TOUCH; then
328 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
329
330=== modified file 'tests/mocks/AccountsService/AccountsService.cpp'
331--- tests/mocks/AccountsService/AccountsService.cpp 2014-06-17 18:23:44 +0000
332+++ tests/mocks/AccountsService/AccountsService.cpp 2014-07-25 13:39:35 +0000
333@@ -29,12 +29,14 @@
334
335 QString AccountsService::user() const
336 {
337- return "testuser";
338+ return m_user;
339 }
340
341 void AccountsService::setUser(const QString &user)
342 {
343- Q_UNUSED(user)
344+ m_user = user;
345+ Q_EMIT userChanged();
346+ Q_EMIT passwordDisplayHintChanged();
347 }
348
349 bool AccountsService::demoEdges() const
350@@ -68,3 +70,11 @@
351 m_statsWelcomeScreen = statsWelcomeScreen;
352 statsWelcomeScreenChanged();
353 }
354+
355+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
356+{
357+ if (m_user == "has-pin")
358+ return PasswordDisplayHint::Numeric;
359+ else
360+ return PasswordDisplayHint::Keyboard;
361+}
362
363=== modified file 'tests/mocks/AccountsService/AccountsService.h'
364--- tests/mocks/AccountsService/AccountsService.h 2014-06-11 15:36:51 +0000
365+++ tests/mocks/AccountsService/AccountsService.h 2014-07-25 13:39:35 +0000
366@@ -27,6 +27,7 @@
367 class AccountsService: public QObject
368 {
369 Q_OBJECT
370+ Q_ENUMS(PasswordDisplayHint)
371 Q_PROPERTY (QString user
372 READ user
373 WRITE setUser
374@@ -43,8 +44,16 @@
375 READ statsWelcomeScreen
376 WRITE setStatsWelcomeScreen // only available in mock
377 NOTIFY statsWelcomeScreenChanged)
378+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
379+ READ passwordDisplayHint
380+ NOTIFY passwordDisplayHintChanged)
381
382 public:
383+ enum PasswordDisplayHint {
384+ Keyboard,
385+ Numeric,
386+ };
387+
388 explicit AccountsService(QObject *parent = 0);
389
390 QString user() const;
391@@ -55,15 +64,18 @@
392 void setBackgroundFile(const QString &backgroundFile);
393 bool statsWelcomeScreen() const;
394 void setStatsWelcomeScreen(bool statsWelcomeScreen);
395+ PasswordDisplayHint passwordDisplayHint() const;
396
397 Q_SIGNALS:
398 void userChanged();
399 void demoEdgesChanged();
400 void backgroundFileChanged();
401 void statsWelcomeScreenChanged();
402+ void passwordDisplayHintChanged();
403
404 private:
405 QString m_backgroundFile;
406+ QString m_user;
407 bool m_statsWelcomeScreen;
408 };
409
410
411=== modified file 'tests/mocks/AccountsService/CMakeLists.txt'
412--- tests/mocks/AccountsService/CMakeLists.txt 2014-06-17 18:23:44 +0000
413+++ tests/mocks/AccountsService/CMakeLists.txt 2014-07-25 13:39:35 +0000
414@@ -5,4 +5,7 @@
415
416 qt5_use_modules(MockAccountsService-qml DBus Qml)
417
418-add_unity8_mock(AccountsService 0.1 AccountsService TARGETS MockAccountsService-qml)
419+add_unity8_mock(AccountsService 0.1 AccountsService
420+ PREFIX mocks
421+ TARGETS MockAccountsService-qml
422+ )
423
424=== modified file 'tests/mocks/AccountsService/plugin.cpp'
425--- tests/mocks/AccountsService/plugin.cpp 2013-11-19 17:01:25 +0000
426+++ tests/mocks/AccountsService/plugin.cpp 2014-07-25 13:39:35 +0000
427@@ -32,5 +32,6 @@
428 void AccountsServicePlugin::registerTypes(const char *uri)
429 {
430 Q_ASSERT(uri == QLatin1String("AccountsService"));
431+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
432 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
433 }
434
435=== modified file 'tests/mocks/LightDM/GreeterPrivate.h'
436--- tests/mocks/LightDM/GreeterPrivate.h 2013-06-05 22:03:08 +0000
437+++ tests/mocks/LightDM/GreeterPrivate.h 2014-07-25 13:39:35 +0000
438@@ -24,6 +24,7 @@
439 namespace QLightDM
440 {
441 class Greeter;
442+class GreeterImpl;
443
444 class GreeterPrivate
445 {
446@@ -40,6 +41,7 @@
447 void handleRespond(const QString &response);
448
449 protected:
450+ GreeterImpl *m_impl; // if the backend needs more private data
451 Greeter * const q_ptr;
452
453 private:
454
455=== modified file 'tests/mocks/LightDM/demo/CMakeLists.txt'
456--- tests/mocks/LightDM/demo/CMakeLists.txt 2014-06-11 15:36:51 +0000
457+++ tests/mocks/LightDM/demo/CMakeLists.txt 2014-07-25 13:39:35 +0000
458@@ -11,11 +11,13 @@
459 add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
460
461 include_directories(
462+ ${CMAKE_CURRENT_BINARY_DIR}
463 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
464 )
465
466 target_link_libraries(MockLightDM-demo
467 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
468- )
469+ -lpam
470+)
471
472-qt5_use_modules(MockLightDM-demo Gui)
473+qt5_use_modules(MockLightDM-demo Concurrent Gui)
474
475=== modified file 'tests/mocks/LightDM/demo/GreeterPrivate.cpp'
476--- tests/mocks/LightDM/demo/GreeterPrivate.cpp 2014-06-11 15:36:51 +0000
477+++ tests/mocks/LightDM/demo/GreeterPrivate.cpp 2014-07-25 13:39:35 +0000
478@@ -18,53 +18,225 @@
479
480 #include "../Greeter.h"
481 #include "../GreeterPrivate.h"
482-#include <QtCore/QDir>
483-#include <QtCore/QSettings>
484+#include <QFuture>
485+#include <QFutureInterface>
486+#include <QFutureWatcher>
487+#include <QQueue>
488+#include <QtConcurrent>
489+#include <QVector>
490+#include <security/pam_appl.h>
491
492 namespace QLightDM
493 {
494
495+class GreeterImpl : public QObject
496+{
497+ Q_OBJECT
498+
499+ typedef QFutureInterface<QString> ResponseFuture;
500+
501+public:
502+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
503+ : QObject(parent),
504+ greeter(parent),
505+ greeterPrivate(greeterPrivate),
506+ pamHandle(NULL)
507+ {
508+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
509+
510+ connect(&futureWatcher, SIGNAL(finished()),
511+ this, SLOT(finishPam()));
512+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
513+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
514+ // This next connect is how we pass ResponseFutures between threads
515+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
516+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
517+ }
518+
519+ void start(QString username)
520+ {
521+ // Clear out any existing PAM interactions first (we can't simply
522+ // cancel our QFuture because QtConcurrent::run doesn't support cancel)
523+ if (pamHandle != NULL) {
524+ pam_handle *handle = pamHandle;
525+ pamHandle = NULL; // to disable normal finishPam() handling
526+ while (respond(QString())); // clear our local queue of QFutures
527+ pam_end(handle, PAM_CONV_ERR);
528+ }
529+
530+ // Now actually start a new conversation with PAM
531+ pam_conv conversation;
532+ conversation.conv = converseWithPam;
533+ conversation.appdata_ptr = static_cast<void*>(this);
534+
535+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
536+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
537+ } else {
538+ greeterPrivate->authenticated = false;
539+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
540+ Q_EMIT greeter->authenticationComplete();
541+ }
542+ }
543+
544+ static int authenticateWithPam(pam_handle* pamHandle)
545+ {
546+ int pamStatus = pam_authenticate(pamHandle, 0);
547+ if (pamStatus == PAM_SUCCESS) {
548+ pamStatus = pam_acct_mgmt(pamHandle, 0);
549+ }
550+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
551+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
552+ }
553+ if (pamStatus == PAM_SUCCESS) {
554+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
555+ }
556+ return pamStatus;
557+ }
558+
559+ static int converseWithPam(int num_msg, const pam_message** msg,
560+ pam_response** resp, void* appdata_ptr)
561+ {
562+ if (num_msg <= 0)
563+ return PAM_CONV_ERR;
564+
565+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
566+ if (!tmp_response)
567+ return PAM_CONV_ERR;
568+
569+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
570+
571+ int count;
572+ QVector<ResponseFuture> responses;
573+
574+ for (count = 0; count < num_msg; ++count)
575+ {
576+ switch (msg[count]->msg_style)
577+ {
578+ case PAM_PROMPT_ECHO_ON:
579+ {
580+ QString message(msg[count]->msg);
581+ responses.append(ResponseFuture());
582+ responses.last().reportStarted();
583+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
584+ break;
585+ }
586+ case PAM_PROMPT_ECHO_OFF:
587+ {
588+ QString message(msg[count]->msg);
589+ responses.append(ResponseFuture());
590+ responses.last().reportStarted();
591+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
592+ break;
593+ }
594+ case PAM_TEXT_INFO:
595+ {
596+ QString message(msg[count]->msg);
597+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
598+ break;
599+ }
600+ default:
601+ {
602+ QString message(msg[count]->msg);
603+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
604+ break;
605+ }
606+ }
607+ }
608+
609+ int i = 0;
610+ bool raise_error = false;
611+
612+ for (auto &response : responses)
613+ {
614+ pam_response* resp_item = &tmp_response[i++];
615+ resp_item->resp_retcode = 0;
616+ resp_item->resp = strdup(response.future().result().toUtf8());
617+
618+ if (!resp_item->resp)
619+ {
620+ raise_error = true;
621+ break;
622+ }
623+ }
624+
625+ if (raise_error)
626+ {
627+ for (int i = 0; i < count; ++i)
628+ free(tmp_response[i].resp);
629+
630+ free(tmp_response);
631+ return PAM_CONV_ERR;
632+ }
633+ else
634+ {
635+ *resp = tmp_response;
636+ return PAM_SUCCESS;
637+ }
638+ }
639+
640+public Q_SLOTS:
641+ bool respond(QString response)
642+ {
643+ if (!futures.isEmpty()) {
644+ futures.dequeue().reportFinished(&response);
645+ return true;
646+ } else {
647+ return false;
648+ }
649+ }
650+
651+Q_SIGNALS:
652+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
653+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
654+
655+private Q_SLOTS:
656+ void finishPam()
657+ {
658+ if (pamHandle == NULL) {
659+ return;
660+ }
661+
662+ int pamStatus = futureWatcher.result();
663+
664+ pam_end(pamHandle, pamStatus);
665+ pamHandle = NULL;
666+
667+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
668+ Q_EMIT greeter->authenticationComplete();
669+ }
670+
671+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
672+ {
673+ futures.enqueue(future);
674+ Q_EMIT greeter->showPrompt(text, type);
675+ }
676+
677+private:
678+ Greeter *greeter;
679+ GreeterPrivate *greeterPrivate;
680+ pam_handle* pamHandle;
681+ QFutureWatcher<int> futureWatcher;
682+ QQueue<ResponseFuture> futures;
683+};
684+
685 GreeterPrivate::GreeterPrivate(Greeter* parent)
686 : authenticated(false),
687 authenticationUser(),
688+ m_impl(new GreeterImpl(parent, this)),
689 q_ptr(parent)
690 {
691 }
692
693 void GreeterPrivate::handleAuthenticate()
694 {
695- Q_Q(Greeter);
696-
697- QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
698- settings.beginGroup(authenticationUser);
699- QVariant password = settings.value("password", "none");
700-
701- if (password == "pin") {
702- Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
703- } else if (password == "keyboard") {
704- Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
705- } else {
706- authenticated = true;
707- Q_EMIT q->authenticationComplete();
708- }
709+ m_impl->start(authenticationUser);
710 }
711
712 void GreeterPrivate::handleRespond(const QString &response)
713 {
714- Q_Q(Greeter);
715-
716- QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
717- settings.beginGroup(authenticationUser);
718- QVariant password = settings.value("password", "none");
719-
720- QString passwordValue;
721- if (password == "pin") {
722- passwordValue = settings.value("passwordValue", "1234").toString();
723- } else {
724- passwordValue = settings.value("passwordValue", "password").toString();
725- }
726- authenticated = (response == passwordValue);
727- Q_EMIT q->authenticationComplete();
728-}
729-
730-}
731+ m_impl->respond(response);
732+}
733+
734+}
735+
736+#include "GreeterPrivate.moc"
737
738=== modified file 'tests/mocks/LightDM/demo/UsersModelPrivate.cpp'
739--- tests/mocks/LightDM/demo/UsersModelPrivate.cpp 2014-06-11 15:36:51 +0000
740+++ tests/mocks/LightDM/demo/UsersModelPrivate.cpp 2014-07-25 13:39:35 +0000
741@@ -29,7 +29,7 @@
742 : q_ptr(parent)
743 {
744 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
745- QStringList users = settings.value("users", QStringList() << "phablet").toStringList();
746+ QStringList users = settings.value("users", QStringList() << qgetenv("USER")).toStringList();
747
748 Q_FOREACH(const QString &user, users)
749 {
750
751=== modified file 'tests/mocks/LightDM/full/GreeterPrivate.cpp'
752--- tests/mocks/LightDM/full/GreeterPrivate.cpp 2013-06-11 10:30:07 +0000
753+++ tests/mocks/LightDM/full/GreeterPrivate.cpp 2014-07-25 13:39:35 +0000
754@@ -54,7 +54,7 @@
755 authenticated = true;
756 Q_EMIT q->authenticationComplete();
757 } else if (authenticationUser == "has-pin"){
758- Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
759+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
760 } else if (authenticationUser == "auth-error") {
761 authenticated = false;
762 Q_EMIT q->authenticationComplete();
763
764=== modified file 'tests/mocks/LightDM/single-pin/GreeterPrivate.cpp'
765--- tests/mocks/LightDM/single-pin/GreeterPrivate.cpp 2014-06-24 23:52:12 +0000
766+++ tests/mocks/LightDM/single-pin/GreeterPrivate.cpp 2014-07-25 13:39:35 +0000
767@@ -32,7 +32,7 @@
768 void GreeterPrivate::handleAuthenticate()
769 {
770 Q_Q(Greeter);
771- Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
772+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
773 }
774
775 void GreeterPrivate::handleRespond(const QString &response)
776
777=== modified file 'tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp'
778--- tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp 2013-06-14 19:35:25 +0000
779+++ tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp 2014-07-25 13:39:35 +0000
780@@ -26,7 +26,7 @@
781 {
782 entries =
783 {
784- { "single", "Has PIN", 0, 0, false, false, 0, 0 },
785+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
786 };
787 }
788
789
790=== modified file 'tests/qmltests/Greeter/tst_Lockscreen.qml'
791--- tests/qmltests/Greeter/tst_Lockscreen.qml 2014-06-30 22:26:41 +0000
792+++ tests/qmltests/Greeter/tst_Lockscreen.qml 2014-07-25 13:39:35 +0000
793@@ -18,8 +18,9 @@
794 import QtTest 1.0
795 import ".."
796 import "../../../qml/Components"
797+import AccountsService 0.1
798+import LightDM 0.1 as LightDM
799 import Ubuntu.Components 0.1
800-import LightDM 0.1 as LightDM
801 import Unity.Test 0.1 as UT
802
803 Rectangle {
804@@ -53,11 +54,16 @@
805 }
806 }
807
808+ function setUser(username) {
809+ AccountsService.user = username
810+ LightDM.Greeter.authenticate(username)
811+ }
812+
813 Connections {
814 target: LightDM.Greeter
815
816 onShowPrompt: {
817- if (text === "PIN") {
818+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
819 pinPadCheckBox.checked = false
820 } else {
821 pinPadCheckBox.checked = true
822@@ -139,12 +145,12 @@
823 Button {
824 text: "start auth (1234)"
825 width: parent.width
826- onClicked: LightDM.Greeter.authenticate("has-pin")
827+ onClicked: setUser("has-pin")
828 }
829 Button {
830 text: "start auth (password)"
831 width: parent.width
832- onClicked: LightDM.Greeter.authenticate("has-password")
833+ onClicked: setUser("has-password")
834 }
835
836 TextField {
837@@ -255,7 +261,7 @@
838 enteredLabel.text = ""
839 minPinLengthTextField.text = data.minPinLength
840 maxPinLengthTextField.text = data.maxPinLength
841- LightDM.Greeter.authenticate(data.username)
842+ setUser(data.username)
843 waitForLockscreenReady();
844
845 var inputField = findChild(lockscreen, "pinentryField")

Subscribers

People subscribed via source and target branches