on 239fdcc help sign in
unity8/qml/Greeter/Greeter.qml
473 lines 15 kB history raw
1/*
2 * Copyright (C) 2013,2014,2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
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
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.4
18import AccountsService 0.1
19import GSettings 1.0
20import Ubuntu.Components 1.3
21import Ubuntu.SystemImage 0.1
22import Unity.Launcher 0.1
23import Unity.Session 0.1
24import "../Components"
26Showable {
27 id: root
28 created: loader.status == Loader.Ready
30 property real dragHandleLeftMargin: 0
32 property url background
34 // How far to offset the top greeter layer during a launcher left-drag
35 property real launcherOffset
37 readonly property bool active: required || hasLockedApp
38 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
40 // True when the greeter is waiting for PAM or other setup process
41 readonly property alias waiting: d.waiting
43 property string lockedApp: ""
44 readonly property bool hasLockedApp: lockedApp !== ""
46 property bool forcedUnlock
47 readonly property bool locked: lightDM.greeter.active && !lightDM.greeter.authenticated && !forcedUnlock
49 property bool tabletMode
50 property url viewSource // only used for testing
52 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53 property int failedLoginsDelayAttempts: 7 // number of failed logins
54 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
56 readonly property bool animating: loader.item ? loader.item.animating : false
58 signal tease()
59 signal sessionStarted()
60 signal emergencyCall()
62 function forceShow() {
63 showNow();
64 d.selectUser(d.currentIndex, true);
65 }
67 function notifyAppFocusRequested(appId) {
68 if (!active) {
69 return;
70 }
72 if (hasLockedApp) {
73 if (appId === lockedApp) {
74 hide(); // show locked app
75 } else {
76 show();
77 d.startUnlock(false /* toTheRight */);
78 }
79 } else if (appId !== "unity8-dash") { // dash isn't started by user
80 d.startUnlock(false /* toTheRight */);
81 }
82 }
84 // Notify that the user has explicitly requested the given app through unity8 GUI.
85 function notifyUserRequestedApp(appId) {
86 if (!active) {
87 return;
88 }
90 // A hint that we're about to focus an app. This way we can look
91 // a little more responsive, rather than waiting for the above
92 // notifyAppFocusRequested call. We also need this in case we have a locked
93 // app, in order to show lockscreen instead of new app.
94 d.startUnlock(false /* toTheRight */);
95 }
97 // This is a just a glorified notifyUserRequestedApp(), but it does one
98 // other thing: it hides any cover pages to the RIGHT, because the user
99 // just came from a launcher drag starting on the left.
100 // It also returns a boolean value, indicating whether there was a visual
101 // change or not (the shell only wants to hide the launcher if there was
102 // a change).
103 function notifyShowingDashFromDrag() {
104 if (!active) {
105 return false;
106 }
108 return d.startUnlock(true /* toTheRight */);
109 }
111 LightDM{id:lightDM} // Provide backend access
112 QtObject {
113 id: d
115 readonly property bool multiUser: lightDM.users.count > 1
116 property int currentIndex
117 property bool waiting
119 // We want 'launcherOffset' to animate down to zero. But not to animate
120 // while being dragged. So ideally we change this only when the user
121 // lets go and launcherOffset drops to zero. But we need to wait for
122 // the behavior to be enabled first. So we cache the last known good
123 // launcherOffset value to cover us during that brief gap between
124 // release and the behavior turning on.
125 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
126 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
127 Behavior on launcherOffsetProxy {
128 id: launcherOffsetProxyBehavior
129 enabled: launcherOffset === 0
130 UbuntuNumberAnimation {}
131 }
133 function selectUser(uid, reset) {
134 d.waiting = true;
135 if (reset) {
136 loader.item.reset();
137 }
138 currentIndex = uid;
139 var user = lightDM.users.data(uid, lightDM.userRoles.NameRole);
140 AccountsService.user = user;
141 LauncherModel.setUser(user);
142 lightDM.greeter.authenticate(user); // always resets auth state
143 }
145 function login() {
146 enabled = false;
147 if (lightDM.greeter.startSessionSync()) {
148 sessionStarted();
149 if (loader.item) {
150 loader.item.notifyAuthenticationSucceeded();
151 }
152 } else if (loader.item) {
153 loader.item.notifyAuthenticationFailed();
154 }
155 enabled = true;
156 }
158 function startUnlock(toTheRight) {
159 if (loader.item) {
160 return loader.item.tryToUnlock(toTheRight);
161 } else {
162 return false;
163 }
164 }
166 function checkForcedUnlock() {
167 if (forcedUnlock && shown && loader.item) {
168 // pretend we were just authenticated
169 loader.item.notifyAuthenticationSucceeded();
170 loader.item.hide();
171 }
172 }
173 }
175 onLauncherOffsetChanged: {
176 if (launcherOffset > 0) {
177 d.lastKnownPositiveOffset = launcherOffset;
178 }
179 }
181 onForcedUnlockChanged: d.checkForcedUnlock()
182 Component.onCompleted: d.checkForcedUnlock()
184 onRequiredChanged: {
185 if (required) {
186 d.waiting = true;
187 lockedApp = "";
188 }
189 }
191 GSettings {
192 id: greeterSettings
193 schema.id: "com.canonical.Unity8.Greeter"
194 }
196 Timer {
197 id: forcedDelayTimer
199 // We use a short interval and check against the system wall clock
200 // because we have to consider the case that the system is suspended
201 // for a few minutes. When we wake up, we want to quickly be correct.
202 interval: 500
204 property var delayTarget
205 property int delayMinutes
207 function forceDelay() {
208 // Store the beginning time for a lockout in GSettings, so that
209 // we still lock the user out if they reboot. And we store
210 // starting time rather than end-time or how-long because:
211 // - If storing end-time and on boot we have a problem with NTP,
212 // we might get locked out for a lot longer than we thought.
213 // - If storing how-long, and user turns their phone off for an
214 // hour rather than wait, they wouldn't expect to still be locked
215 // out.
216 // - A malicious actor could manipulate either of the above
217 // settings to keep the user out longer. But by storing
218 // start-time, we never make the user wait longer than the full
219 // lock out time.
220 greeterSettings.lockedOutTime = new Date().getTime();
221 checkForForcedDelay();
222 }
224 onTriggered: {
225 var diff = delayTarget - new Date();
226 if (diff > 0) {
227 delayMinutes = Math.ceil(diff / 60000);
228 start(); // go again
229 } else {
230 delayMinutes = 0;
231 }
232 }
234 function checkForForcedDelay() {
235 if (greeterSettings.lockedOutTime === 0) {
236 return;
237 }
239 var now = new Date();
240 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
242 // If tooEarly is true, something went very wrong. Bug or NTP
243 // misconfiguration maybe?
244 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
245 var tooLate = now >= delayTarget;
247 // Compare stored time to system time. If a malicious actor is
248 // able to manipulate time to avoid our lockout, they already have
249 // enough access to cause damage. So we choose to trust this check.
250 if (tooEarly || tooLate) {
251 stop();
252 delayMinutes = 0;
253 } else {
254 triggered();
255 }
256 }
258 Component.onCompleted: checkForForcedDelay()
259 }
261 // event eater
262 // Nothing should leak to items behind the greeter
263 MouseArea { anchors.fill: parent; hoverEnabled: true }
265 Loader {
266 id: loader
267 objectName: "loader"
269 anchors.fill: parent
271 active: root.required
272 source: root.viewSource.toString() ? root.viewSource :
273 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
275 onLoaded: {
276 root.lockedApp = "";
277 root.forceActiveFocus();
278 d.selectUser(d.currentIndex, true);
279 lightDM.infographic.readyForDataChange();
280 }
282 Connections {
283 target: loader.item
284 onSelected: {
285 d.selectUser(index, true);
286 }
287 onResponded: {
288 if (root.locked) {
289 lightDM.greeter.respond(response);
290 } else {
291 if (lightDM.greeter.active && !lightDM.greeter.authenticated) { // could happen if forcedUnlock
292 d.login();
293 }
294 loader.item.hide();
295 }
296 }
297 onTease: root.tease()
298 onEmergencyCall: root.emergencyCall()
299 onRequiredChanged: {
300 if (!loader.item.required) {
301 root.hide();
302 }
303 }
304 }
306 Binding {
307 target: loader.item
308 property: "backgroundTopMargin"
309 value: -root.y
310 }
312 Binding {
313 target: loader.item
314 property: "launcherOffset"
315 value: d.launcherOffsetProxy
316 }
318 Binding {
319 target: loader.item
320 property: "dragHandleLeftMargin"
321 value: root.dragHandleLeftMargin
322 }
324 Binding {
325 target: loader.item
326 property: "delayMinutes"
327 value: forcedDelayTimer.delayMinutes
328 }
330 Binding {
331 target: loader.item
332 property: "background"
333 value: root.background
334 }
336 Binding {
337 target: loader.item
338 property: "locked"
339 value: root.locked
340 }
342 Binding {
343 target: loader.item
344 property: "alphanumeric"
345 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
346 }
348 Binding {
349 target: loader.item
350 property: "currentIndex"
351 value: d.currentIndex
352 }
354 Binding {
355 target: loader.item
356 property: "userModel"
357 value: lightDM.users
358 }
360 Binding {
361 target: loader.item
362 property: "infographicModel"
363 value: lightDM.infographic
364 }
365 }
367 Connections {
368 target: lightDM.greeter
370 onShowGreeter: root.forceShow()
372 onHideGreeter: {
373 d.login();
374 loader.item.hide();
375 }
377 onShowMessage: {
378 if (!lightDM.greeter.active) {
379 return; // could happen if hideGreeter() comes in before we prompt
380 }
382 // inefficient, but we only rarely deal with messages
383 var html = text.replace(/&/g, "&amp;")
384 .replace(/</g, "&lt;")
385 .replace(/>/g, "&gt;")
386 .replace(/\n/g, "<br>");
387 if (isError) {
388 html = "<font color=\"#df382c\">" + html + "</font>";
389 }
391 loader.item.showMessage(html);
392 }
394 onShowPrompt: {
395 d.waiting = false;
397 if (!lightDM.greeter.active) {
398 return; // could happen if hideGreeter() comes in before we prompt
399 }
401 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
402 }
404 onAuthenticationComplete: {
405 d.waiting = false;
407 if (lightDM.greeter.authenticated) {
408 AccountsService.failedLogins = 0;
409 d.login();
410 if (!lightDM.greeter.promptless) {
411 loader.item.hide();
412 }
413 } else {
414 if (!lightDM.greeter.promptless) {
415 AccountsService.failedLogins++;
416 }
418 // Check if we should initiate a factory reset
419 if (maxFailedLogins >= 2) { // require at least a warning
420 if (AccountsService.failedLogins === maxFailedLogins - 1) {
421 loader.item.showLastChance();
422 } else if (AccountsService.failedLogins >= maxFailedLogins) {
423 SystemImage.factoryReset(); // Ouch!
424 }
425 }
427 // Check if we should initiate a forced login delay
428 if (failedLoginsDelayAttempts > 0
429 && AccountsService.failedLogins > 0
430 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
431 forcedDelayTimer.forceDelay();
432 }
434 loader.item.notifyAuthenticationFailed();
435 if (!lightDM.greeter.promptless) {
436 d.selectUser(d.currentIndex, false);
437 }
438 }
439 }
441 onRequestAuthenticationUser: {
442 // Find index for requested user, if it exists
443 for (var i = 0; i < lightDM.users.count; i++) {
444 if (user === lightDM.users.data(i, lightDM.userRoles.NameRole)) {
445 d.selectUser(i, true);
446 return;
447 }
448 }
449 }
450 }
452 Connections {
453 target: DBusUnitySessionService
454 onLockRequested: root.forceShow()
455 }
457 Binding {
458 target: lightDM.greeter
459 property: "active"
460 value: root.active
461 }
463 Binding {
464 target: lightDM.infographic
465 property: "username"
466 value: AccountsService.statsWelcomeScreen ? lightDM.users.data(d.currentIndex, lightDM.userRoles.NameRole) : ""
467 }
469 Connections {
470 target: i18n
471 onLanguageChanged: lightDM.infographic.readyForDataChange()
472 }