2 * Copyright (C) 2013-2016 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
22 import Ubuntu.Components 1.3
23 import Ubuntu.SystemImage 0.1
24 import Unity.Launcher 0.1
25 import Unity.Session 0.1
28 import "../Components"
32 created: loader.status == Loader.Ready
34 property real dragHandleLeftMargin: 0
36 property url background
37 property bool hasCustomBackground
39 // How far to offset the top greeter layer during a launcher left-drag
40 property real launcherOffset
42 readonly property bool active: required || hasLockedApp
43 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
45 property bool allowFingerprint: true
47 // True when the greeter is waiting for PAM or other setup process
48 readonly property alias waiting: d.waiting
50 property string lockedApp: ""
51 readonly property bool hasLockedApp: lockedApp !== ""
53 property bool forcedUnlock
54 readonly property bool locked: LightDMService.greeter.active && !LightDMService.greeter.authenticated && !forcedUnlock
56 property bool tabletMode
57 property url viewSource // only used for testing
59 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
60 property int failedLoginsDelayAttempts: 7 // number of failed logins
61 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
62 property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
64 readonly property bool animating: loader.item ? loader.item.animating : false
67 signal sessionStarted()
68 signal emergencyCall()
70 function forceShow() {
72 d.isLockscreen = true;
77 loader.item.reset(true /* forceShow */);
79 // Normally loader.onLoaded will select a user, but if we're
80 // already shown, do it manually.
81 d.selectUser(d.currentIndex, false);
84 // Even though we may already be shown, we want to call show() for its
85 // possible side effects, like hiding indicators and such.
87 // We re-check forcedUnlock here, because selectUser above might
88 // process events during authentication, and a request to unlock could
89 // have come in in the meantime.
95 function notifyAppFocusRequested(appId) {
101 if (appId === lockedApp) {
102 hide(); // show locked app
105 d.startUnlock(false /* toTheRight */);
107 } else if (appId !== "unity8-dash") { // dash isn't started by user
108 d.startUnlock(false /* toTheRight */);
112 // Notify that the user has explicitly requested an app
113 function notifyUserRequestedApp() {
118 // A hint that we're about to focus an app. This way we can look
119 // a little more responsive, rather than waiting for the above
120 // notifyAppFocusRequested call. We also need this in case we have a locked
121 // app, in order to show lockscreen instead of new app.
122 d.startUnlock(false /* toTheRight */);
125 // This is a just a glorified notifyUserRequestedApp(), but it does one
126 // other thing: it hides any cover pages to the RIGHT, because the user
127 // just came from a launcher drag starting on the left.
128 // It also returns a boolean value, indicating whether there was a visual
129 // change or not (the shell only wants to hide the launcher if there was
131 function notifyShowingDashFromDrag() {
136 return d.startUnlock(true /* toTheRight */);
139 function sessionToStart() {
140 for (var i = 0; i < LightDMService.sessions.count; i++) {
141 var session = LightDMService.sessions.data(i,
142 LightDMService.sessionRoles.KeyRole);
143 if (loader.item.sessionToStart === session) {
148 if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
149 return LightDMService.greeter.defaultSession;
151 return "ubuntu"; // The default / fallback
158 readonly property bool multiUser: LightDMService.users.count > 1
159 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
160 property int currentIndex: Math.max(selectUserIndex, 0)
161 property bool waiting
162 property bool isLockscreen // true when we are locking an active session, rather than first user login
163 readonly property bool secureFingerprint: isLockscreen &&
164 AccountsService.failedFingerprintLogins <
165 root.failedFingerprintLoginsDisableAttempts
166 readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
168 // We want 'launcherOffset' to animate down to zero. But not to animate
169 // while being dragged. So ideally we change this only when the user
170 // lets go and launcherOffset drops to zero. But we need to wait for
171 // the behavior to be enabled first. So we cache the last known good
172 // launcherOffset value to cover us during that brief gap between
173 // release and the behavior turning on.
174 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
175 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
176 Behavior on launcherOffsetProxy {
177 id: launcherOffsetProxyBehavior
178 enabled: launcherOffset === 0
179 UbuntuNumberAnimation {}
182 function getUserIndex(username) {
186 // Find index for requested user, if it exists
187 for (var i = 0; i < LightDMService.users.count; i++) {
188 if (username === LightDMService.users.data(i, LightDMService.userRoles.NameRole)) {
196 function selectUser(index, reset) {
197 if (index < 0 || index >= LightDMService.users.count)
201 loader.item.reset(false /* forceShow */);
203 currentIndex = index;
204 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
205 AccountsService.user = user;
206 LauncherModel.setUser(user);
207 LightDMService.greeter.authenticate(user); // always resets auth state
210 function hideView() {
212 loader.item.enabled = false; // drop OSK and prevent interaction
213 loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
220 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
223 } else if (loader.item) {
224 loader.item.notifyAuthenticationFailed();
229 function startUnlock(toTheRight) {
231 return loader.item.tryToUnlock(toTheRight);
237 function checkForcedUnlock(hideNow) {
238 if (forcedUnlock && shown) {
241 root.hideNow(); // skip hide animation
246 function showPromptMessage(text, isError) {
247 // inefficient, but we only rarely deal with messages
248 var html = text.replace(/&/g, "&")
249 .replace(/</g, "<")
250 .replace(/>/g, ">")
251 .replace(/\n/g, "<br>");
253 html = "<font color=\"#df382c\">" + html + "</font>";
257 loader.item.showMessage(html);
261 function showFingerprintMessage(msg) {
263 loader.item.reset(false /* forceShow */);
264 loader.item.showErrorMessage(msg);
266 showPromptMessage(msg, true);
270 onLauncherOffsetChanged: {
271 if (launcherOffset > 0) {
272 d.lastKnownPositiveOffset = launcherOffset;
276 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
277 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
281 AccountsService.failedLogins = 0;
282 AccountsService.failedFingerprintLogins = 0;
284 // Stop delay timer if they logged in with fingerprint
285 forcedDelayTimer.stop();
286 forcedDelayTimer.delayMinutes = 0;
299 schema.id: "com.canonical.Unity8.Greeter"
305 // We use a short interval and check against the system wall clock
306 // because we have to consider the case that the system is suspended
307 // for a few minutes. When we wake up, we want to quickly be correct.
310 property var delayTarget
311 property int delayMinutes
313 function forceDelay() {
314 // Store the beginning time for a lockout in GSettings, so that
315 // we still lock the user out if they reboot. And we store
316 // starting time rather than end-time or how-long because:
317 // - If storing end-time and on boot we have a problem with NTP,
318 // we might get locked out for a lot longer than we thought.
319 // - If storing how-long, and user turns their phone off for an
320 // hour rather than wait, they wouldn't expect to still be locked
322 // - A malicious actor could manipulate either of the above
323 // settings to keep the user out longer. But by storing
324 // start-time, we never make the user wait longer than the full
326 greeterSettings.lockedOutTime = new Date().getTime();
327 checkForForcedDelay();
331 var diff = delayTarget - new Date();
333 delayMinutes = Math.ceil(diff / 60000);
340 function checkForForcedDelay() {
341 if (greeterSettings.lockedOutTime === 0) {
345 var now = new Date();
346 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
348 // If tooEarly is true, something went very wrong. Bug or NTP
349 // misconfiguration maybe?
350 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
351 var tooLate = now >= delayTarget;
353 // Compare stored time to system time. If a malicious actor is
354 // able to manipulate time to avoid our lockout, they already have
355 // enough access to cause damage. So we choose to trust this check.
356 if (tooEarly || tooLate) {
364 Component.onCompleted: checkForForcedDelay()
368 // Nothing should leak to items behind the greeter
369 MouseArea { anchors.fill: parent; hoverEnabled: true }
377 active: root.required
378 source: root.viewSource.toString() ? root.viewSource :
379 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
383 item.forceActiveFocus();
384 d.selectUser(d.currentIndex, true);
385 LightDMService.infographic.readyForDataChange();
391 d.selectUser(index, true);
395 LightDMService.greeter.respond(response);
400 onTease: root.tease()
401 onEmergencyCall: root.emergencyCall()
403 if (!loader.item.required) {
411 property: "backgroundTopMargin"
417 property: "launcherOffset"
418 value: d.launcherOffsetProxy
423 property: "dragHandleLeftMargin"
424 value: root.dragHandleLeftMargin
429 property: "delayMinutes"
430 value: forcedDelayTimer.delayMinutes
435 property: "background"
436 value: root.background
441 property: "hasCustomBackground"
442 value: root.hasCustomBackground
459 property: "alphanumeric"
460 value: d.alphanumeric
465 property: "currentIndex"
466 value: d.currentIndex
471 property: "userModel"
472 value: LightDMService.users
477 property: "infographicModel"
478 value: LightDMService.infographic
483 target: LightDMService.greeter
485 onShowGreeter: root.forceShow()
486 onHideGreeter: root.forcedUnlock = true
488 onShowMessage: d.showPromptMessage(text, isError)
492 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
498 onAuthenticationComplete: {
501 if (LightDMService.greeter.authenticated) {
502 if (!LightDMService.greeter.promptless) {
506 if (!LightDMService.greeter.promptless) {
507 AccountsService.failedLogins++;
510 // Check if we should initiate a factory reset
511 if (maxFailedLogins >= 2) { // require at least a warning
512 if (AccountsService.failedLogins === maxFailedLogins - 1) {
513 loader.item.showLastChance();
514 } else if (AccountsService.failedLogins >= maxFailedLogins) {
515 SystemImage.factoryReset(); // Ouch!
519 // Check if we should initiate a forced login delay
520 if (failedLoginsDelayAttempts > 0
521 && AccountsService.failedLogins > 0
522 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
523 forcedDelayTimer.forceDelay();
526 loader.item.notifyAuthenticationFailed();
527 if (!LightDMService.greeter.promptless) {
528 d.selectUser(d.currentIndex, false);
533 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
537 target: DBusUnitySessionService
538 onLockRequested: root.forceShow()
540 root.forcedUnlock = true;
546 target: LightDMService.greeter
552 target: LightDMService.infographic
554 value: AccountsService.statsWelcomeScreen ? LightDMService.users.data(d.currentIndex, LightDMService.userRoles.NameRole) : ""
559 onLanguageChanged: LightDMService.infographic.readyForDataChange()
564 objectName: "biometryd"
566 property var operation: null
567 readonly property bool idEnabled: root.active &&
568 root.allowFingerprint &&
569 Powerd.status === Powerd.On &&
570 Biometryd.available &&
571 AccountsService.enableFingerprintIdentification
573 function cancelOperation() {
580 function restartOperation() {
584 var identifier = Biometryd.defaultDevice.identifier;
585 operation = identifier.identifyUser();
586 operation.start(biometryd);
590 function failOperation(reason) {
591 console.log("Failed to identify user by fingerprint:", reason);
593 if (!d.secureFingerprint) {
594 d.startUnlock(false /* toTheRight */); // use normal login instead
596 var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
597 d.showFingerprintMessage(msg);
600 Component.onCompleted: restartOperation()
601 Component.onDestruction: cancelOperation()
602 onIdEnabledChanged: restartOperation()
605 if (!d.secureFingerprint) {
606 failOperation("fingerprint reader is locked");
609 if (result !== LightDMService.users.data(d.currentIndex, LightDMService.userRoles.UidRole)) {
610 AccountsService.failedFingerprintLogins++;
611 failOperation("not the selected user");
614 console.log("Identified user by fingerprint:", result);
616 loader.item.enabled = false;
617 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
620 root.forcedUnlock = true;
623 if (!d.secureFingerprint) {
624 failOperation("fingerprint reader is locked");
626 AccountsService.failedFingerprintLogins++;
627 failOperation(reason);