2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2021 UBports Foundation
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import AccountsService 0.1
23 import Ubuntu.Components 1.3
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
38 property real backgroundSourceSize
40 // How far to offset the top greeter layer during a launcher left-drag
41 property real launcherOffset
43 // How far down to position the greeter's interface to avoid the Panel
44 property real panelHeight
46 readonly property bool active: required || hasLockedApp
47 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
49 property bool allowFingerprint: true
51 // True when the greeter is waiting for PAM or other setup process
52 readonly property alias waiting: d.waiting
54 property string lockedApp: ""
55 readonly property bool hasLockedApp: lockedApp !== ""
57 property bool forcedUnlock
58 readonly property bool locked: LightDMService.greeter.active && !LightDMService.greeter.authenticated && !forcedUnlock
60 property bool tabletMode
61 property string usageMode
62 property url viewSource // only used for testing
64 property int failedLoginsDelayAttempts: 7 // number of failed logins
65 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
66 property int failedFingerprintLoginsDisableAttempts: 5 // number of failed fingerprint logins
67 property int failedFingerprintReaderRetryDelay: 250 // time to wait before retrying a failed fingerprint read [ms]
69 readonly property bool animating: loader.item ? loader.item.animating : false
71 property rect inputMethodRect
73 property bool hasKeyboard: false
74 property int orientation
77 signal sessionStarted()
78 signal emergencyCall()
80 function forceShow() {
82 d.isLockscreen = true;
87 loader.item.forceShow();
89 // Normally loader.onLoaded will select a user, but if we're
90 // already shown, do it manually.
91 d.selectUser(d.currentIndex);
94 // Even though we may already be shown, we want to call show() for its
95 // possible side effects, like hiding indicators and such.
97 // We re-check forcedUnlock here, because selectUser above might
98 // process events during authentication, and a request to unlock could
99 // have come in in the meantime.
105 function notifyAppFocusRequested(appId) {
111 if (appId === lockedApp) {
112 hide(); // show locked app
115 d.startUnlock(false /* toTheRight */);
118 d.startUnlock(false /* toTheRight */);
122 // Notify that the user has explicitly requested an app
123 function notifyUserRequestedApp() {
128 // A hint that we're about to focus an app. This way we can look
129 // a little more responsive, rather than waiting for the above
130 // notifyAppFocusRequested call. We also need this in case we have a locked
131 // app, in order to show lockscreen instead of new app.
132 d.startUnlock(false /* toTheRight */);
135 // This is a just a glorified notifyUserRequestedApp(), but it does one
136 // other thing: it hides any cover pages to the RIGHT, because the user
137 // just came from a launcher drag starting on the left.
138 // It also returns a boolean value, indicating whether there was a visual
139 // change or not (the shell only wants to hide the launcher if there was
141 function notifyShowingDashFromDrag() {
146 return d.startUnlock(true /* toTheRight */);
149 function sessionToStart() {
150 for (var i = 0; i < LightDMService.sessions.count; i++) {
151 var session = LightDMService.sessions.data(i,
152 LightDMService.sessionRoles.KeyRole);
153 if (loader.item.sessionToStart === session) {
158 return LightDMService.greeter.defaultSession;
164 readonly property bool multiUser: LightDMService.users.count > 1
165 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
166 property int currentIndex: Math.max(selectUserIndex, 0)
167 readonly property bool waiting: LightDMService.prompts.count == 0 && !root.forcedUnlock
168 property bool isLockscreen // true when we are locking an active session, rather than first user login
169 readonly property bool secureFingerprint: isLockscreen &&
170 AccountsService.failedFingerprintLogins <
171 root.failedFingerprintLoginsDisableAttempts
172 readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
174 // We want 'launcherOffset' to animate down to zero. But not to animate
175 // while being dragged. So ideally we change this only when the user
176 // lets go and launcherOffset drops to zero. But we need to wait for
177 // the behavior to be enabled first. So we cache the last known good
178 // launcherOffset value to cover us during that brief gap between
179 // release and the behavior turning on.
180 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
181 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
182 Behavior on launcherOffsetProxy {
183 id: launcherOffsetProxyBehavior
184 enabled: launcherOffset === 0
185 UbuntuNumberAnimation {}
188 function getUserIndex(username) {
192 // Find index for requested user, if it exists
193 for (var i = 0; i < LightDMService.users.count; i++) {
194 if (username === LightDMService.users.data(i, LightDMService.userRoles.NameRole)) {
202 function selectUser(index) {
203 if (index < 0 || index >= LightDMService.users.count)
205 currentIndex = index;
206 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
207 AccountsService.user = user;
208 LauncherModel.setUser(user);
209 LightDMService.greeter.authenticate(user); // always resets auth state
212 function hideView() {
214 loader.item.enabled = false; // drop OSK and prevent interaction
220 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
223 } else if (loader.item) {
224 loader.item.notifyAuthenticationFailed();
228 function startUnlock(toTheRight) {
230 return loader.item.tryToUnlock(toTheRight);
236 function checkForcedUnlock(hideNow) {
237 if (forcedUnlock && shown) {
240 root.hideNow(); // skip hide animation
245 function showFingerprintMessage(msg) {
246 d.selectUser(d.currentIndex);
247 LightDMService.prompts.prepend(msg, LightDMService.prompts.Error);
249 loader.item.showErrorMessage(msg);
250 loader.item.notifyAuthenticationFailed();
255 onLauncherOffsetChanged: {
256 if (launcherOffset > 0) {
257 d.lastKnownPositiveOffset = launcherOffset;
261 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
262 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
266 AccountsService.failedLogins = 0;
267 AccountsService.failedFingerprintLogins = 0;
269 // Stop delay timer if they logged in with fingerprint
270 forcedDelayTimer.stop();
271 forcedDelayTimer.delayMinutes = 0;
283 schema.id: "com.canonical.Unity8.Greeter"
289 // We use a short interval and check against the system wall clock
290 // because we have to consider the case that the system is suspended
291 // for a few minutes. When we wake up, we want to quickly be correct.
294 property var delayTarget
295 property int delayMinutes
297 function forceDelay() {
298 // Store the beginning time for a lockout in GSettings, so that
299 // we still lock the user out if they reboot. And we store
300 // starting time rather than end-time or how-long because:
301 // - If storing end-time and on boot we have a problem with NTP,
302 // we might get locked out for a lot longer than we thought.
303 // - If storing how-long, and user turns their phone off for an
304 // hour rather than wait, they wouldn't expect to still be locked
306 // - A malicious actor could manipulate either of the above
307 // settings to keep the user out longer. But by storing
308 // start-time, we never make the user wait longer than the full
310 greeterSettings.lockedOutTime = new Date().getTime();
311 checkForForcedDelay();
315 var diff = delayTarget - new Date();
317 delayMinutes = Math.ceil(diff / 60000);
324 function checkForForcedDelay() {
325 if (greeterSettings.lockedOutTime === 0) {
329 var now = new Date();
330 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
332 // If tooEarly is true, something went very wrong. Bug or NTP
333 // misconfiguration maybe?
334 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
335 var tooLate = now >= delayTarget;
337 // Compare stored time to system time. If a malicious actor is
338 // able to manipulate time to avoid our lockout, they already have
339 // enough access to cause damage. So we choose to trust this check.
340 if (tooEarly || tooLate) {
348 Component.onCompleted: checkForForcedDelay()
352 // Nothing should leak to items behind the greeter
353 MouseArea { anchors.fill: parent; hoverEnabled: true }
361 active: root.required
362 source: root.viewSource.toString() ? root.viewSource : "GreeterView.qml"
366 item.forceActiveFocus();
367 d.selectUser(d.currentIndex);
368 LightDMService.infographic.readyForDataChange();
378 LightDMService.greeter.respond(response);
383 onTease: root.tease()
384 onEmergencyCall: root.emergencyCall()
386 if (!loader.item.required) {
394 property: "panelHeight"
395 value: root.panelHeight
400 property: "launcherOffset"
401 value: d.launcherOffsetProxy
406 property: "dragHandleLeftMargin"
407 value: root.dragHandleLeftMargin
412 property: "delayMinutes"
413 value: forcedDelayTimer.delayMinutes
418 property: "background"
419 value: root.background
424 property: "backgroundSourceSize"
425 value: root.backgroundSourceSize
430 property: "hasCustomBackground"
431 value: root.hasCustomBackground
448 property: "alphanumeric"
449 value: d.alphanumeric
454 property: "currentIndex"
455 value: d.currentIndex
460 property: "userModel"
461 value: LightDMService.users
466 property: "infographicModel"
467 value: LightDMService.infographic
472 property: "inputMethodRect"
473 value: root.inputMethodRect
478 property: "hasKeyboard"
479 value: root.hasKeyboard
484 property: "usageMode"
485 value: root.usageMode
490 property: "multiUser"
496 property: "orientation"
497 value: root.orientation
502 target: LightDMService.greeter
504 onShowGreeter: root.forceShow()
505 onHideGreeter: root.forcedUnlock = true
512 loader.item.notifyAuthenticationFailed();
515 AccountsService.failedLogins++;
517 // Check if we should initiate a forced login delay
518 if (failedLoginsDelayAttempts > 0
519 && AccountsService.failedLogins > 0
520 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
521 forcedDelayTimer.forceDelay();
524 d.selectUser(d.currentIndex);
534 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
538 target: DBusUnitySessionService
539 onLockRequested: root.forceShow()
541 root.forcedUnlock = true;
547 target: LightDMService.greeter
553 target: LightDMService.infographic
555 value: AccountsService.statsWelcomeScreen ? LightDMService.users.data(d.currentIndex, LightDMService.userRoles.NameRole) : ""
560 onLanguageChanged: LightDMService.infographic.readyForDataChange()
567 onTriggered: biometryd.startOperation()
568 interval: failedFingerprintReaderRetryDelay
573 objectName: "biometryd"
575 property var operation: null
576 readonly property bool idEnabled: root.active &&
577 root.allowFingerprint &&
578 Powerd.status === Powerd.On &&
579 Biometryd.available &&
580 AccountsService.enableFingerprintIdentification
582 function startOperation() {
584 var identifier = Biometryd.defaultDevice.identifier;
585 operation = identifier.identifyUser();
586 operation.start(biometryd);
590 function cancelOperation() {
597 function restartOperation() {
599 if (failedFingerprintReaderRetryDelay > 0) {
600 fpRetryTimer.running = true;
606 function failOperation(reason) {
607 console.log("Failed to identify user by fingerprint:", reason);
609 var msg = d.secureFingerprint ? i18n.tr("Try again") :
610 d.alphanumeric ? i18n.tr("Enter passphrase to unlock") :
611 i18n.tr("Enter passcode to unlock");
612 d.showFingerprintMessage(msg);
615 Component.onCompleted: startOperation()
616 Component.onDestruction: cancelOperation()
617 onIdEnabledChanged: restartOperation()
620 if (!d.secureFingerprint) {
621 failOperation("fingerprint reader is locked");
624 if (result !== LightDMService.users.data(d.currentIndex, LightDMService.userRoles.UidRole)) {
625 AccountsService.failedFingerprintLogins++;
626 failOperation("not the selected user");
629 console.log("Identified user by fingerprint:", result);
631 loader.item.showFakePassword();
634 root.forcedUnlock = true;
637 if (!d.secureFingerprint) {
638 failOperation("fingerprint reader is locked");
639 } else if (reason !== "ERROR_CANCELED") {
640 AccountsService.failedFingerprintLogins++;
641 failOperation(reason);