on c1843bf help sign in
unity8/qml/Greeter/Greeter.qml
472 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 notifyAppFocused(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 function notifyAboutToFocusApp(appId) {
85 if (!active) {
86 return;
87 }
89 // A hint that we're about to focus an app. This way we can look
90 // a little more responsive, rather than waiting for the above
91 // notifyAppFocused call. We also need this in case we have a locked
92 // app, in order to show lockscreen instead of new app.
93 d.startUnlock(false /* toTheRight */);
94 }
96 // This is a just a glorified notifyAboutToFocusApp(), but it does one
97 // other thing: it hides any cover pages to the RIGHT, because the user
98 // just came from a launcher drag starting on the left.
99 // It also returns a boolean value, indicating whether there was a visual
100 // change or not (the shell only wants to hide the launcher if there was
101 // a change).
102 function notifyShowingDashFromDrag() {
103 if (!active) {
104 return false;
105 }
107 return d.startUnlock(true /* toTheRight */);
108 }
110 LightDM{id:lightDM} // Provide backend access
111 QtObject {
112 id: d
114 readonly property bool multiUser: lightDM.users.count > 1
115 property int currentIndex
116 property bool waiting
118 // We want 'launcherOffset' to animate down to zero. But not to animate
119 // while being dragged. So ideally we change this only when the user
120 // lets go and launcherOffset drops to zero. But we need to wait for
121 // the behavior to be enabled first. So we cache the last known good
122 // launcherOffset value to cover us during that brief gap between
123 // release and the behavior turning on.
124 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
125 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
126 Behavior on launcherOffsetProxy {
127 id: launcherOffsetProxyBehavior
128 enabled: launcherOffset === 0
129 UbuntuNumberAnimation {}
130 }
132 function selectUser(uid, reset) {
133 d.waiting = true;
134 if (reset) {
135 loader.item.reset();
136 }
137 currentIndex = uid;
138 var user = lightDM.users.data(uid, lightDM.userRoles.NameRole);
139 AccountsService.user = user;
140 LauncherModel.setUser(user);
141 lightDM.greeter.authenticate(user); // always resets auth state
142 }
144 function login() {
145 enabled = false;
146 if (lightDM.greeter.startSessionSync()) {
147 sessionStarted();
148 if (loader.item) {
149 loader.item.notifyAuthenticationSucceeded();
150 }
151 } else if (loader.item) {
152 loader.item.notifyAuthenticationFailed();
153 }
154 enabled = true;
155 }
157 function startUnlock(toTheRight) {
158 if (loader.item) {
159 return loader.item.tryToUnlock(toTheRight);
160 } else {
161 return false;
162 }
163 }
165 function checkForcedUnlock() {
166 if (forcedUnlock && shown && loader.item) {
167 // pretend we were just authenticated
168 loader.item.notifyAuthenticationSucceeded();
169 loader.item.hide();
170 }
171 }
172 }
174 onLauncherOffsetChanged: {
175 if (launcherOffset > 0) {
176 d.lastKnownPositiveOffset = launcherOffset;
177 }
178 }
180 onForcedUnlockChanged: d.checkForcedUnlock()
181 Component.onCompleted: d.checkForcedUnlock()
183 onRequiredChanged: {
184 if (required) {
185 d.waiting = true;
186 lockedApp = "";
187 }
188 }
190 GSettings {
191 id: greeterSettings
192 schema.id: "com.canonical.Unity8.Greeter"
193 }
195 Timer {
196 id: forcedDelayTimer
198 // We use a short interval and check against the system wall clock
199 // because we have to consider the case that the system is suspended
200 // for a few minutes. When we wake up, we want to quickly be correct.
201 interval: 500
203 property var delayTarget
204 property int delayMinutes
206 function forceDelay() {
207 // Store the beginning time for a lockout in GSettings, so that
208 // we still lock the user out if they reboot. And we store
209 // starting time rather than end-time or how-long because:
210 // - If storing end-time and on boot we have a problem with NTP,
211 // we might get locked out for a lot longer than we thought.
212 // - If storing how-long, and user turns their phone off for an
213 // hour rather than wait, they wouldn't expect to still be locked
214 // out.
215 // - A malicious actor could manipulate either of the above
216 // settings to keep the user out longer. But by storing
217 // start-time, we never make the user wait longer than the full
218 // lock out time.
219 greeterSettings.lockedOutTime = new Date().getTime();
220 checkForForcedDelay();
221 }
223 onTriggered: {
224 var diff = delayTarget - new Date();
225 if (diff > 0) {
226 delayMinutes = Math.ceil(diff / 60000);
227 start(); // go again
228 } else {
229 delayMinutes = 0;
230 }
231 }
233 function checkForForcedDelay() {
234 if (greeterSettings.lockedOutTime === 0) {
235 return;
236 }
238 var now = new Date();
239 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
241 // If tooEarly is true, something went very wrong. Bug or NTP
242 // misconfiguration maybe?
243 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
244 var tooLate = now >= delayTarget;
246 // Compare stored time to system time. If a malicious actor is
247 // able to manipulate time to avoid our lockout, they already have
248 // enough access to cause damage. So we choose to trust this check.
249 if (tooEarly || tooLate) {
250 stop();
251 delayMinutes = 0;
252 } else {
253 triggered();
254 }
255 }
257 Component.onCompleted: checkForForcedDelay()
258 }
260 // event eater
261 // Nothing should leak to items behind the greeter
262 MouseArea { anchors.fill: parent; hoverEnabled: true }
264 Loader {
265 id: loader
266 objectName: "loader"
268 anchors.fill: parent
270 active: root.required
271 source: root.viewSource.toString() ? root.viewSource :
272 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
274 onLoaded: {
275 root.lockedApp = "";
276 root.forceActiveFocus();
277 d.selectUser(d.currentIndex, true);
278 lightDM.infographic.readyForDataChange();
279 }
281 Connections {
282 target: loader.item
283 onSelected: {
284 d.selectUser(index, true);
285 }
286 onResponded: {
287 if (root.locked) {
288 lightDM.greeter.respond(response);
289 } else {
290 if (lightDM.greeter.active && !lightDM.greeter.authenticated) { // could happen if forcedUnlock
291 d.login();
292 }
293 loader.item.hide();
294 }
295 }
296 onTease: root.tease()
297 onEmergencyCall: root.emergencyCall()
298 onRequiredChanged: {
299 if (!loader.item.required) {
300 root.hide();
301 }
302 }
303 }
305 Binding {
306 target: loader.item
307 property: "backgroundTopMargin"
308 value: -root.y
309 }
311 Binding {
312 target: loader.item
313 property: "launcherOffset"
314 value: d.launcherOffsetProxy
315 }
317 Binding {
318 target: loader.item
319 property: "dragHandleLeftMargin"
320 value: root.dragHandleLeftMargin
321 }
323 Binding {
324 target: loader.item
325 property: "delayMinutes"
326 value: forcedDelayTimer.delayMinutes
327 }
329 Binding {
330 target: loader.item
331 property: "background"
332 value: root.background
333 }
335 Binding {
336 target: loader.item
337 property: "locked"
338 value: root.locked
339 }
341 Binding {
342 target: loader.item
343 property: "alphanumeric"
344 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
345 }
347 Binding {
348 target: loader.item
349 property: "currentIndex"
350 value: d.currentIndex
351 }
353 Binding {
354 target: loader.item
355 property: "userModel"
356 value: lightDM.users
357 }
359 Binding {
360 target: loader.item
361 property: "infographicModel"
362 value: lightDM.infographic
363 }
364 }
366 Connections {
367 target: lightDM.greeter
369 onShowGreeter: root.forceShow()
371 onHideGreeter: {
372 d.login();
373 loader.item.hide();
374 }
376 onShowMessage: {
377 if (!lightDM.greeter.active) {
378 return; // could happen if hideGreeter() comes in before we prompt
379 }
381 // inefficient, but we only rarely deal with messages
382 var html = text.replace(/&/g, "&amp;")
383 .replace(/</g, "&lt;")
384 .replace(/>/g, "&gt;")
385 .replace(/\n/g, "<br>");
386 if (isError) {
387 html = "<font color=\"#df382c\">" + html + "</font>";
388 }
390 loader.item.showMessage(html);
391 }
393 onShowPrompt: {
394 d.waiting = false;
396 if (!lightDM.greeter.active) {
397 return; // could happen if hideGreeter() comes in before we prompt
398 }
400 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
401 }
403 onAuthenticationComplete: {
404 d.waiting = false;
406 if (lightDM.greeter.authenticated) {
407 AccountsService.failedLogins = 0;
408 d.login();
409 if (!lightDM.greeter.promptless) {
410 loader.item.hide();
411 }
412 } else {
413 if (!lightDM.greeter.promptless) {
414 AccountsService.failedLogins++;
415 }
417 // Check if we should initiate a factory reset
418 if (maxFailedLogins >= 2) { // require at least a warning
419 if (AccountsService.failedLogins === maxFailedLogins - 1) {
420 loader.item.showLastChance();
421 } else if (AccountsService.failedLogins >= maxFailedLogins) {
422 SystemImage.factoryReset(); // Ouch!
423 }
424 }
426 // Check if we should initiate a forced login delay
427 if (failedLoginsDelayAttempts > 0
428 && AccountsService.failedLogins > 0
429 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
430 forcedDelayTimer.forceDelay();
431 }
433 loader.item.notifyAuthenticationFailed();
434 if (!lightDM.greeter.promptless) {
435 d.selectUser(d.currentIndex, false);
436 }
437 }
438 }
440 onRequestAuthenticationUser: {
441 // Find index for requested user, if it exists
442 for (var i = 0; i < lightDM.users.count; i++) {
443 if (user === lightDM.users.data(i, lightDM.userRoles.NameRole)) {
444 d.selectUser(i, true);
445 return;
446 }
447 }
448 }
449 }
451 Connections {
452 target: DBusUnitySessionService
453 onLockRequested: root.forceShow()
454 }
456 Binding {
457 target: lightDM.greeter
458 property: "active"
459 value: root.active
460 }
462 Binding {
463 target: lightDM.infographic
464 property: "username"
465 value: AccountsService.statsWelcomeScreen ? lightDM.users.data(d.currentIndex, lightDM.userRoles.NameRole) : ""
466 }
468 Connections {
469 target: i18n
470 onLanguageChanged: lightDM.infographic.readyForDataChange()
471 }