Merge lp:~mterry/ubuntu-system-settings/revoke-auth into lp:ubuntu-system-settings

Proposed by Michael Terry
Status: Merged
Approved by: Ken VanDine
Approved revision: 1266
Merged at revision: 1275
Proposed branch: lp:~mterry/ubuntu-system-settings/revoke-auth
Merge into: lp:ubuntu-system-settings
Diff against target: 255 lines (+84/-50)
5 files modified
plugins/security-privacy/LockSecurity.qml (+27/-13)
plugins/security-privacy/polkitlistener.cpp (+30/-1)
plugins/security-privacy/securityprivacy.cpp (+26/-34)
plugins/security-privacy/securityprivacy.h (+0/-1)
src/SystemSettings/ItemPage.qml (+1/-1)
To merge this branch: bzr merge lp:~mterry/ubuntu-system-settings/revoke-auth
Reviewer Review Type Date Requested Status
Ken VanDine Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+247610@code.launchpad.net

Commit message

Revoke any cached authorizations before trying to change password mode. This makes sure that we authenticate the user again even if we just did so (for UX consistency).

Note that this is kind of an involved change:

- I took out the trySetSecurity call which doesn't make sense anymore if we don't allow cached authorization.

- I changed the policykit agent to register as a session-wide agent (not just for the system-settings process). This is because polkitd does not yet allow you to revoke cached authorizations for processes. Registering and revoking as a session-wide agent should be fine.

- I also had to use the call to revoke all authorizations. There is a more targeted call, to only remove one authorization, by ID. But polkitd didn't let me find the authorization ID when I queried for it. So... I figured just revoking all of them was fine. We are changing the password after all. Either the user changes it and it makes sense to revoke all auths from the old password or the user fails to change it and it makes sense to revoke all auths as a security measure (some non-owner is holding the phone). Between the problems revoking-by-process and revoking-by-ID, I get the sense that the revokation API isn't often used.

- Adding more calls to the policykit agent made it more noticeable that we were using a blocking call to wait for the agent to finish. That's bad UX. So I changed system-settings to call QCoreApplication::processEvents every now and then while waiting.

- Now that Qt was processing events while waiting, I had introduced a new problem: the user can interact with the main page (even pressing the "back" button) while the dialog is waiting -- you can see this by going to landscape mode). So I made sure that the page and back button are disabled while the dialog is up. (And to get access to the back button, I had to bump the Ubuntu.Components API version to 1.1 to get the 'head' property on a Page.)

Description of the change

Revoke any cached authorizations before trying to change password mode. This makes sure that we authenticate the user again even if we just did so (for UX consistency).

Note that this is kind of an involved change:

- I took out the trySetSecurity call which doesn't make sense anymore if we don't allow cached authorization.

- I changed the policykit agent to register as a session-wide agent (not just for the system-settings process). This is because polkitd does not yet allow you to revoke cached authorizations for processes. Registering and revoking as a session-wide agent should be fine.

- I also had to use the call to revoke all authorizations. There is a more targeted call, to only remove one authorization, by ID. But polkitd didn't let me find the authorization ID when I queried for it. So... I figured just revoking all of them was fine. We are changing the password after all. Either the user changes it and it makes sense to revoke all auths from the old password or the user fails to change it and it makes sense to revoke all auths as a security measure (some non-owner is holding the phone). Between the problems revoking-by-process and revoking-by-ID, I get the sense that the revokation API isn't often used.

- Adding more calls to the policykit agent made it more noticeable that we were using a blocking call to wait for the agent to finish. That's bad UX. So I changed system-settings to call QCoreApplication::processEvents every now and then while waiting.

- Now that Qt was processing events while waiting, I had introduced a new problem: the user can interact with the main page (even pressing the "back" button) while the dialog is waiting -- you can see this by going to landscape mode). So I made sure that the page and back button are disabled while the dialog is up. (And to get access to the back button, I had to bump the Ubuntu.Components API version to 1.1 to get the 'head' property on a Page.)

To post a comment you must log in.
Revision history for this message
Ken VanDine (ken-vandine) wrote :

I love all the code comments, thanks! I did ask a question inline, but I don't think it's anything that needs fixing. I think the code looks good, but we'll need to do some manual testing once we get debs from CI.

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

Inline comments replied!

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

Fix header

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)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

