2 * Copyright (C) 2013,2014,2015 Canonical, Ltd.
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.
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.
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/>.
18 import AccountsService 0.1
20 import Ubuntu.Components 1.3
21 import Ubuntu.SystemImage 0.1
22 import Unity.Launcher 0.1
23 import Unity.Session 0.1
24 import "../Components"
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
59 signal sessionStarted()
60 signal emergencyCall()
62 function forceShow() {
65 showNow(); // loader.onLoaded will select a user
67 d.selectUser(d.currentIndex, true);
71 function notifyAppFocusRequested(appId) {
77 if (appId === lockedApp) {
78 hide(); // show locked app
81 d.startUnlock(false /* toTheRight */);
83 } else if (appId !== "unity8-dash") { // dash isn't started by user
84 d.startUnlock(false /* toTheRight */);
88 // Notify that the user has explicitly requested the given app through unity8 GUI.
89 function notifyUserRequestedApp(appId) {
94 // A hint that we're about to focus an app. This way we can look
95 // a little more responsive, rather than waiting for the above
96 // notifyAppFocusRequested call. We also need this in case we have a locked
97 // app, in order to show lockscreen instead of new app.
98 d.startUnlock(false /* toTheRight */);
101 // This is a just a glorified notifyUserRequestedApp(), but it does one
102 // other thing: it hides any cover pages to the RIGHT, because the user
103 // just came from a launcher drag starting on the left.
104 // It also returns a boolean value, indicating whether there was a visual
105 // change or not (the shell only wants to hide the launcher if there was
107 function notifyShowingDashFromDrag() {
112 return d.startUnlock(true /* toTheRight */);
115 LightDM{id:lightDM} // Provide backend access
119 readonly property bool multiUser: lightDM.users.count > 1
120 property int currentIndex
121 property bool waiting
123 // We want 'launcherOffset' to animate down to zero. But not to animate
124 // while being dragged. So ideally we change this only when the user
125 // lets go and launcherOffset drops to zero. But we need to wait for
126 // the behavior to be enabled first. So we cache the last known good
127 // launcherOffset value to cover us during that brief gap between
128 // release and the behavior turning on.
129 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
130 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
131 Behavior on launcherOffsetProxy {
132 id: launcherOffsetProxyBehavior
133 enabled: launcherOffset === 0
134 UbuntuNumberAnimation {}
137 function selectUser(uid, reset) {
143 var user = lightDM.users.data(uid, lightDM.userRoles.NameRole);
144 AccountsService.user = user;
145 LauncherModel.setUser(user);
146 lightDM.greeter.authenticate(user); // always resets auth state
151 if (lightDM.greeter.startSessionSync()) {
154 loader.item.notifyAuthenticationSucceeded();
156 } else if (loader.item) {
157 loader.item.notifyAuthenticationFailed();
162 function startUnlock(toTheRight) {
164 return loader.item.tryToUnlock(toTheRight);
170 function checkForcedUnlock(hideNow) {
171 if (forcedUnlock && shown && loader.item) {
172 // pretend we were just authenticated
173 loader.item.notifyAuthenticationSucceeded();
176 root.hideNow(); // skip hide animation
182 onLauncherOffsetChanged: {
183 if (launcherOffset > 0) {
184 d.lastKnownPositiveOffset = launcherOffset;
188 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
189 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
200 schema.id: "com.canonical.Unity8.Greeter"
206 // We use a short interval and check against the system wall clock
207 // because we have to consider the case that the system is suspended
208 // for a few minutes. When we wake up, we want to quickly be correct.
211 property var delayTarget
212 property int delayMinutes
214 function forceDelay() {
215 // Store the beginning time for a lockout in GSettings, so that
216 // we still lock the user out if they reboot. And we store
217 // starting time rather than end-time or how-long because:
218 // - If storing end-time and on boot we have a problem with NTP,
219 // we might get locked out for a lot longer than we thought.
220 // - If storing how-long, and user turns their phone off for an
221 // hour rather than wait, they wouldn't expect to still be locked
223 // - A malicious actor could manipulate either of the above
224 // settings to keep the user out longer. But by storing
225 // start-time, we never make the user wait longer than the full
227 greeterSettings.lockedOutTime = new Date().getTime();
228 checkForForcedDelay();
232 var diff = delayTarget - new Date();
234 delayMinutes = Math.ceil(diff / 60000);
241 function checkForForcedDelay() {
242 if (greeterSettings.lockedOutTime === 0) {
246 var now = new Date();
247 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
249 // If tooEarly is true, something went very wrong. Bug or NTP
250 // misconfiguration maybe?
251 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
252 var tooLate = now >= delayTarget;
254 // Compare stored time to system time. If a malicious actor is
255 // able to manipulate time to avoid our lockout, they already have
256 // enough access to cause damage. So we choose to trust this check.
257 if (tooEarly || tooLate) {
265 Component.onCompleted: checkForForcedDelay()
269 // Nothing should leak to items behind the greeter
270 MouseArea { anchors.fill: parent; hoverEnabled: true }
278 active: root.required
279 source: root.viewSource.toString() ? root.viewSource :
280 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
284 root.forceActiveFocus();
285 d.selectUser(d.currentIndex, true);
286 lightDM.infographic.readyForDataChange();
292 d.selectUser(index, true);
296 lightDM.greeter.respond(response);
298 if (lightDM.greeter.active && !lightDM.greeter.authenticated) { // could happen if forcedUnlock
304 onTease: root.tease()
305 onEmergencyCall: root.emergencyCall()
307 if (!loader.item.required) {
315 property: "backgroundTopMargin"
321 property: "launcherOffset"
322 value: d.launcherOffsetProxy
327 property: "dragHandleLeftMargin"
328 value: root.dragHandleLeftMargin
333 property: "delayMinutes"
334 value: forcedDelayTimer.delayMinutes
339 property: "background"
340 value: root.background
351 property: "alphanumeric"
352 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
357 property: "currentIndex"
358 value: d.currentIndex
363 property: "userModel"
369 property: "infographicModel"
370 value: lightDM.infographic
375 target: lightDM.greeter
377 onShowGreeter: root.forceShow()
385 // inefficient, but we only rarely deal with messages
386 var html = text.replace(/&/g, "&")
387 .replace(/</g, "<")
388 .replace(/>/g, ">")
389 .replace(/\n/g, "<br>");
391 html = "<font color=\"#df382c\">" + html + "</font>";
395 loader.item.showMessage(html);
403 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
407 onAuthenticationComplete: {
410 if (lightDM.greeter.authenticated) {
411 AccountsService.failedLogins = 0;
413 if (!lightDM.greeter.promptless) {
417 if (!lightDM.greeter.promptless) {
418 AccountsService.failedLogins++;
421 // Check if we should initiate a factory reset
422 if (maxFailedLogins >= 2) { // require at least a warning
423 if (AccountsService.failedLogins === maxFailedLogins - 1) {
424 loader.item.showLastChance();
425 } else if (AccountsService.failedLogins >= maxFailedLogins) {
426 SystemImage.factoryReset(); // Ouch!
430 // Check if we should initiate a forced login delay
431 if (failedLoginsDelayAttempts > 0
432 && AccountsService.failedLogins > 0
433 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
434 forcedDelayTimer.forceDelay();
437 loader.item.notifyAuthenticationFailed();
438 if (!lightDM.greeter.promptless) {
439 d.selectUser(d.currentIndex, false);
444 onRequestAuthenticationUser: {
445 // Find index for requested user, if it exists
446 for (var i = 0; i < lightDM.users.count; i++) {
447 if (user === lightDM.users.data(i, lightDM.userRoles.NameRole)) {
448 d.selectUser(i, true);
456 target: DBusUnitySessionService
457 onLockRequested: root.forceShow()
458 onUnlocked: root.forcedUnlock = true
462 target: lightDM.greeter
468 target: lightDM.infographic
470 value: AccountsService.statsWelcomeScreen ? lightDM.users.data(d.currentIndex, lightDM.userRoles.NameRole) : ""
475 onLanguageChanged: lightDM.infographic.readyForDataChange()