on xenial help sign in
Make wrong-password handling much nicer by showing a pretty spinner while we wait for PAM, by improving the prompt text to match designs, by forcing the user to wait five seconds after every five failed attemps, and by supporting (but not yet enabling) an opt-in "factory-reset your phone after X failed attemps" feature.

Approved by: Michał Sawicz

commit548e044c5638fb8981df1edc1896f5ceaa9558ce authorMichael Terry <michael.terry@canonical.com> dateThu 14 Aug 2014, 01:28 (12 years ago) parentcad788d3a83692
43 files changed+659−111
cmake/modules/QmlTest.cmake+2−2 cmake/modules/autopilot.cmake+2−2 debian/control+2−1 debian/unity8-private.install−2 plugins/AccountsService/50-com.canonical.unity.AccountsService.pkla−6 plugins/AccountsService/AccountsService.cpp+27−1 plugins/AccountsService/AccountsService.h+9 plugins/AccountsService/CMakeLists.txt−10 plugins/AccountsService/com.canonical.unity.AccountsService.policy−24 plugins/AccountsService/com.canonical.unity.AccountsService.xml+28−5 plugins/LightDM/Greeter.cpp+4−1 plugins/LightDM/Greeter.h+1−1 plugins/Ubuntu/CMakeLists.txt+1 plugins/Ubuntu/SystemImage/CMakeLists.txt+10 plugins/Ubuntu/SystemImage/SystemImage.cpp+34 plugins/Ubuntu/SystemImage/SystemImage.h+38 plugins/Ubuntu/SystemImage/SystemImage.qmltypes+17 plugins/Ubuntu/SystemImage/plugin.cpp+34 plugins/Ubuntu/SystemImage/plugin.h+31 plugins/Ubuntu/SystemImage/qmldir+3 po/unity8.pot+69−16 qml/Components/Lockscreen.qml+31−8 qml/Components/PassphraseLockscreen.qml+3−4 qml/Components/PinLockscreen.qml+3−4 qml/Notifications/NotificationMenuItemFactory.qml−1 qml/Shell.qml+50−5 tests/mocks/AccountsService/AccountsService.cpp+13−1 tests/mocks/AccountsService/AccountsService.h+8 tests/mocks/LightDM/Greeter.cpp+11 tests/mocks/LightDM/Greeter.h+3 tests/mocks/LightDM/full/GreeterPrivate.cpp+4−4 tests/mocks/LightDM/single-passphrase/GreeterPrivate.cpp+2−9 tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+3−2 tests/mocks/Ubuntu/CMakeLists.txt+1 tests/mocks/Ubuntu/SystemImage/CMakeLists.txt+10 tests/mocks/Ubuntu/SystemImage/MockSystemImage.cpp+27 tests/mocks/Ubuntu/SystemImage/MockSystemImage.h+36 tests/mocks/Ubuntu/SystemImage/SystemImage.qmltypes+18 tests/mocks/Ubuntu/SystemImage/plugin.cpp+34 tests/mocks/Ubuntu/SystemImage/plugin.h+31 tests/mocks/Ubuntu/SystemImage/qmldir+3 tests/qmltests/Greeter/tst_Lockscreen.qml−2 tests/qmltests/tst_ShellWithPin.qml+56
cmake/modules/QmlTest.cmake+2−2
@@ -103,7 +103,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
103103 endif()
104104
105105 set(qmltest_command
106− env ${qmltest_ENVIRONMENT}
106+ env ${qmltest_ENVIRONMENT} UNITY_TESTING=1
107107 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
108108 ${qmltestrunner_imports}
109109 ${ITERATIONS_STRING}
@@ -117,7 +117,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
117117 set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
118118 endif()
119119 set(qmltest_xvfb_command
120− env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
120+ env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH} UNITY_TESTING=1
121121 xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
122122 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
123123 ${qmltestrunner_imports}
cmake/modules/autopilot.cmake+2−2
@@ -2,7 +2,7 @@ add_custom_target(autopilot)
22
33function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
44 add_custom_target(autopilot-${TEST_NAME}
5− COMMAND LANG=C QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks python3 -m autopilot.run run ${TEST_SUITE}
5+ COMMAND UNITY_TESTING=1 LANG=C QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks python3 -m autopilot.run run ${TEST_SUITE}
66 WORKING_DIRECTORY ${WORKING_DIR}
77 DEPENDS fake_install
88 )
@@ -14,7 +14,7 @@ function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
1414 add_dependencies(autopilot autopilot-${TEST_NAME})
1515
1616 add_custom_target(autopilot2-${TEST_NAME}
17− COMMAND LANG=C QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks python2 -m autopilot.run run ${TEST_SUITE}
17+ COMMAND UNITY_TESTING=1 LANG=C QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks python2 -m autopilot.run run ${TEST_SUITE}
1818 WORKING_DIRECTORY ${WORKING_DIR}
1919 DEPENDS fake_install
2020 )
debian/control+2−1
@@ -165,7 +165,8 @@ Package: unity8-private
165165Architecture: any
166166Multi-Arch: same
167167Pre-Depends: ${misc:Pre-Depends},
168−Depends: gsettings-ubuntu-schemas,
168+Depends: accountsservice-ubuntu-schemas,
169+ gsettings-ubuntu-schemas,
169170 libhardware2,
170171 libunity-core-6.0-9,
171172 pay-service,
debian/unity8-private.install−2
@@ -10,5 +10,3 @@ usr/lib/*/unity8/qml/Unity
1010usr/lib/*/unity8/qml/Utils
1111usr/share/accountsservice/interfaces
1212usr/share/dbus-1/interfaces
13−usr/share/polkit-1
14−var/lib/polkit-1
plugins/AccountsService/50-com.canonical.unity.AccountsService.pkladeleted−6
@@ -1,6 +0,0 @@
1−[Allow LightDM to set Unity AccountsService fields]
2−Identity=unix-user:lightdm
3−Action=com.canonical.unity.AccountsService.ModifyAnyUser
4−ResultActive=yes
5−ResultInactive=yes
6−ResultAny=yes
plugins/AccountsService/AccountsService.cpp+27−1
@@ -27,7 +27,8 @@ AccountsService::AccountsService(QObject* parent)
2727 m_user(qgetenv("USER")),
2828 m_demoEdges(false),
2929 m_statsWelcomeScreen(false),
30− m_passwordDisplayHint(Keyboard)
30+ m_passwordDisplayHint(Keyboard),
31+ m_failedLogins(0)
3132{
3233 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
3334 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -49,6 +50,7 @@ void AccountsService::setUser(const QString &user)
4950 updateBackgroundFile();
5051 updateStatsWelcomeScreen();
5152 updatePasswordDisplayHint();
53+ updateFailedLogins();
5254}
5355
5456bool AccountsService::demoEdges() const
@@ -113,6 +115,26 @@ void AccountsService::updatePasswordDisplayHint()
113115 }
114116}
115117
118+void AccountsService::updateFailedLogins()
119+{
120+ uint failedLogins = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService.Private", "FailedLogins").toUInt();
121+ if (m_failedLogins != failedLogins) {
122+ m_failedLogins = failedLogins;
123+ Q_EMIT failedLoginsChanged();
124+ }
125+}
126+
127+uint AccountsService::failedLogins() const
128+{
129+ return m_failedLogins;
130+}
131+
132+void AccountsService::setFailedLogins(uint failedLogins)
133+{
134+ m_failedLogins = failedLogins;
135+ m_service->setUserProperty(m_user, "com.canonical.unity.AccountsService.Private", "FailedLogins", failedLogins);
136+}
137+
116138void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
117139{
118140 if (m_user != user) {
@@ -123,6 +145,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
123145 if (changed.contains("demo-edges")) {
124146 updateDemoEdges();
125147 }
148+ } else if (interface == "com.canonical.unity.AccountsService.Private") {
149+ if (changed.contains("FailedLogins")) {
150+ updateFailedLogins();
151+ }
126152 } else if (interface == "com.ubuntu.touch.AccountsService.SecurityPrivacy") {
127153 if (changed.contains("StatsWelcomeScreen")) {
128154 updateStatsWelcomeScreen();
plugins/AccountsService/AccountsService.h+9
@@ -45,6 +45,10 @@ class AccountsService: public QObject
4545 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
4646 READ passwordDisplayHint
4747 NOTIFY passwordDisplayHintChanged)
48+ Q_PROPERTY (uint failedLogins
49+ READ failedLogins
50+ WRITE setFailedLogins
51+ NOTIFY failedLoginsChanged)
4852
4953public:
5054 enum PasswordDisplayHint {
@@ -61,6 +65,8 @@ public:
6165 QString backgroundFile() const;
6266 bool statsWelcomeScreen() const;
6367 PasswordDisplayHint passwordDisplayHint() const;
68+ uint failedLogins() const;
69+ void setFailedLogins(uint failedLogins);
6470
6571Q_SIGNALS:
6672 void userChanged();
@@ -68,6 +74,7 @@ Q_SIGNALS:
6874 void backgroundFileChanged();
6975 void statsWelcomeScreenChanged();
7076 void passwordDisplayHintChanged();
77+ void failedLoginsChanged();
7178
7279private Q_SLOTS:
7380 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -78,6 +85,7 @@ private:
7885 void updateBackgroundFile();
7986 void updateStatsWelcomeScreen();
8087 void updatePasswordDisplayHint();
88+ void updateFailedLogins();
8189
8290 AccountsServiceDBusAdaptor *m_service;
8391 QString m_user;
@@ -85,6 +93,7 @@ private:
8593 QString m_backgroundFile;
8694 bool m_statsWelcomeScreen;
8795 PasswordDisplayHint m_passwordDisplayHint;
96+ uint m_failedLogins;
8897};
8998
9099#endif
plugins/AccountsService/CMakeLists.txt−10
@@ -10,8 +10,6 @@ qt5_use_modules(AccountsService-qml DBus Qml)
1010
1111add_unity8_plugin(AccountsService 0.1 AccountsService TARGETS AccountsService-qml)
1212
13−set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
14−set(POLKIT_DATA_DIR "${CMAKE_INSTALL_PREFIX}/share/polkit-1")
1513set(DBUS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/dbus-1/interfaces")
1614set(ACCOUNTS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/accountsservice/interfaces")
1715
@@ -24,11 +22,3 @@ install(CODE "
2422execute_process(COMMAND mkdir -p \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
2523execute_process(COMMAND ln -sf ../../dbus-1/interfaces/com.canonical.unity.AccountsService.xml \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
2624 ")
27−
28−install(FILES com.canonical.unity.AccountsService.policy
29− DESTINATION "${POLKIT_DATA_DIR}/actions"
30− )
31−
32−install(FILES 50-com.canonical.unity.AccountsService.pkla
33− DESTINATION "${POLKIT_LIB_DIR}/localauthority/10-vendor.d"
34− )
plugins/AccountsService/com.canonical.unity.AccountsService.policydeleted−24
@@ -1,24 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8"?>
2−
3−<policyconfig>
4− <action id="com.canonical.unity.AccountsService.ModifyOwnUser">
5− <description>Set properties of own user</description>
6− <message>Authentication is required to set one's own unity properties.</message>
7− <defaults>
8− <allow_any>yes</allow_any>
9− <allow_inactive>yes</allow_inactive>
10− <allow_active>yes</allow_active>
11− </defaults>
12− </action>
13−
14− <action id="com.canonical.unity.AccountsService.ModifyAnyUser">
15− <description>Set properties of any user</description>
16− <message>Authentication is required to set another user's unity
17−properties.</message>
18− <defaults>
19− <allow_any>no</allow_any>
20− <allow_inactive>no</allow_inactive>
21− <allow_active>no</allow_active>
22− </defaults>
23− </action>
24−</policyconfig>
plugins/AccountsService/com.canonical.unity.AccountsService.xml+28−5
@@ -3,22 +3,45 @@
33
44 <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
55
6− <annotation name="org.freedesktop.Accounts.Authentication.ChangeOwn"
7− value="com.canonical.unity.AccountsService.ModifyOwnUser"/>
8−
96 <annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
10− value="com.canonical.unity.AccountsService.ModifyAnyUser"/>
7+ value="com.ubuntu.AccountsService.GreeterReadAny"/>
118
129 <annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
13− value="com.canonical.unity.AccountsService.ModifyAnyUser"/>
10+ value="com.ubuntu.AccountsService.GreeterChangeAny"/>
1411
12+ <!-- Should have been named DemoEdges, sorry folks. -mterry -->
1513 <property name="demo-edges" type="b" access="readwrite">
1614 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
1715 </property>
1816
17+ <!-- Should have been named LauncherItems, sorry folks. -mterry -->
1918 <property name="launcher-items" type="aa{sv}" access="readwrite">
2019 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
2120 </property>
2221
2322 </interface>
23+
24+ <!-- This interface is for bits of data that the greeter wants to track
25+ per-user in a persistent way, but that users shouldn't be able to edit
26+ in an ideal world.
27+
28+ This interface is identical in permissions to the above one for now,
29+ but once we stop running the greeter in user-space, we should
30+ disallow org.freedesktop.Accounts.Authentication.ChangeOwn from the
31+ Private inteface. -->
32+ <interface name="com.canonical.unity.AccountsService.Private">
33+
34+ <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
35+
36+ <annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
37+ value="com.ubuntu.AccountsService.GreeterReadAny"/>
38+
39+ <annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
40+ value="com.ubuntu.AccountsService.GreeterChangeAny"/>
41+
42+ <property name="FailedLogins" type="u" access="readwrite">
43+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
44+ </property>
45+
46+ </interface>
2447</node>
plugins/LightDM/Greeter.cpp+4−1
@@ -17,6 +17,7 @@
1717 */
1818
1919#include "Greeter.h"
20+#include <libintl.h>
2021#include <QLightDM/Greeter>
2122
2223class GreeterPrivate
@@ -125,13 +126,15 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
125126 Q_D(Greeter);
126127 d->wasPrompted = true;
127128
129+ bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
130+
128131 // Strip prompt of any colons at the end
129132 QString trimmedText = text.trimmed();
130133 if (trimmedText.endsWith(":") || trimmedText.endsWith(":")) {
131134 trimmedText.chop(1);
132135 }
133136
134− Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret);
137+ Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
135138}
136139
137140void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
plugins/LightDM/Greeter.h+1−1
@@ -55,7 +55,7 @@ public Q_SLOTS:
5555
5656Q_SIGNALS:
5757 void showMessage(const QString &text, bool isError);
58− void showPrompt(const QString &text, bool isSecret);
58+ void showPrompt(const QString &text, bool isSecret, bool isDefaultPrompt);
5959 void authenticationComplete();
6060 void authenticationUserChanged(const QString &user);
6161 void isActiveChanged();
plugins/Ubuntu/CMakeLists.txt+1
@@ -1,3 +1,4 @@
11add_subdirectory(Gestures)
22add_subdirectory(DownloadDaemonListener)
33add_subdirectory(Payments)
4+add_subdirectory(SystemImage)
plugins/Ubuntu/SystemImage/CMakeLists.txtnew file+10
@@ -0,0 +1,10 @@
1+set(SYSTEMIMAGE_SOURCES
2+ plugin.cpp
3+ SystemImage.cpp
4+)
5+
6+add_library(SystemImage MODULE ${SYSTEMIMAGE_SOURCES})
7+
8+qt5_use_modules(SystemImage Qml DBus Core)
9+
10+add_unity8_plugin(Ubuntu.SystemImage 0.1 Ubuntu/SystemImage TARGETS SystemImage)
plugins/Ubuntu/SystemImage/SystemImage.cppnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#include "SystemImage.h"
18+#include <QDBusConnection>
19+#include <QDBusInterface>
20+
21+SystemImage::SystemImage(QObject *parent)
22+ : QObject(parent),
23+ m_interface(new QDBusInterface("com.canonical.SystemImage",
24+ "/Service",
25+ "com.canonical.SystemImage",
26+ QDBusConnection::systemBus(),
27+ this))
28+{
29+}
30+
31+void SystemImage::factoryReset()
32+{
33+ m_interface->call("FactoryReset");
34+}
plugins/Ubuntu/SystemImage/SystemImage.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#ifndef SYSTEMIMAGE_H
18+#define SYSTEMIMAGE_H
19+
20+#include <QObject>
21+
22+class QDBusInterface;
23+
24+class SystemImage : public QObject
25+{
26+ Q_OBJECT
27+ Q_DISABLE_COPY(SystemImage)
28+
29+public:
30+ explicit SystemImage(QObject *parent = 0);
31+
32+ Q_INVOKABLE void factoryReset();
33+
34+private:
35+ QDBusInterface *m_interface;
36+};
37+
38+#endif // SYSTEMIMAGE_H
plugins/Ubuntu/SystemImage/SystemImage.qmltypesnew file+17
@@ -0,0 +1,17 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Ubuntu.SystemImage 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "SystemImage"
12+ prototype: "QObject"
13+ exports: ["Ubuntu.SystemImage/SystemImage 0.1"]
14+ exportMetaObjectRevisions: [0]
15+ Method { name: "factoryReset" }
16+ }
17+}
plugins/Ubuntu/SystemImage/plugin.cppnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#include "plugin.h"
18+#include "SystemImage.h"
19+
20+#include <QtQml>
21+
22+static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
23+{
24+ Q_UNUSED(engine)
25+ Q_UNUSED(scriptEngine)
26+ return new SystemImage();
27+}
28+
29+void BackendPlugin::registerTypes(const char *uri)
30+{
31+ Q_ASSERT(uri == QLatin1String("Ubuntu.SystemImage"));
32+
33+ qmlRegisterSingletonType<SystemImage>(uri, 0, 1, "SystemImage", service_provider);
34+}
plugins/Ubuntu/SystemImage/plugin.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#ifndef SYSTEMIMAGE_PLUGIN_H
18+#define SYSTEMIMAGE_PLUGIN_H
19+
20+#include <QQmlExtensionPlugin>
21+
22+class BackendPlugin : public QQmlExtensionPlugin
23+{
24+ Q_OBJECT
25+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26+
27+public:
28+ void registerTypes(const char *uri);
29+};
30+
31+#endif // SYSTEMIMAGE_PLUGIN_H
plugins/Ubuntu/SystemImage/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Ubuntu.SystemImage
2+plugin SystemImage
3+typeinfo SystemImage.qmltypes
po/unity8.pot+69−16
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-08-08 11:17+0200\n"
11+"POT-Creation-Date: 2014-08-13 14:09-0400\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,6 +17,10 @@ msgstr ""
1717"Content-Type: text/plain; charset=UTF-8\n"
1818"Content-Transfer-Encoding: 8bit\n"
1919
20+#: plugins/LightDM/Greeter.cpp:129
21+msgid "Password: "
22+msgstr ""
23+
2024#: plugins/Unity/Launcher/launcheritem.cpp:43
2125#: plugins/Unity/Launcher/launcheritem.cpp:73
2226msgid "Pin shortcut"
@@ -88,39 +92,51 @@ msgid ""
8892"phone<br><br>Tap on the screen to start"
8993msgstr ""
9094
91−#: qml/Components/Lockscreen.qml:220
95+#: qml/Components/Lockscreen.qml:161
96+msgid "Too many incorrect attempts"
97+msgstr ""
98+
99+#: qml/Components/Lockscreen.qml:163
100+msgid "Please wait"
101+msgstr ""
102+
103+#: qml/Components/Lockscreen.qml:245
92104msgid "Emergency Call"
93105msgstr ""
94106
95−#: qml/Components/Lockscreen.qml:243
107+#: qml/Components/Lockscreen.qml:268
96108msgid "OK"
97109msgstr ""
98110
99−#: qml/Components/PassphraseLockscreen.qml:62
111+#: qml/Components/PassphraseLockscreen.qml:61
100112#, qt-format
101113msgid "Hello %1"
102114msgstr ""
103115
104−#: qml/Components/PassphraseLockscreen.qml:62
116+#: qml/Components/PassphraseLockscreen.qml:61
105117msgid "Hello"
106118msgstr ""
107119
108−#: qml/Components/PinLockscreen.qml:198
120+#: qml/Components/PinLockscreen.qml:197
109121msgid "CANCEL"
110122msgstr ""
111123
112−#: qml/Components/PinLockscreen.qml:216
124+#: qml/Components/PinLockscreen.qml:215
113125msgid "DONE"
114126msgstr ""
115127
116−#: qml/Dash/GenericScopeView.qml:358
128+#: qml/Dash/GenericScopeView.qml:360
117129msgid "See less"
118130msgstr ""
119131
120−#: qml/Dash/GenericScopeView.qml:358
132+#: qml/Dash/GenericScopeView.qml:360
121133msgid "See all"
122134msgstr ""
123135
136+#: qml/Dash/GenericScopeView.qml:423 qml/Panel/SearchIndicator.qml:27
137+msgid "Search"
138+msgstr ""
139+
124140#: qml/Dash/Previews/PreviewActionCombo.qml:34
125141msgid "More..."
126142msgstr ""
@@ -141,15 +157,15 @@ msgstr ""
141157msgid "Send"
142158msgstr ""
143159
144−#: qml/Dash/ScopesOverview.qml:200
160+#: qml/Dash/ScopesOverview.qml:201
145161msgid "Manage Dash"
146162msgstr ""
147163
148−#: qml/Dash/ScopesOverview.qml:405
164+#: qml/Dash/ScopesOverview.qml:408
149165msgid "Done"
150166msgstr ""
151167
152−#: qml/Dash/ScopesOverview.qml:431
168+#: qml/Dash/ScopesOverview.qml:434
153169msgid "Store"
154170msgstr ""
155171
@@ -233,11 +249,48 @@ msgstr ""
233249msgid "Roaming"
234250msgstr ""
235251
236−#: qml/Panel/SearchIndicator.qml:27
237−msgid "Search"
252+#: qml/Shell.qml:267
253+msgid "passphrase"
254+msgstr ""
255+
256+#: qml/Shell.qml:267
257+msgid "passcode"
258+msgstr ""
259+
260+#: qml/Shell.qml:269
261+#, qt-format
262+msgid "Enter your %1"
238263msgstr ""
239264
240−#: qml/Shell.qml:256
265+#: qml/Shell.qml:270
241266#, qt-format
242−msgid "Please enter %1"
267+msgid "Incorrect %1"
268+msgstr ""
269+
270+#: qml/Shell.qml:272
271+msgid "Please re-enter"
272+msgstr ""
273+
274+#: qml/Shell.qml:306
275+msgid "Sorry, incorrect passphrase."
276+msgstr ""
277+
278+#: qml/Shell.qml:307
279+msgid "Sorry, incorrect passcode."
280+msgstr ""
281+
282+#: qml/Shell.qml:308
283+msgid "This will be your last attempt."
284+msgstr ""
285+
286+#: qml/Shell.qml:310
287+msgid ""
288+"If passphrase is entered incorrectly, your phone will conduct a factory "
289+"reset and all personal data will be deleted."
290+msgstr ""
291+
292+#: qml/Shell.qml:311
293+msgid ""
294+"If passcode is entered incorrectly, your phone will conduct a factory reset "
295+"and all personal data will be deleted."
243296msgstr ""
qml/Components/Lockscreen.qml+31−8
@@ -57,10 +57,15 @@ Showable {
5757
5858 onRequiredChanged: {
5959 if (required && pinPadLoader.item) {
60− pinPadLoader.item.clear(false);
60+ clear(false)
6161 }
6262 }
6363
64+ function forceDelay(delay) {
65+ forcedDelayTimer.interval = delay
66+ forcedDelayTimer.start()
67+ }
68+
6469 function reset() {
6570 // This causes the loader below to destry and recreate the source
6671 pinPadLoader.resetting = true;
@@ -71,12 +76,19 @@ Showable {
7176 if (pinPadLoader.item) {
7277 pinPadLoader.item.clear(showAnimation);
7378 }
79+ pinPadLoader.showWrongText = showAnimation
80+ pinPadLoader.waiting = false
7481 }
7582
7683 function showInfoPopup(title, text) {
7784 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
7885 }
7986
87+ Timer {
88+ id: forcedDelayTimer
89+ onTriggered: pinPadLoader.showWrongText = false
90+ }
91+
8092 Rectangle {
8193 // In case background fails to load or is undefined
8294 id: backgroundBackup
@@ -133,8 +145,6 @@ Showable {
133145 }
134146 }
135147
136−
137−
138148 Loader {
139149 id: pinPadLoader
140150 objectName: "pinPadLoader"
@@ -145,13 +155,24 @@ Showable {
145155 verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : -units.gu(4)
146156 }
147157 property bool resetting: false
158+ property bool waiting: false
159+ property bool showWrongText: false
160+
161+ readonly property string forcedDelayText: i18n.tr("Too many incorrect attempts") +
162+ "\n" +
163+ i18n.tr("Please wait")
148164
149165 source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
166+ onSourceChanged: {
167+ waiting = false
168+ showWrongText = false
169+ }
150170
151171 Connections {
152172 target: pinPadLoader.item
153173
154174 onEntered: {
175+ pinPadLoader.waiting = true
155176 root.entered(passphrase);
156177 }
157178
@@ -173,17 +194,19 @@ Showable {
173194 Binding {
174195 target: pinPadLoader.item
175196 property: "placeholderText"
176− value: root.placeholderText
197+ value: forcedDelayTimer.running ? pinPadLoader.forcedDelayText :
198+ (pinPadLoader.showWrongText ? root.wrongPlaceholderText :
199+ root.placeholderText)
177200 }
178201 Binding {
179202 target: pinPadLoader.item
180− property: "wrongPlaceholderText"
181− value: root.wrongPlaceholderText
203+ property: "username"
204+ value: root.username
182205 }
183206 Binding {
184207 target: pinPadLoader.item
185− property: "username"
186− value: root.username
208+ property: "entryEnabled"
209+ value: !pinPadLoader.waiting && !forcedDelayTimer.running
187210 }
188211 }
189212
qml/Components/PassphraseLockscreen.qml+3−4
@@ -23,15 +23,14 @@ Item {
2323 height: highlightItem.height
2424
2525 property string placeholderText
26− property string wrongPlaceholderText
2726 property string username: ""
27+ property bool entryEnabled: true
2828
2929 signal entered(string passphrase)
3030 signal cancel()
3131
3232 function clear(playAnimation) {
3333 pinentryField.text = "";
34− pinentryField.enabled = true
3534 if (playAnimation) {
3635 wrongPasswordAnimation.start();
3736 pinentryField.forceActiveFocus();
@@ -76,11 +75,11 @@ Item {
7675 echoMode: TextInput.Password
7776 opacity: 0.9
7877 hasClearButton: false
79− placeholderText: wrongPasswordAnimation.running ? root.wrongPlaceholderText : root.placeholderText
78+ enabled: entryEnabled
79+ placeholderText: root.placeholderText
8080
8181 onAccepted: {
8282 if (pinentryField.text) {
83− pinentryField.enabled = false;
8483 root.entered(pinentryField.text);
8584 }
8685 }
qml/Components/PinLockscreen.qml+3−4
@@ -25,7 +25,6 @@ Column {
2525 spacing: units.gu(3.5)
2626
2727 property alias placeholderText: pinentryField.placeholderText
28− property alias wrongPlaceholderText: pinentryField.wrongPlaceholderText
2928 property int padWidth: units.gu(34)
3029 property int padHeight: units.gu(28)
3130 property int minPinLength: -1
@@ -58,7 +57,6 @@ Column {
5857 radius: "medium"
5958 property string text: ""
6059 property string placeholderText: ""
61− property string wrongPlaceholderText: ""
6260
6361 function appendChar(character) {
6462 if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
@@ -90,9 +88,10 @@ Column {
9088 id: pinentryFieldPlaceHolder
9189 objectName: "pinentryFieldPlaceHolder"
9290 anchors.centerIn: parent
91+ horizontalAlignment: Text.AlignHCenter
9392 color: "#f3f3e7"
9493 opacity: 0.6
95− text: wrongPasswordAnimation.running ? parent.wrongPlaceholderText : parent.placeholderText
94+ text: parent.placeholderText
9695 visible: pinentryFieldLabel.text.length == 0
9796 }
9897
@@ -107,7 +106,7 @@ Column {
107106 bottom: parent.bottom
108107 bottomMargin: units.gu(1)
109108 }
110− visible: !priv.autoConfirm
109+ visible: entryEnabled && !priv.autoConfirm
111110 width: height
112111 name: "erase"
113112 color: "#f3f3e7"
qml/Notifications/NotificationMenuItemFactory.qml−1
@@ -122,7 +122,6 @@ Loader {
122122
123123 onEntered: {
124124 menuModel.changeState(menuIndex, passphrase);
125− entryEnabled = false;
126125 }
127126
128127 onCancel: {
qml/Shell.qml+50−5
@@ -19,7 +19,9 @@ import AccountsService 0.1
1919import GSettings 1.0
2020import Unity.Application 0.1
2121import Ubuntu.Components 0.1
22+import Ubuntu.Components.Popups 1.0
2223import Ubuntu.Gestures 0.1
24+import Ubuntu.SystemImage 0.1
2325import Unity.Launcher 0.1
2426import Utils 0.1
2527import LightDM 0.1 as LightDM
@@ -53,6 +55,10 @@ Item {
5355 property bool sideStageEnabled: shell.width >= units.gu(100)
5456 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
5557
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 int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
61+
5662 function activateApplication(appId) {
5763 if (ApplicationManager.findApplication(appId)) {
5864 ApplicationManager.requestFocusApplication(appId);
@@ -255,7 +261,15 @@ Item {
255261
256262 onShowPrompt: {
257263 if (greeter.narrowMode) {
258− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
264+ var promptText = text.toLowerCase()
265+ if (isDefaultPrompt) {
266+ promptText = lockscreen.alphaNumeric ?
267+ i18n.tr("passphrase") : i18n.tr("passcode")
268+ }
269+ lockscreen.placeholderText = i18n.tr("Enter your %1").arg(promptText)
270+ lockscreen.wrongPlaceholderText = i18n.tr("Incorrect %1").arg(promptText) +
271+ "\n" +
272+ i18n.tr("Please re-enter")
259273 lockscreen.show();
260274 }
261275 }
@@ -270,13 +284,40 @@ Item {
270284 }
271285
272286 onAuthenticationComplete: {
287+ if (LightDM.Greeter.authenticated) {
288+ AccountsService.failedLogins = 0
289+ }
290+ // Else only penalize user for a failed login if they actually were
291+ // prompted for a password. We do this below after the promptless
292+ // early exit.
293+
273294 if (LightDM.Greeter.promptless) {
274295 return;
275296 }
297+
276298 if (LightDM.Greeter.authenticated) {
277299 lockscreen.hide();
278300 greeter.login();
279301 } else {
302+ AccountsService.failedLogins++
303+ if (maxFailedLogins >= 2) { // require at least a warning
304+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
305+ var title = lockscreen.alphaNumeric ?
306+ i18n.tr("Sorry, incorrect passphrase.") :
307+ i18n.tr("Sorry, incorrect passcode.")
308+ var text = i18n.tr("This will be your last attempt.") + " " +
309+ (lockscreen.alphaNumeric ?
310+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
311+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
312+ lockscreen.showInfoPopup(title, text)
313+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
314+ SystemImage.factoryReset() // Ouch!
315+ }
316+ }
317+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
318+ lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
319+ }
320+
280321 lockscreen.clear(true);
281322 if (greeter.narrowMode) {
282323 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
@@ -311,6 +352,14 @@ Item {
311352 }
312353
313354 property bool fullyShown: showProgress === 1.0
355+ onFullyShownChanged: {
356+ // Wait until the greeter is completely covering lockscreen before resetting it.
357+ if (fullyShown && !LightDM.Greeter.authenticated) {
358+ lockscreen.reset();
359+ lockscreen.show();
360+ }
361+ }
362+
314363 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
315364 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
316365
@@ -350,10 +399,6 @@ Item {
350399 if (greeter.narrowMode) {
351400 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
352401 }
353− if (!LightDM.Greeter.authenticated) {
354− lockscreen.reset();
355− lockscreen.show();
356− }
357402 greeter.fakeActiveForApp = "";
358403 greeter.forceActiveFocus();
359404 }
tests/mocks/AccountsService/AccountsService.cpp+13−1
@@ -23,7 +23,8 @@
2323AccountsService::AccountsService(QObject* parent)
2424 : QObject(parent),
2525 m_backgroundFile(qmlDirectory() + "graphics/phone_background.jpg"),
26− m_statsWelcomeScreen(true)
26+ m_statsWelcomeScreen(true),
27+ m_failedLogins(0)
2728{
2829}
2930
@@ -78,3 +79,14 @@ AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() cons
7879 else
7980 return PasswordDisplayHint::Keyboard;
8081}
82+
83+uint AccountsService::failedLogins() const
84+{
85+ return m_failedLogins;
86+}
87+
88+void AccountsService::setFailedLogins(uint failedLogins)
89+{
90+ m_failedLogins = failedLogins;
91+ failedLoginsChanged();
92+}
tests/mocks/AccountsService/AccountsService.h+8
@@ -47,6 +47,10 @@ class AccountsService: public QObject
4747 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
4848 READ passwordDisplayHint
4949 NOTIFY passwordDisplayHintChanged)
50+ Q_PROPERTY (uint failedLogins
51+ READ failedLogins
52+ WRITE setFailedLogins
53+ NOTIFY failedLoginsChanged)
5054
5155public:
5256 enum PasswordDisplayHint {
@@ -65,6 +69,8 @@ public:
6569 bool statsWelcomeScreen() const;
6670 void setStatsWelcomeScreen(bool statsWelcomeScreen);
6771 PasswordDisplayHint passwordDisplayHint() const;
72+ uint failedLogins() const;
73+ void setFailedLogins(uint failedLogins);
6874
6975Q_SIGNALS:
7076 void userChanged();
@@ -72,11 +78,13 @@ Q_SIGNALS:
7278 void backgroundFileChanged();
7379 void statsWelcomeScreenChanged();
7480 void passwordDisplayHintChanged();
81+ void failedLoginsChanged();
7582
7683private:
7784 QString m_backgroundFile;
7885 QString m_user;
7986 bool m_statsWelcomeScreen;
87+ uint m_failedLogins;
8088};
8189
8290#endif
tests/mocks/LightDM/Greeter.cpp+11
@@ -19,6 +19,7 @@
1919#include "Greeter.h"
2020#include "GreeterPrivate.h"
2121#include <QtCore/QCoreApplication>
22+#include <QTimer>
2223
2324namespace QLightDM
2425{
@@ -164,4 +165,14 @@ void Greeter::respond(const QString &response)
164165 d->handleRespond(response);
165166}
166167
168+void Greeter::sendAuthenticationComplete()
169+{
170+ if (qgetenv("UNITY_TESTING").isEmpty()) {
171+ // simulate PAM's delay
172+ QTimer::singleShot(1000, this, SIGNAL(authenticationComplete()));
173+ } else {
174+ Q_EMIT authenticationComplete();
175+ }
176+}
177+
167178}
tests/mocks/LightDM/Greeter.h+3
@@ -96,6 +96,9 @@ Q_SIGNALS:
9696 void authenticationComplete();
9797 void autologinTimerExpired();
9898
99+protected:
100+ void sendAuthenticationComplete();
101+
99102private:
100103 GreeterPrivate *d_ptr;
101104 Q_DECLARE_PRIVATE(Greeter)
tests/mocks/LightDM/full/GreeterPrivate.cpp+4−4
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
5454 authenticated = true;
5555 Q_EMIT q->authenticationComplete();
5656 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password: ", Greeter::PromptTypeSecret);
5858 } else if (authenticationUser == "auth-error") {
5959 authenticated = false;
6060 Q_EMIT q->authenticationComplete();
@@ -78,11 +78,11 @@ void GreeterPrivate::handleRespond(const QString &response)
7878 Q_EMIT q->showPrompt("otp", Greeter::PromptTypeQuestion);
7979 } else {
8080 authenticated = false;
81− Q_EMIT q->authenticationComplete();
81+ q->sendAuthenticationComplete();
8282 }
8383 } else {
8484 authenticated = (response == "otp");
85− Q_EMIT q->authenticationComplete();
85+ q->sendAuthenticationComplete();
8686 }
8787 return;
8888 }
@@ -92,7 +92,7 @@ void GreeterPrivate::handleRespond(const QString &response)
9292 } else {
9393 authenticated = (response == "password");
9494 }
95− Q_EMIT q->authenticationComplete();
95+ q->sendAuthenticationComplete();
9696}
9797
9898}
tests/mocks/LightDM/single-passphrase/GreeterPrivate.cpp+2−9
@@ -19,8 +19,6 @@
1919#include "../Greeter.h"
2020#include "../GreeterPrivate.h"
2121
22−#include <QDebug>
23−
2422namespace QLightDM
2523{
2624
@@ -34,20 +32,15 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
3432void GreeterPrivate::handleAuthenticate()
3533{
3634 Q_Q(Greeter);
37−
38− qDebug() << "handleAuthentication called!" << authenticationUser;
39−
40− Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password: ", Greeter::PromptTypeSecret);
4136}
4237
4338void GreeterPrivate::handleRespond(const QString &response)
4439{
4540 Q_Q(Greeter);
4641
47−
4842 authenticated = (response == "password");
49− qDebug() << "responding" << response << authenticated;
50− Q_EMIT q->authenticationComplete();
43+ q->sendAuthenticationComplete();
5144}
5245
5346}
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+3−2
@@ -32,14 +32,15 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
3232void GreeterPrivate::handleAuthenticate()
3333{
3434 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password: ", Greeter::PromptTypeSecret);
3636}
3737
3838void GreeterPrivate::handleRespond(const QString &response)
3939{
4040 Q_Q(Greeter);
41+
4142 authenticated = (response == "1234");
42− Q_EMIT q->authenticationComplete();
43+ q->sendAuthenticationComplete();
4344}
4445
4546}
tests/mocks/Ubuntu/CMakeLists.txt+1
@@ -1,4 +1,5 @@
11add_subdirectory(DownloadDaemonListener)
22add_subdirectory(Payments)
3+add_subdirectory(SystemImage)
34add_subdirectory(Telephony)
45add_subdirectory(Thumbnailer)
tests/mocks/Ubuntu/SystemImage/CMakeLists.txtnew file+10
@@ -0,0 +1,10 @@
1+set(MOCK_SYSTEMIMAGE_SOURCES
2+ plugin.cpp
3+ MockSystemImage.cpp
4+)
5+
6+add_library(MockSystemImage MODULE ${MOCK_SYSTEMIMAGE_SOURCES})
7+
8+qt5_use_modules(MockSystemImage Qml Quick Core)
9+
10+add_unity8_mock(Ubuntu.SystemImage 0.1 Ubuntu/SystemImage TARGETS MockSystemImage)
tests/mocks/Ubuntu/SystemImage/MockSystemImage.cppnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#include "MockSystemImage.h"
18+
19+MockSystemImage::MockSystemImage(QObject *parent)
20+ : QObject(parent)
21+{
22+}
23+
24+void MockSystemImage::factoryReset()
25+{
26+ Q_EMIT resettingDevice();
27+}
tests/mocks/Ubuntu/SystemImage/MockSystemImage.hnew file+36
@@ -0,0 +1,36 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#ifndef MOCK_SYSTEMIMAGE_H
18+#define MOCK_SYSTEMIMAGE_H
19+
20+#include <QObject>
21+
22+class MockSystemImage : public QObject
23+{
24+ Q_OBJECT
25+ Q_DISABLE_COPY(MockSystemImage)
26+
27+public:
28+ explicit MockSystemImage(QObject *parent = 0);
29+
30+ Q_INVOKABLE void factoryReset();
31+
32+Q_SIGNALS:
33+ void resettingDevice(); // only for mock
34+};
35+
36+#endif // MOCK_SYSTEMIMAGE_H
tests/mocks/Ubuntu/SystemImage/SystemImage.qmltypesnew file+18
@@ -0,0 +1,18 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Ubuntu.SystemImage 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "MockSystemImage"
12+ prototype: "QObject"
13+ exports: ["Ubuntu.SystemImage/SystemImage 0.1"]
14+ exportMetaObjectRevisions: [0]
15+ Signal { name: "resettingDevice" }
16+ Method { name: "factoryReset" }
17+ }
18+}
tests/mocks/Ubuntu/SystemImage/plugin.cppnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#include "plugin.h"
18+#include "MockSystemImage.h"
19+
20+#include <QtQml>
21+
22+static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
23+{
24+ Q_UNUSED(engine)
25+ Q_UNUSED(scriptEngine)
26+ return new MockSystemImage();
27+}
28+
29+void BackendPlugin::registerTypes(const char *uri)
30+{
31+ Q_ASSERT(uri == QLatin1String("Ubuntu.SystemImage"));
32+
33+ qmlRegisterSingletonType<MockSystemImage>(uri, 0, 1, "SystemImage", service_provider);
34+}
tests/mocks/Ubuntu/SystemImage/plugin.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+#ifndef MOCK_SYSTEMIMAGE_PLUGIN_H
18+#define MOCK_SYSTEMIMAGE_PLUGIN_H
19+
20+#include <QQmlExtensionPlugin>
21+
22+class BackendPlugin : public QQmlExtensionPlugin
23+{
24+ Q_OBJECT
25+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26+
27+public:
28+ void registerTypes(const char *uri);
29+};
30+
31+#endif // MOCK_SYSTEMIMAGE_PLUGIN_H
tests/mocks/Ubuntu/SystemImage/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Ubuntu.SystemImage
2+plugin MockSystemImage
3+typeinfo SystemImage.qmltypes
tests/qmltests/Greeter/tst_Lockscreen.qml−2
@@ -319,11 +319,9 @@ Rectangle {
319319 if (data.animation) {
320320 if (data.alphanumeric) {
321321 tryCompare(inputField, "placeholderText", lockscreen.wrongPlaceholderText)
322− tryCompare(inputField, "placeholderText", lockscreen.placeholderText)
323322 } else {
324323 var label = findChild(lockscreen, "pinentryFieldPlaceHolder");
325324 tryCompare(label, "text", lockscreen.wrongPlaceholderText)
326− tryCompare(label, "text", lockscreen.placeholderText)
327325 }
328326 }
329327
tests/qmltests/tst_ShellWithPin.qml+56
@@ -16,8 +16,10 @@
1616
1717import QtQuick 2.0
1818import QtTest 1.0
19+import AccountsService 0.1
1920import GSettings 1.0
2021import LightDM 0.1 as LightDM
22+import Ubuntu.SystemImage 0.1
2123import Unity.Application 0.1
2224import Unity.Test 0.1 as UT
2325import Powerd 0.1
@@ -25,6 +27,7 @@ import Powerd 0.1
2527import "../../qml"
2628
2729Item {
30+ id: root
2831 width: shell.width
2932 height: shell.height
3033
@@ -53,6 +56,12 @@ Item {
5356 signalName: "sessionStarted"
5457 }
5558
59+ SignalSpy {
60+ id: resetSpy
61+ target: SystemImage
62+ signalName: "resettingDevice"
63+ }
64+
5665 UT.UnityTestCase {
5766 name: "ShellWithPin"
5867 when: windowShown
@@ -64,6 +73,8 @@ Item {
6473
6574 function init() {
6675 swipeAwayGreeter()
76+ shell.failedLoginsDelayAttempts = -1
77+ shell.maxFailedLogins = -1
6778 }
6879
6980 function cleanup() {
@@ -111,6 +122,7 @@ Item {
111122 }
112123
113124 function test_login() {
125+ sessionSpy.clear()
114126 tryCompare(sessionSpy, "count", 0)
115127 enterPin("1234")
116128 tryCompare(sessionSpy, "count", 1)
@@ -175,5 +187,49 @@ Item {
175187 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
176188 tryCompare(lockscreen, "shown", true)
177189 }
190+
191+ function test_failedLoginsCount() {
192+ AccountsService.failedLogins = 0
193+
194+ enterPin("1111")
195+ tryCompare(AccountsService, "failedLogins", 1)
196+
197+ enterPin("1234")
198+ tryCompare(AccountsService, "failedLogins", 0)
199+ }
200+
201+ function test_wrongEntries() {
202+ shell.failedLoginsDelayAttempts = 3
203+
204+ var placeHolder = findChild(shell, "pinentryFieldPlaceHolder")
205+ tryCompare(placeHolder, "text", "Enter your passcode")
206+
207+ enterPin("1111")
208+ tryCompare(placeHolder, "text", "Incorrect passcode\nPlease re-enter")
209+
210+ enterPin("1111")
211+ tryCompare(placeHolder, "text", "Incorrect passcode\nPlease re-enter")
212+
213+ enterPin("1111")
214+ tryCompare(placeHolder, "text", "Too many incorrect attempts\nPlease wait")
215+ }
216+
217+ function test_factoryReset() {
218+ shell.maxFailedLogins = 3
219+ resetSpy.clear()
220+
221+ enterPin("1111")
222+ enterPin("1111")
223+ tryCompareFunction(function() {return findChild(root, "infoPopup") !== null}, true)
224+
225+ var dialog = findChild(root, "infoPopup")
226+ var button = findChild(dialog, "infoPopupOkButton")
227+ mouseClick(button, units.gu(1), units.gu(1))
228+ tryCompareFunction(function() {return findChild(root, "infoPopup")}, null)
229+
230+ tryCompare(resetSpy, "count", 0)
231+ enterPin("1111")
232+ tryCompare(resetSpy, "count", 1)
233+ }
178234 }
179235}