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 Unity.Launcher 0.1
24 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 failedLoginsDelayAttempts: 7 // number of failed logins
60 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
61 property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
63 readonly property bool animating: loader.item ? loader.item.animating : false
66 signal sessionStarted()
67 signal emergencyCall()
69 function forceShow() {
71 d.isLockscreen = true;
76 loader.item.forceShow();
78 // Normally loader.onLoaded will select a user, but if we're
79 // already shown, do it manually.
80 d.selectUser(d.currentIndex);
83 // Even though we may already be shown, we want to call show() for its
84 // possible side effects, like hiding indicators and such.
86 // We re-check forcedUnlock here, because selectUser above might
87 // process events during authentication, and a request to unlock could
88 // have come in in the meantime.
94 function notifyAppFocusRequested(appId) {
100 if (appId === lockedApp) {
101 hide(); // show locked app
104 d.startUnlock(false /* toTheRight */);
107 d.startUnlock(false /* toTheRight */);
111 // Notify that the user has explicitly requested an app
112 function notifyUserRequestedApp() {
117 // A hint that we're about to focus an app. This way we can look
118 // a little more responsive, rather than waiting for the above
119 // notifyAppFocusRequested call. We also need this in case we have a locked
120 // app, in order to show lockscreen instead of new app.
121 d.startUnlock(false /* toTheRight */);
124 // This is a just a glorified notifyUserRequestedApp(), but it does one
125 // other thing: it hides any cover pages to the RIGHT, because the user
126 // just came from a launcher drag starting on the left.
127 // It also returns a boolean value, indicating whether there was a visual
128 // change or not (the shell only wants to hide the launcher if there was
130 function notifyShowingDashFromDrag() {
135 return d.startUnlock(true /* toTheRight */);
138 function sessionToStart() {
139 for (var i = 0; i < LightDMService.sessions.count; i++) {
140 var session = LightDMService.sessions.data(i,
141 LightDMService.sessionRoles.KeyRole);
142 if (loader.item.sessionToStart === session) {
147 return LightDMService.greeter.defaultSession;
153 readonly property bool multiUser: LightDMService.users.count > 1
154 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
155 property int currentIndex: Math.max(selectUserIndex, 0)
156 readonly property bool waiting: LightDMService.prompts.count == 0 && !root.forcedUnlock
157 property bool isLockscreen // true when we are locking an active session, rather than first user login
158 readonly property bool secureFingerprint: isLockscreen &&
159 AccountsService.failedFingerprintLogins <
160 root.failedFingerprintLoginsDisableAttempts
161 readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
163 // We want 'launcherOffset' to animate down to zero. But not to animate
164 // while being dragged. So ideally we change this only when the user
165 // lets go and launcherOffset drops to zero. But we need to wait for
166 // the behavior to be enabled first. So we cache the last known good
167 // launcherOffset value to cover us during that brief gap between
168 // release and the behavior turning on.
169 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
170 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
171 Behavior on launcherOffsetProxy {
172 id: launcherOffsetProxyBehavior
173 enabled: launcherOffset === 0
174 UbuntuNumberAnimation {}
177 function getUserIndex(username) {
181 // Find index for requested user, if it exists
182 for (var i = 0; i < LightDMService.users.count; i++) {
183 if (username === LightDMService.users.data(i, LightDMService.userRoles.NameRole)) {
191 function selectUser(index) {
192 if (index < 0 || index >= LightDMService.users.count)
194 currentIndex = index;
195 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
196 AccountsService.user = user;
197 LauncherModel.setUser(user);
198 LightDMService.greeter.authenticate(user); // always resets auth state
201 function hideView() {
203 loader.item.enabled = false; // drop OSK and prevent interaction
209 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
212 } else if (loader.item) {
213 loader.item.notifyAuthenticationFailed();
217 function startUnlock(toTheRight) {
219 return loader.item.tryToUnlock(toTheRight);
225 function checkForcedUnlock(hideNow) {
226 if (forcedUnlock && shown) {
229 ShellNotifier.greeter.hide(true); // skip hide animation
234 function showFingerprintMessage(msg) {
235 d.selectUser(d.currentIndex);
236 LightDMService.prompts.prepend(msg, LightDMService.prompts.Error);
238 loader.item.showErrorMessage(msg);
239 loader.item.notifyAuthenticationFailed();
244 onLauncherOffsetChanged: {
245 if (launcherOffset > 0) {
246 d.lastKnownPositiveOffset = launcherOffset;
250 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
251 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
255 AccountsService.failedLogins = 0;
256 AccountsService.failedFingerprintLogins = 0;
258 // Stop delay timer if they logged in with fingerprint
259 forcedDelayTimer.stop();
260 forcedDelayTimer.delayMinutes = 0;
272 schema.id: "com.canonical.Unity8.Greeter"
278 // We use a short interval and check against the system wall clock
279 // because we have to consider the case that the system is suspended
280 // for a few minutes. When we wake up, we want to quickly be correct.
283 property var delayTarget
284 property int delayMinutes
286 function forceDelay() {
287 // Store the beginning time for a lockout in GSettings, so that
288 // we still lock the user out if they reboot. And we store
289 // starting time rather than end-time or how-long because:
290 // - If storing end-time and on boot we have a problem with NTP,
291 // we might get locked out for a lot longer than we thought.
292 // - If storing how-long, and user turns their phone off for an
293 // hour rather than wait, they wouldn't expect to still be locked
295 // - A malicious actor could manipulate either of the above
296 // settings to keep the user out longer. But by storing
297 // start-time, we never make the user wait longer than the full
299 greeterSettings.lockedOutTime = new Date().getTime();
300 checkForForcedDelay();
304 var diff = delayTarget - new Date();
306 delayMinutes = Math.ceil(diff / 60000);
313 function checkForForcedDelay() {
314 if (greeterSettings.lockedOutTime === 0) {
318 var now = new Date();
319 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
321 // If tooEarly is true, something went very wrong. Bug or NTP
322 // misconfiguration maybe?
323 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
324 var tooLate = now >= delayTarget;
326 // Compare stored time to system time. If a malicious actor is
327 // able to manipulate time to avoid our lockout, they already have
328 // enough access to cause damage. So we choose to trust this check.
329 if (tooEarly || tooLate) {
337 Component.onCompleted: checkForForcedDelay()
341 // Nothing should leak to items behind the greeter
342 MouseArea { anchors.fill: parent; hoverEnabled: true }
350 active: root.required
351 source: root.viewSource.toString() ? root.viewSource :
352 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
356 item.forceActiveFocus();
357 d.selectUser(d.currentIndex);
358 LightDMService.infographic.readyForDataChange();
368 LightDMService.greeter.respond(response);
373 onTease: root.tease()
374 onEmergencyCall: root.emergencyCall()
376 if (!loader.item.required) {
377 ShellNotifier.greeter.hide(false);
384 property: "backgroundTopMargin"
390 property: "launcherOffset"
391 value: d.launcherOffsetProxy
396 property: "dragHandleLeftMargin"
397 value: root.dragHandleLeftMargin
402 property: "delayMinutes"
403 value: forcedDelayTimer.delayMinutes
408 property: "background"
409 value: root.background
414 property: "hasCustomBackground"
415 value: root.hasCustomBackground
432 property: "alphanumeric"
433 value: d.alphanumeric
438 property: "currentIndex"
439 value: d.currentIndex
444 property: "userModel"
445 value: LightDMService.users
450 property: "infographicModel"
451 value: LightDMService.infographic
456 target: LightDMService.greeter
458 onShowGreeter: root.forceShow()
459 onHideGreeter: root.forcedUnlock = true
466 loader.item.notifyAuthenticationFailed();
469 AccountsService.failedLogins++;
471 // Check if we should initiate a forced login delay
472 if (failedLoginsDelayAttempts > 0
473 && AccountsService.failedLogins > 0
474 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
475 forcedDelayTimer.forceDelay();
478 d.selectUser(d.currentIndex);
488 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
492 target: ShellNotifier.greeter
495 root.hideNow(); // skip hide animation
503 target: ShellNotifier.greeter
509 target: DBusUnitySessionService
510 onLockRequested: root.forceShow()
512 root.forcedUnlock = true;
513 ShellNotifier.greeter.hide(true);
518 target: LightDMService.greeter
524 target: LightDMService.infographic
526 value: AccountsService.statsWelcomeScreen ? LightDMService.users.data(d.currentIndex, LightDMService.userRoles.NameRole) : ""
531 onLanguageChanged: LightDMService.infographic.readyForDataChange()
536 objectName: "biometryd"
538 property var operation: null
539 readonly property bool idEnabled: root.active &&
540 root.allowFingerprint &&
541 Powerd.status === Powerd.On &&
542 Biometryd.available &&
543 AccountsService.enableFingerprintIdentification
545 function cancelOperation() {
552 function restartOperation() {
556 var identifier = Biometryd.defaultDevice.identifier;
557 operation = identifier.identifyUser();
558 operation.start(biometryd);
562 function failOperation(reason) {
563 console.log("Failed to identify user by fingerprint:", reason);
565 if (!d.secureFingerprint) {
566 d.startUnlock(false /* toTheRight */); // use normal login instead
568 var msg = d.secureFingerprint ? i18n.tr("Try again") :
569 d.alphanumeric ? i18n.tr("Enter passphrase to unlock") :
570 i18n.tr("Enter passcode to unlock");
571 d.showFingerprintMessage(msg);
574 Component.onCompleted: restartOperation()
575 Component.onDestruction: cancelOperation()
576 onIdEnabledChanged: restartOperation()
579 if (!d.secureFingerprint) {
580 failOperation("fingerprint reader is locked");
583 if (result !== LightDMService.users.data(d.currentIndex, LightDMService.userRoles.UidRole)) {
584 AccountsService.failedFingerprintLogins++;
585 failOperation("not the selected user");
588 console.log("Identified user by fingerprint:", result);
590 loader.item.showFakePassword();
593 root.forcedUnlock = true;
596 if (!d.secureFingerprint) {
597 failOperation("fingerprint reader is locked");
599 AccountsService.failedFingerprintLogins++;
600 failOperation(reason);