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
1=== modified file 'plugins/security-privacy/LockSecurity.qml'
2--- plugins/security-privacy/LockSecurity.qml 2015-01-12 13:31:14 +0000
3+++ plugins/security-privacy/LockSecurity.qml 2015-01-26 20:32:19 +0000
4@@ -31,6 +31,24 @@
5 id: page
6 title: i18n.tr("Lock security")
7
8+ // The user can still press the main "back" button or other buttons on the
9+ // page while the "change password" dialog is up. This is because the
10+ // dialog is not guaranteed to cover the whole screen; consider the case of
11+ // turning a phone to landscape mode. We'd rather not have the password
12+ // changing operation interrupted by destroying the dialog out from under
13+ // it. So we make sure the whole page and header back button are disabled
14+ // while the dialog is working.
15+ enabled: dialog === null
16+ head.backAction: Action {
17+ iconName: "back"
18+ enabled: page.enabled
19+ onTriggered: {
20+ pageStack.pop();
21+ }
22+ }
23+
24+ property var dialog: null
25+
26 UbuntuSecurityPrivacyPanel {
27 id: securityPrivacy
28 }
29@@ -58,16 +76,12 @@
30 }
31
32 function openDialog() {
33- var newMethod = indexToMethod(unlockMethod.selectedIndex)
34- if (securityPrivacy.trySetSecurity(newMethod))
35- return // Oh, that was easy! No need to prompt
36-
37- var dlg = PopupUtils.open(dialogComponent)
38+ dialog = PopupUtils.open(dialogComponent, page)
39 // Set manually rather than have these be dynamically bound, since
40 // the security type can change out from under us, but we don't
41 // want dialog to change in that case.
42- dlg.oldMethod = securityPrivacy.securityType
43- dlg.newMethod = newMethod
44+ dialog.oldMethod = securityPrivacy.securityType
45+ dialog.newMethod = indexToMethod(unlockMethod.selectedIndex)
46 }
47
48 RegExpValidator {
49@@ -341,6 +355,7 @@
50 enabled: newInput.acceptableInput
51 onClicked: {
52 changeSecurityDialog.enabled = false
53+ incorrect.text = ""
54
55 var match = (newInput.text == confirmInput.text)
56 notMatching.visible = !match
57@@ -355,16 +370,15 @@
58 currentInput.visible ? currentInput.text : "",
59 newInput.text,
60 changeSecurityDialog.newMethod)
61- incorrect.text = errorText
62+
63 if (errorText !== "") {
64- changeSecurityDialog.enabled = true
65+ incorrect.text = errorText
66 currentInput.forceActiveFocus()
67 currentInput.selectAll()
68- return
69+ changeSecurityDialog.enabled = true
70+ } else {
71+ PopupUtils.close(changeSecurityDialog)
72 }
73-
74- changeSecurityDialog.enabled = true
75- PopupUtils.close(changeSecurityDialog)
76 }
77 }
78 }
79
80=== modified file 'plugins/security-privacy/polkitlistener.cpp'
81--- plugins/security-privacy/polkitlistener.cpp 2014-09-03 13:28:48 +0000
82+++ plugins/security-privacy/polkitlistener.cpp 2015-01-26 20:32:19 +0000
83@@ -18,6 +18,7 @@
84 */
85
86 #include "polkitlistener.h"
87+#include <stdlib.h>
88
89 struct _UssPolkitListenerPrivate
90 {
91@@ -104,7 +105,35 @@
92 {
93 UssPolkitListenerPrivate *priv = listener->priv;
94
95- PolkitSubject *subject = polkit_unix_process_new_for_owner(priv->pid, 0, getuid());
96+ // Use session subject rather than process subject because polkitd doesn't
97+ // yet support revoking process subject authorizations yet. Note that this
98+ // means for a brief moment we will be answering authorization requests for
99+ // everyone. But that's OK. It also means when we revoke authorization,
100+ // we clear the whole session's cached auths. But that's also OK because
101+ // we are changing passwords here. Not unreasonable to do so. And they're
102+ // only cached auths. It's not critical that they are preserved.
103+ PolkitSubject *subject = polkit_unix_session_new(getenv("XDG_SESSION_ID"));
104+ if (!subject) {
105+ return false;
106+ }
107+
108+ // Revoke any authentication. This is to ensure that policykit actually
109+ // verifies the password we took from the user. If policykit has a cached
110+ // auth token, the user could have entered the wrong password and wonder
111+ // why we asked for it if we don't check it. We value a consistent
112+ // interface over the rare times a user will be pleasantly surprised we
113+ // kept track of the authorization (for only the swipe option really...).
114+ // There will still be a tiny race between revokation and asking policykit,
115+ // where the user could be granted authorization again. But that seems
116+ // vanishingly unlikely and to fix it, we'd need to pass the password
117+ // prompt signal up to UI and back down again. Let's just not worry
118+ // about it.
119+ PolkitAuthority *authority = polkit_authority_get_sync(nullptr, nullptr);
120+ polkit_authority_revoke_temporary_authorizations_sync(authority, subject,
121+ nullptr, nullptr);
122+ g_object_unref(authority);
123+
124+ // Now actually register ourselves
125 priv->registration = polkit_agent_listener_register(POLKIT_AGENT_LISTENER(listener),
126 POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,
127 subject, nullptr, nullptr, nullptr);
128
129=== modified file 'plugins/security-privacy/securityprivacy.cpp'
130--- plugins/security-privacy/securityprivacy.cpp 2014-10-16 15:53:41 +0000
131+++ plugins/security-privacy/securityprivacy.cpp 2015-01-26 20:32:19 +0000
132@@ -18,6 +18,8 @@
133 */
134
135 #include "securityprivacy.h"
136+#include <QtCore/QCoreApplication>
137+#include <QtCore/QDateTime>
138 #include <QtCore/QDebug>
139 #include <QtCore/QProcess>
140 #include <QtDBus/QDBusConnection>
141@@ -256,7 +258,7 @@
142 // SetPasswordMode will involve a check with policykit to see
143 // if we have admin authorization. Since Touch doesn't have a general
144 // policykit agent yet (and the design for this panel involves asking for
145- // the password up from anyway), we will spawn our own agent just for this
146+ // the password directly anyway), we will spawn our own agent just for this
147 // call. It will only authorize one request for this pid and it will use
148 // the password we pass it via stdin. We can drop this helper code when
149 // Touch has a real policykit agent and/or the design for this panel
150@@ -267,10 +269,7 @@
151 // and QProcess's signal handling conflict. They seem to get in each
152 // other's way for the same signals. So we just do this out-of-process.
153
154- // But first, see if we have cached authentication
155- if (setPasswordMode(type))
156- return true;
157- else if (password.isEmpty())
158+ if (password.isEmpty())
159 return false;
160
161 QProcess polkitHelper;
162@@ -279,15 +278,32 @@
163 polkitHelper.write(password.toUtf8() + "\n");
164 polkitHelper.closeWriteChannel();
165
166- while (polkitHelper.canReadLine() || polkitHelper.waitForReadyRead()) {
167- QString output = polkitHelper.readLine();
168- if (output == "ready\n")
169- break;
170+ qint64 endTime = QDateTime::currentMSecsSinceEpoch() + 10000;
171+
172+ while (polkitHelper.state() != QProcess::NotRunning) {
173+ if (polkitHelper.canReadLine()) {
174+ QString output = polkitHelper.readLine();
175+ if (output == "ready\n")
176+ break;
177+ }
178+ qint64 waitTime = endTime - QDateTime::currentMSecsSinceEpoch();
179+ if (waitTime <= 0) {
180+ polkitHelper.kill();
181+ return false;
182+ }
183+ QCoreApplication::processEvents(QEventLoop::AllEvents, waitTime);
184 }
185
186 bool success = setPasswordMode(type);
187
188- polkitHelper.waitForFinished();
189+ while (polkitHelper.state() != QProcess::NotRunning) {
190+ qint64 waitTime = endTime - QDateTime::currentMSecsSinceEpoch();
191+ if (waitTime <= 0) {
192+ polkitHelper.kill();
193+ return false;
194+ }
195+ QCoreApplication::processEvents(QEventLoop::AllEvents, waitTime);
196+ }
197
198 return success;
199 }
200@@ -337,30 +353,6 @@
201 }
202 }
203
204-bool SecurityPrivacy::trySetSecurity(SecurityType type)
205-{
206- if (m_user == NULL || !act_user_is_loaded(m_user))
207- return false;
208-
209- // We only support setting swipe without more information
210- if (type != SecurityPrivacy::Swipe)
211- return false;
212-
213- SecurityType oldType = getSecurityType();
214- if (type == oldType)
215- return true; // nothing to do
216-
217- if (!setDisplayHint(type))
218- return false;
219-
220- if (!setPasswordMode(type)) {
221- setDisplayHint(oldType);
222- return false;
223- }
224-
225- return true;
226-}
227-
228 QString SecurityPrivacy::setSecurity(QString oldValue, QString value, SecurityType type)
229 {
230 if (m_user == nullptr || !act_user_is_loaded(m_user))
231
232=== modified file 'plugins/security-privacy/securityprivacy.h'
233--- plugins/security-privacy/securityprivacy.h 2014-10-16 13:43:16 +0000
234+++ plugins/security-privacy/securityprivacy.h 2015-01-26 20:32:19 +0000
235@@ -93,7 +93,6 @@
236
237 // Returns error text, if an error occurred
238 Q_INVOKABLE QString setSecurity(QString oldValue, QString value, SecurityType type);
239- Q_INVOKABLE bool trySetSecurity(SecurityType type);
240
241 public Q_SLOTS:
242 void slotChanged(QString, QString);
243
244=== modified file 'src/SystemSettings/ItemPage.qml'
245--- src/SystemSettings/ItemPage.qml 2013-07-30 14:09:08 +0000
246+++ src/SystemSettings/ItemPage.qml 2015-01-26 20:32:19 +0000
247@@ -19,7 +19,7 @@
248 */
249
250 import QtQuick 2.0
251-import Ubuntu.Components 0.1
252+import Ubuntu.Components 1.1
253
254 Page {
255 id: root

Subscribers

People subscribed via source and target branches