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/>.
18import AccountsService 0.1
22import Ubuntu.Components 1.3
23import Ubuntu.SystemImage 0.1
24import Unity.Launcher 0.1
25import Unity.Session 0.1
32 created: loader.status == Loader.Ready
34 property real dragHandleLeftMargin: 0
36 property url background
38 // How far to offset the top greeter layer during a launcher left-drag
39 property real launcherOffset
41 readonly property bool active: required || hasLockedApp
42 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
44 property bool allowFingerprint: true
46 // True when the greeter is waiting for PAM or other setup process
47 readonly property alias waiting: d.waiting
49 property string lockedApp: ""
50 readonly property bool hasLockedApp: lockedApp !== ""
52 property bool forcedUnlock
53 readonly property bool locked: LightDMService.greeter.active && !LightDMService.greeter.authenticated && !forcedUnlock
55 property bool tabletMode
56 property url viewSource // only used for testing
58 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
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;
75 // Normally loader.onLoaded will select a user, but if we're
76 // already shown, do it manually.
77 d.selectUser(d.currentIndex, true);
80 // Even though we may already be shown, we want to call show() for its
81 // possible side effects, like hiding indicators and such.
83 // We re-check forcedUnlock here, because selectUser above might
84 // process events during authentication, and a request to unlock could
85 // have come in in the meantime.
91 function notifyAppFocusRequested(appId) {
97 if (appId === lockedApp) {
98 hide(); // show locked app
101 d.startUnlock(false /* toTheRight */);
103 } else if (appId !== "unity8-dash") { // dash isn't started by user
104 d.startUnlock(false /* toTheRight */);
108 // Notify that the user has explicitly requested the given app through unity8 GUI.
109 function notifyUserRequestedApp(appId) {
114 // A hint that we're about to focus an app. This way we can look
115 // a little more responsive, rather than waiting for the above
116 // notifyAppFocusRequested call. We also need this in case we have a locked
117 // app, in order to show lockscreen instead of new app.
118 d.startUnlock(false /* toTheRight */);
121 // This is a just a glorified notifyUserRequestedApp(), but it does one
122 // other thing: it hides any cover pages to the RIGHT, because the user
123 // just came from a launcher drag starting on the left.
124 // It also returns a boolean value, indicating whether there was a visual
125 // change or not (the shell only wants to hide the launcher if there was
127 function notifyShowingDashFromDrag() {
132 return d.startUnlock(true /* toTheRight */);
138 readonly property bool multiUser: LightDMService.users.count > 1
139 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
140 property int currentIndex: Math.max(selectUserIndex, 0)
141 property bool waiting
142 property bool isLockscreen // true when we are locking an active session, rather than first user login
143 readonly property bool secureFingerprint: isLockscreen &&
144 AccountsService.failedFingerprintLogins <
145 root.failedFingerprintLoginsDisableAttempts
146 readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
148 // We want 'launcherOffset' to animate down to zero. But not to animate
149 // while being dragged. So ideally we change this only when the user
150 // lets go and launcherOffset drops to zero. But we need to wait for
151 // the behavior to be enabled first. So we cache the last known good
152 // launcherOffset value to cover us during that brief gap between
153 // release and the behavior turning on.
154 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
155 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
156 Behavior on launcherOffsetProxy {
157 id: launcherOffsetProxyBehavior
158 enabled: launcherOffset === 0
159 UbuntuNumberAnimation {}
162 function getUserIndex(username) {
166 // Find index for requested user, if it exists
167 for (var i = 0; i < LightDMService.users.count; i++) {
168 if (username === LightDMService.users.data(i, LightDMService.userRoles.NameRole)) {
176 function selectUser(index, reset) {
177 if (index < 0 || index >= LightDMService.users.count)
183 currentIndex = index;
184 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
185 AccountsService.user = user;
186 LauncherModel.setUser(user);
187 LightDMService.greeter.authenticate(user); // always resets auth state
190 function hideView() {
192 loader.item.enabled = false; // drop OSK and prevent interaction
193 loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
200 if (LightDMService.greeter.startSessionSync()) {
203 } else if (loader.item) {
204 loader.item.notifyAuthenticationFailed();
209 function startUnlock(toTheRight) {
211 return loader.item.tryToUnlock(toTheRight);
217 function checkForcedUnlock(hideNow) {
218 if (forcedUnlock && shown) {
221 root.hideNow(); // skip hide animation
227 onLauncherOffsetChanged: {
228 if (launcherOffset > 0) {
229 d.lastKnownPositiveOffset = launcherOffset;
233 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
234 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
238 AccountsService.failedLogins = 0;
239 AccountsService.failedFingerprintLogins = 0;
241 // Stop delay timer if they logged in with fingerprint
242 forcedDelayTimer.stop();
243 forcedDelayTimer.delayMinutes = 0;
256 schema.id: "com.canonical.Unity8.Greeter"
262 // We use a short interval and check against the system wall clock
263 // because we have to consider the case that the system is suspended
264 // for a few minutes. When we wake up, we want to quickly be correct.
267 property var delayTarget
268 property int delayMinutes
270 function forceDelay() {
271 // Store the beginning time for a lockout in GSettings, so that
272 // we still lock the user out if they reboot. And we store
273 // starting time rather than end-time or how-long because:
274 // - If storing end-time and on boot we have a problem with NTP,
275 // we might get locked out for a lot longer than we thought.
276 // - If storing how-long, and user turns their phone off for an
277 // hour rather than wait, they wouldn't expect to still be locked
279 // - A malicious actor could manipulate either of the above
280 // settings to keep the user out longer. But by storing
281 // start-time, we never make the user wait longer than the full
283 greeterSettings.lockedOutTime = new Date().getTime();
284 checkForForcedDelay();
288 var diff = delayTarget - new Date();
290 delayMinutes = Math.ceil(diff / 60000);
297 function checkForForcedDelay() {
298 if (greeterSettings.lockedOutTime === 0) {
302 var now = new Date();
303 delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
305 // If tooEarly is true, something went very wrong. Bug or NTP
306 // misconfiguration maybe?
307 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
308 var tooLate = now >= delayTarget;
310 // Compare stored time to system time. If a malicious actor is
311 // able to manipulate time to avoid our lockout, they already have
312 // enough access to cause damage. So we choose to trust this check.
313 if (tooEarly || tooLate) {
321 Component.onCompleted: checkForForcedDelay()
325 // Nothing should leak to items behind the greeter
326 MouseArea { anchors.fill: parent; hoverEnabled: true }
334 active: root.required
335 source: root.viewSource.toString() ? root.viewSource :
336 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
340 item.forceActiveFocus();
341 d.selectUser(d.currentIndex, true);
342 LightDMService.infographic.readyForDataChange();
348 d.selectUser(index, true);
352 LightDMService.greeter.respond(response);
357 onTease: root.tease()
358 onEmergencyCall: root.emergencyCall()
360 if (!loader.item.required) {
368 property: "backgroundTopMargin"
374 property: "launcherOffset"
375 value: d.launcherOffsetProxy
380 property: "dragHandleLeftMargin"
381 value: root.dragHandleLeftMargin
386 property: "delayMinutes"
387 value: forcedDelayTimer.delayMinutes
392 property: "background"
393 value: root.background
410 property: "alphanumeric"
411 value: d.alphanumeric
416 property: "currentIndex"
417 value: d.currentIndex
422 property: "userModel"
423 value: LightDMService.users
428 property: "infographicModel"
429 value: LightDMService.infographic
434 target: LightDMService.greeter
436 onShowGreeter: root.forceShow()
437 onHideGreeter: root.forcedUnlock = true
440 // inefficient, but we only rarely deal with messages
441 var html = text.replace(/&/g, "&")
442 .replace(/</g, "<")
443 .replace(/>/g, ">")
444 .replace(/\n/g, "<br>");
446 html = "<font color=\"#df382c\">" + html + "</font>";
450 loader.item.showMessage(html);
456 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
462 onAuthenticationComplete: {
465 if (LightDMService.greeter.authenticated) {
466 if (!LightDMService.greeter.promptless) {
470 if (!LightDMService.greeter.promptless) {
471 AccountsService.failedLogins++;
474 // Check if we should initiate a factory reset
475 if (maxFailedLogins >= 2) { // require at least a warning
476 if (AccountsService.failedLogins === maxFailedLogins - 1) {
477 loader.item.showLastChance();
478 } else if (AccountsService.failedLogins >= maxFailedLogins) {
479 SystemImage.factoryReset(); // Ouch!
483 // Check if we should initiate a forced login delay
484 if (failedLoginsDelayAttempts > 0
485 && AccountsService.failedLogins > 0
486 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
487 forcedDelayTimer.forceDelay();
490 loader.item.notifyAuthenticationFailed();
491 if (!LightDMService.greeter.promptless) {
492 d.selectUser(d.currentIndex, false);
497 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
501 target: DBusUnitySessionService
502 onLockRequested: root.forceShow()
504 root.forcedUnlock = true;
510 target: LightDMService.greeter
516 target: LightDMService.infographic
518 value: AccountsService.statsWelcomeScreen ? LightDMService.users.data(d.currentIndex, LightDMService.userRoles.NameRole) : ""
523 onLanguageChanged: LightDMService.infographic.readyForDataChange()
528 objectName: "biometryd"
530 property var operation: null
531 readonly property bool idEnabled: root.active &&
532 root.allowFingerprint &&
533 Powerd.status === Powerd.On &&
534 Biometryd.available &&
535 AccountsService.enableFingerprintIdentification
537 function cancelOperation() {
544 function restartOperation() {
548 var identifier = Biometryd.defaultDevice.identifier;
549 operation = identifier.identifyUser();
550 operation.start(biometryd);
554 function failOperation(reason) {
555 console.log("Failed to identify user by fingerprint:", reason);
557 if (!d.secureFingerprint) {
558 d.startUnlock(false /* toTheRight */); // use normal login instead
561 var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
562 loader.item.showErrorMessage(msg);
566 Component.onCompleted: restartOperation()
567 Component.onDestruction: cancelOperation()
568 onIdEnabledChanged: restartOperation()
571 if (!d.secureFingerprint) {
572 failOperation("fingerprint reader is locked");
575 if (result !== LightDMService.users.data(d.currentIndex, LightDMService.userRoles.UidRole)) {
576 AccountsService.failedFingerprintLogins++;
577 failOperation("not the selected user");
580 console.log("Identified user by fingerprint:", result);
582 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
584 root.forcedUnlock = true;
587 if (!d.secureFingerprint) {
588 failOperation("fingerprint reader is locked");
590 AccountsService.failedFingerprintLogins++;
591 failOperation(reason);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list