In testing I've confirmed it does require the old password in each attempt. However, a couple times in the change password dialog, I ran in to cases where using an incorrect current password silently failed to change the password. It's good that it didn't actually change the password, but the UI didn't show the incorrect password error. I can't reproduce it reliably, not sure if it is a regression or not. I'll do the same test without this branch now.

Revision history for this message
Ken VanDine (ken-vandine) wrote :

I did manage to reproduce the silent failure to change a password without this branch. So this isn't a regression.

review: Approve
Revision history for this message
Ken VanDine (ken-vandine) wrote :

I actually don't think what I ran into was a failure case, I think this happened when I used the incorrect current password and the current password as the new password. So it was just a no-op. I just needed a sample case of more than two different passwords.

Revision history for this message
Matthew Paul Thomas (mpt) :
Revision history for this message
Michael Terry (mterry) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'plugins/security-privacy/LockSecurity.qml'
--- plugins/security-privacy/LockSecurity.qml 2015-01-12 13:31:14 +0000
+++ plugins/security-privacy/LockSecurity.qml 2015-01-26 20:32:19 +0000
@@ -31,6 +31,24 @@
31 id: page31 id: page
32 title: i18n.tr("Lock security")32 title: i18n.tr("Lock security")
3333
34 // The user can still press the main "back" button or other buttons on the
35 // page while the "change password" dialog is up. This is because the
36 // dialog is not guaranteed to cover the whole screen; consider the case of
37 // turning a phone to landscape mode. We'd rather not have the password
38 // changing operation interrupted by destroying the dialog out from under
39 // it. So we make sure the whole page and header back button are disabled
40 // while the dialog is working.
41 enabled: dialog === null
42 head.backAction: Action {
43 iconName: "back"
44 enabled: page.enabled
45 onTriggered: {
46 pageStack.pop();
47 }
48 }
49
50 property var dialog: null
51
34 UbuntuSecurityPrivacyPanel {52 UbuntuSecurityPrivacyPanel {
35 id: securityPrivacy53 id: securityPrivacy
36 }54 }
@@ -58,16 +76,12 @@
58 }76 }
5977
60 function openDialog() {78 function openDialog() {
61 var newMethod = indexToMethod(unlockMethod.selectedIndex)79 dialog = PopupUtils.open(dialogComponent, page)
62 if (securityPrivacy.trySetSecurity(newMethod))
63 return // Oh, that was easy! No need to prompt
64
65 var dlg = PopupUtils.open(dialogComponent)
66 // Set manually rather than have these be dynamically bound, since80 // Set manually rather than have these be dynamically bound, since
67 // the security type can change out from under us, but we don't81 // the security type can change out from under us, but we don't
68 // want dialog to change in that case.82 // want dialog to change in that case.
69 dlg.oldMethod = securityPrivacy.securityType83 dialog.oldMethod = securityPrivacy.securityType
70 dlg.newMethod = newMethod84 dialog.newMethod = indexToMethod(unlockMethod.selectedIndex)
71 }85 }
7286
73 RegExpValidator {87 RegExpValidator {
@@ -341,6 +355,7 @@
341 enabled: newInput.acceptableInput355 enabled: newInput.acceptableInput
342 onClicked: {356 onClicked: {
343 changeSecurityDialog.enabled = false357 changeSecurityDialog.enabled = false
358 incorrect.text = ""
344359
345 var match = (newInput.text == confirmInput.text)360 var match = (newInput.text == confirmInput.text)
346 notMatching.visible = !match361 notMatching.visible = !match
@@ -355,16 +370,15 @@
355 currentInput.visible ? currentInput.text : "",370 currentInput.visible ? currentInput.text : "",
356 newInput.text,371 newInput.text,
357 changeSecurityDialog.newMethod)372 changeSecurityDialog.newMethod)
358 incorrect.text = errorText373
359 if (errorText !== "") {374 if (errorText !== "") {
360 changeSecurityDialog.enabled = true375 incorrect.text = errorText
361 currentInput.forceActiveFocus()376 currentInput.forceActiveFocus()
362 currentInput.selectAll()377 currentInput.selectAll()
363 return378 changeSecurityDialog.enabled = true
379 } else {
380 PopupUtils.close(changeSecurityDialog)
364 }381 }
365
366 changeSecurityDialog.enabled = true
367 PopupUtils.close(changeSecurityDialog)
368 }382 }
369 }383 }
370 }384 }
371385
=== modified file 'plugins/security-privacy/polkitlistener.cpp'
--- plugins/security-privacy/polkitlistener.cpp 2014-09-03 13:28:48 +0000
+++ plugins/security-privacy/polkitlistener.cpp 2015-01-26 20:32:19 +0000
@@ -18,6 +18,7 @@
18 */18 */
1919
20#include "polkitlistener.h"20#include "polkitlistener.h"
21#include <stdlib.h>
2122
22struct _UssPolkitListenerPrivate23struct _UssPolkitListenerPrivate
23{24{
@@ -104,7 +105,35 @@
104{105{
105 UssPolkitListenerPrivate *priv = listener->priv;106 UssPolkitListenerPrivate *priv = listener->priv;
106107
107 PolkitSubject *subject = polkit_unix_process_new_for_owner(priv->pid, 0, getuid());108 // Use session subject rather than process subject because polkitd doesn't
109 // yet support revoking process subject authorizations yet. Note that this
110 // means for a brief moment we will be answering authorization requests for
111 // everyone. But that's OK. It also means when we revoke authorization,
112 // we clear the whole session's cached auths. But that's also OK because
113 // we are changing passwords here. Not unreasonable to do so. And they're
114 // only cached auths. It's not critical that they are preserved.
115 PolkitSubject *subject = polkit_unix_session_new(getenv("XDG_SESSION_ID"));
116 if (!subject) {
117 return false;
118 }
119
120 // Revoke any authentication. This is to ensure that policykit actually
121 // verifies the password we took from the user. If policykit has a cached
122 // auth token, the user could have entered the wrong password and wonder
123 // why we asked for it if we don't check it. We value a consistent
124 // interface over the rare times a user will be pleasantly surprised we
125 // kept track of the authorization (for only the swipe option really...).
126 // There will still be a tiny race between revokation and asking policykit,
127 // where the user could be granted authorization again. But that seems
128 // vanishingly unlikely and to fix it, we'd need to pass the password
129 // prompt signal up to UI and back down again. Let's just not worry
130 // about it.
131 PolkitAuthority *authority = polkit_authority_get_sync(nullptr, nullptr);
132 polkit_authority_revoke_temporary_authorizations_sync(authority, subject,
133 nullptr, nullptr);
134 g_object_unref(authority);
135
136 // Now actually register ourselves
108 priv->registration = polkit_agent_listener_register(POLKIT_AGENT_LISTENER(listener),137 priv->registration = polkit_agent_listener_register(POLKIT_AGENT_LISTENER(listener),
109 POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,138 POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,
110 subject, nullptr, nullptr, nullptr);139 subject, nullptr, nullptr, nullptr);
111140
=== modified file 'plugins/security-privacy/securityprivacy.cpp'
--- plugins/security-privacy/securityprivacy.cpp 2014-10-16 15:53:41 +0000
+++ plugins/security-privacy/securityprivacy.cpp 2015-01-26 20:32:19 +0000
@@ -18,6 +18,8 @@
18 */18 */
1919
20#include "securityprivacy.h"20#include "securityprivacy.h"
21#include <QtCore/QCoreApplication>
22#include <QtCore/QDateTime>
21#include <QtCore/QDebug>23#include <QtCore/QDebug>
22#include <QtCore/QProcess>24#include <QtCore/QProcess>
23#include <QtDBus/QDBusConnection>25#include <QtDBus/QDBusConnection>
@@ -256,7 +258,7 @@
256 // SetPasswordMode will involve a check with policykit to see258 // SetPasswordMode will involve a check with policykit to see
257 // if we have admin authorization. Since Touch doesn't have a general259 // if we have admin authorization. Since Touch doesn't have a general
258 // policykit agent yet (and the design for this panel involves asking for260 // policykit agent yet (and the design for this panel involves asking for
259 // the password up from anyway), we will spawn our own agent just for this261 // the password directly anyway), we will spawn our own agent just for this
260 // call. It will only authorize one request for this pid and it will use262 // call. It will only authorize one request for this pid and it will use
261 // the password we pass it via stdin. We can drop this helper code when263 // the password we pass it via stdin. We can drop this helper code when
262 // Touch has a real policykit agent and/or the design for this panel264 // Touch has a real policykit agent and/or the design for this panel
@@ -267,10 +269,7 @@
267 // and QProcess's signal handling conflict. They seem to get in each269 // and QProcess's signal handling conflict. They seem to get in each
268 // other's way for the same signals. So we just do this out-of-process.270 // other's way for the same signals. So we just do this out-of-process.
269271
270 // But first, see if we have cached authentication272 if (password.isEmpty())
271 if (setPasswordMode(type))
272 return true;
273 else if (password.isEmpty())
274 return false;273 return false;
275274
276 QProcess polkitHelper;275 QProcess polkitHelper;
@@ -279,15 +278,32 @@
279 polkitHelper.write(password.toUtf8() + "\n");278 polkitHelper.write(password.toUtf8() + "\n");
280 polkitHelper.closeWriteChannel();279 polkitHelper.closeWriteChannel();
281280
282 while (polkitHelper.canReadLine() || polkitHelper.waitForReadyRead()) {281 qint64 endTime = QDateTime::currentMSecsSinceEpoch() + 10000;
283 QString output = polkitHelper.readLine();282
284 if (output == "ready\n")283 while (polkitHelper.state() != QProcess::NotRunning) {
285 break;284 if (polkitHelper.canReadLine()) {
285 QString output = polkitHelper.readLine();
286 if (output == "ready\n")
287 break;
288 }
289 qint64 waitTime = endTime - QDateTime::currentMSecsSinceEpoch();
290 if (waitTime <= 0) {
291 polkitHelper.kill();
292 return false;
293 }
294 QCoreApplication::processEvents(QEventLoop::AllEvents, waitTime);
286 }295 }
287296
288 bool success = setPasswordMode(type);297 bool success = setPasswordMode(type);
289298
290 polkitHelper.waitForFinished();299 while (polkitHelper.state() != QProcess::NotRunning) {
300 qint64 waitTime = endTime - QDateTime::currentMSecsSinceEpoch();
301 if (waitTime <= 0) {
302 polkitHelper.kill();
303 return false;
304 }
305 QCoreApplication::processEvents(QEventLoop::AllEvents, waitTime);
306 }
291307
292 return success;308 return success;
293}309}
@@ -337,30 +353,6 @@
337 }353 }
338}354}
339355
340bool SecurityPrivacy::trySetSecurity(SecurityType type)
341{
342 if (m_user == NULL || !act_user_is_loaded(m_user))
343 return false;
344
345 // We only support setting swipe without more information
346 if (type != SecurityPrivacy::Swipe)
347 return false;
348
349 SecurityType oldType = getSecurityType();
350 if (type == oldType)
351 return true; // nothing to do
352
353 if (!setDisplayHint(type))
354 return false;
355
356 if (!setPasswordMode(type)) {
357 setDisplayHint(oldType);
358 return false;
359 }
360
361 return true;
362}
363
364QString SecurityPrivacy::setSecurity(QString oldValue, QString value, SecurityType type)356QString SecurityPrivacy::setSecurity(QString oldValue, QString value, SecurityType type)
365{357{
366 if (m_user == nullptr || !act_user_is_loaded(m_user))358 if (m_user == nullptr || !act_user_is_loaded(m_user))
367359
=== modified file 'plugins/security-privacy/securityprivacy.h'
--- plugins/security-privacy/securityprivacy.h 2014-10-16 13:43:16 +0000
+++ plugins/security-privacy/securityprivacy.h 2015-01-26 20:32:19 +0000
@@ -93,7 +93,6 @@
9393
94 // Returns error text, if an error occurred94 // Returns error text, if an error occurred
95 Q_INVOKABLE QString setSecurity(QString oldValue, QString value, SecurityType type);95 Q_INVOKABLE QString setSecurity(QString oldValue, QString value, SecurityType type);
96 Q_INVOKABLE bool trySetSecurity(SecurityType type);
9796
98public Q_SLOTS:97public Q_SLOTS:
99 void slotChanged(QString, QString);98 void slotChanged(QString, QString);
10099
=== modified file 'src/SystemSettings/ItemPage.qml'
--- src/SystemSettings/ItemPage.qml 2013-07-30 14:09:08 +0000
+++ src/SystemSettings/ItemPage.qml 2015-01-26 20:32:19 +0000
@@ -19,7 +19,7 @@
19 */19 */
2020
21import QtQuick 2.021import QtQuick 2.0
22import Ubuntu.Components 0.122import Ubuntu.Components 1.1
2323
24Page {24Page {
25 id: root25 id: root

Subscribers

People subscribed via source and target branches