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.
548e044Michael Terry
cmake/modules/QmlTest.cmake+2−2
@@ -103,7 +103,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
103 endif()
103 endif()
104
104
105 set(qmltest_command
105 set(qmltest_command
106− env ${qmltest_ENVIRONMENT}
106+ env ${qmltest_ENVIRONMENT} UNITY_TESTING=1
107 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
107 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
108 ${qmltestrunner_imports}
108 ${qmltestrunner_imports}
109 ${ITERATIONS_STRING}
109 ${ITERATIONS_STRING}
@@ -117,7 +117,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
117 set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
117 set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
118 endif()
118 endif()
119 set(qmltest_xvfb_command
119 set(qmltest_xvfb_command
120− env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
120+ env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH} UNITY_TESTING=1
121 xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
121 xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
122 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
122 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
123 ${qmltestrunner_imports}
123 ${qmltestrunner_imports}
cmake/modules/autopilot.cmake+2−2
@@ -2,7 +2,7 @@ add_custom_target(autopilot)
2
2
3function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
3function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
4 add_custom_target(autopilot-${TEST_NAME}
4 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}
6 WORKING_DIRECTORY ${WORKING_DIR}
6 WORKING_DIRECTORY ${WORKING_DIR}
7 DEPENDS fake_install
7 DEPENDS fake_install
8 )
8 )
@@ -14,7 +14,7 @@ function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
14 add_dependencies(autopilot autopilot-${TEST_NAME})
14 add_dependencies(autopilot autopilot-${TEST_NAME})
15
15
16 add_custom_target(autopilot2-${TEST_NAME}
16 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}
18 WORKING_DIRECTORY ${WORKING_DIR}
18 WORKING_DIRECTORY ${WORKING_DIR}
19 DEPENDS fake_install
19 DEPENDS fake_install
20 )
20 )
debian/control+2−1
@@ -165,7 +165,8 @@ Package: unity8-private
165Architecture: any
165Architecture: any
166Multi-Arch: same
166Multi-Arch: same
167Pre-Depends: ${misc:Pre-Depends},
167Pre-Depends: ${misc:Pre-Depends},
168+Depends: accountsservice-ubuntu-schemas,
168−Depends: gsettings-ubuntu-schemas,
169+ gsettings-ubuntu-schemas,
169 libhardware2,
170 libhardware2,
170 libunity-core-6.0-9,
171 libunity-core-6.0-9,
171 pay-service,
172 pay-service,
debian/unity8-private.install−2
@@ -10,5 +10,3 @@ usr/lib/*/unity8/qml/Unity
10usr/lib/*/unity8/qml/Utils
10usr/lib/*/unity8/qml/Utils
11usr/share/accountsservice/interfaces
11usr/share/accountsservice/interfaces
12usr/share/dbus-1/interfaces
12usr/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)
27 m_user(qgetenv("USER")),
27 m_user(qgetenv("USER")),
28 m_demoEdges(false),
28 m_demoEdges(false),
29 m_statsWelcomeScreen(false),
29 m_statsWelcomeScreen(false),
30− m_passwordDisplayHint(Keyboard)
30+ m_passwordDisplayHint(Keyboard),
31+ m_failedLogins(0)
31{
32{
32 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
33 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
33 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
34 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -49,6 +50,7 @@ void AccountsService::setUser(const QString &user)
49 updateBackgroundFile();
50 updateBackgroundFile();
50 updateStatsWelcomeScreen();
51 updateStatsWelcomeScreen();
51 updatePasswordDisplayHint();
52 updatePasswordDisplayHint();
53+ updateFailedLogins();
52}
54}
53
55
54bool AccountsService::demoEdges() const
56bool AccountsService::demoEdges() const
@@ -113,6 +115,26 @@ void AccountsService::updatePasswordDisplayHint()
113 }
115 }
114}
116}
115
117
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+
116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
138void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
117{
139{
118 if (m_user != user) {
140 if (m_user != user) {
@@ -123,6 +145,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
123 if (changed.contains("demo-edges")) {
145 if (changed.contains("demo-edges")) {
124 updateDemoEdges();
146 updateDemoEdges();
125 }
147 }
148+ } else if (interface == "com.canonical.unity.AccountsService.Private") {
149+ if (changed.contains("FailedLogins")) {
150+ updateFailedLogins();
151+ }
126 } else if (interface == "com.ubuntu.touch.AccountsService.SecurityPrivacy") {
152 } else if (interface == "com.ubuntu.touch.AccountsService.SecurityPrivacy") {
127 if (changed.contains("StatsWelcomeScreen")) {
153 if (changed.contains("StatsWelcomeScreen")) {
128 updateStatsWelcomeScreen();
154 updateStatsWelcomeScreen();
plugins/AccountsService/AccountsService.h+9
@@ -45,6 +45,10 @@ class AccountsService: public QObject
45 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
45 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46 READ passwordDisplayHint
46 READ passwordDisplayHint
47 NOTIFY passwordDisplayHintChanged)
47 NOTIFY passwordDisplayHintChanged)
48+ Q_PROPERTY (uint failedLogins
49+ READ failedLogins
50+ WRITE setFailedLogins
51+ NOTIFY failedLoginsChanged)
48
52
49public:
53public:
50 enum PasswordDisplayHint {
54 enum PasswordDisplayHint {
@@ -61,6 +65,8 @@ public:
61 QString backgroundFile() const;
65 QString backgroundFile() const;
62 bool statsWelcomeScreen() const;
66 bool statsWelcomeScreen() const;
63 PasswordDisplayHint passwordDisplayHint() const;
67 PasswordDisplayHint passwordDisplayHint() const;
68+ uint failedLogins() const;
69+ void setFailedLogins(uint failedLogins);
64
70
65Q_SIGNALS:
71Q_SIGNALS:
66 void userChanged();
72 void userChanged();
@@ -68,6 +74,7 @@ Q_SIGNALS:
68 void backgroundFileChanged();
74 void backgroundFileChanged();
69 void statsWelcomeScreenChanged();
75 void statsWelcomeScreenChanged();
70 void passwordDisplayHintChanged();
76 void passwordDisplayHintChanged();
77+ void failedLoginsChanged();
71
78
72private Q_SLOTS:
79private Q_SLOTS:
73 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
80 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -78,6 +85,7 @@ private:
78 void updateBackgroundFile();
85 void updateBackgroundFile();
79 void updateStatsWelcomeScreen();
86 void updateStatsWelcomeScreen();
80 void updatePasswordDisplayHint();
87 void updatePasswordDisplayHint();
88+ void updateFailedLogins();
81
89
82 AccountsServiceDBusAdaptor *m_service;
90 AccountsServiceDBusAdaptor *m_service;
83 QString m_user;
91 QString m_user;
@@ -85,6 +93,7 @@ private:
85 QString m_backgroundFile;
93 QString m_backgroundFile;
86 bool m_statsWelcomeScreen;
94 bool m_statsWelcomeScreen;
87 PasswordDisplayHint m_passwordDisplayHint;
95 PasswordDisplayHint m_passwordDisplayHint;
96+ uint m_failedLogins;
88};
97};
89
98
90#endif
99#endif
plugins/AccountsService/CMakeLists.txt−10
@@ -10,8 +10,6 @@ qt5_use_modules(AccountsService-qml DBus Qml)
10
10
11add_unity8_plugin(AccountsService 0.1 AccountsService TARGETS AccountsService-qml)
11add_unity8_plugin(AccountsService 0.1 AccountsService TARGETS AccountsService-qml)
12
12
13−set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
14−set(POLKIT_DATA_DIR "${CMAKE_INSTALL_PREFIX}/share/polkit-1")
15set(DBUS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/dbus-1/interfaces")
13set(DBUS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/dbus-1/interfaces")
16set(ACCOUNTS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/accountsservice/interfaces")
14set(ACCOUNTS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/accountsservice/interfaces")
17
15
@@ -24,11 +22,3 @@ install(CODE "
24execute_process(COMMAND mkdir -p \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
22execute_process(COMMAND mkdir -p \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
25execute_process(COMMAND ln -sf ../../dbus-1/interfaces/com.canonical.unity.AccountsService.xml \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
23execute_process(COMMAND ln -sf ../../dbus-1/interfaces/com.canonical.unity.AccountsService.xml \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
26 ")
24 ")
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 @@
3
3
4 <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
4 <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
5
5
6− <annotation name="org.freedesktop.Accounts.Authentication.ChangeOwn"
7− value="com.canonical.unity.AccountsService.ModifyOwnUser"/>
8−
9 <annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
6 <annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
10− value="com.canonical.unity.AccountsService.ModifyAnyUser"/>
7+ value="com.ubuntu.AccountsService.GreeterReadAny"/>
11
8
12 <annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
9 <annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
13− value="com.canonical.unity.AccountsService.ModifyAnyUser"/>
10+ value="com.ubuntu.AccountsService.GreeterChangeAny"/>
14
11
12+ <!-- Should have been named DemoEdges, sorry folks. -mterry -->
15 <property name="demo-edges" type="b" access="readwrite">
13 <property name="demo-edges" type="b" access="readwrite">
16 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
14 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
17 </property>
15 </property>
18
16
17+ <!-- Should have been named LauncherItems, sorry folks. -mterry -->
19 <property name="launcher-items" type="aa{sv}" access="readwrite">
18 <property name="launcher-items" type="aa{sv}" access="readwrite">
20 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
19 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
21 </property>
20 </property>
22
21
23 </interface>
22 </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>
24</node>
47</node>
plugins/LightDM/Greeter.cpp+4−1
@@ -17,6 +17,7 @@
17 */
17 */
18
18
19#include "Greeter.h"
19#include "Greeter.h"
20+#include <libintl.h>
20#include <QLightDM/Greeter>
21#include <QLightDM/Greeter>
21
22
22class GreeterPrivate
23class GreeterPrivate
@@ -125,13 +126,15 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
125 Q_D(Greeter);
126 Q_D(Greeter);
126 d->wasPrompted = true;
127 d->wasPrompted = true;
127
128
129+ bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
130+
128 // Strip prompt of any colons at the end
131 // Strip prompt of any colons at the end
129 QString trimmedText = text.trimmed();
132 QString trimmedText = text.trimmed();
130 if (trimmedText.endsWith(":") || trimmedText.endsWith(":")) {
133 if (trimmedText.endsWith(":") || trimmedText.endsWith(":")) {
131 trimmedText.chop(1);
134 trimmedText.chop(1);
132 }
135 }
133
136
134− Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret);
137+ Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
135}
138}
136
139
137void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
140void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
plugins/LightDM/Greeter.h+1−1
@@ -55,7 +55,7 @@ public Q_SLOTS:
55
55
56Q_SIGNALS:
56Q_SIGNALS:
57 void showMessage(const QString &text, bool isError);
57 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);
59 void authenticationComplete();
59 void authenticationComplete();
60 void authenticationUserChanged(const QString &user);
60 void authenticationUserChanged(const QString &user);
61 void isActiveChanged();
61 void isActiveChanged();
plugins/Ubuntu/CMakeLists.txt+1
@@ -1,3 +1,4 @@
1add_subdirectory(Gestures)
1add_subdirectory(Gestures)
2add_subdirectory(DownloadDaemonListener)
2add_subdirectory(DownloadDaemonListener)
3add_subdirectory(Payments)
3add_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 ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"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"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,6 +17,10 @@ msgstr ""
17"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Type: text/plain; charset=UTF-8\n"
18"Content-Transfer-Encoding: 8bit\n"
18"Content-Transfer-Encoding: 8bit\n"
19
19
20+#: plugins/LightDM/Greeter.cpp:129
21+msgid "Password: "
22+msgstr ""
23+
20#: plugins/Unity/Launcher/launcheritem.cpp:43
24#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
25#: plugins/Unity/Launcher/launcheritem.cpp:73
22msgid "Pin shortcut"
26msgid "Pin shortcut"
@@ -88,39 +92,51 @@ msgid ""
88"phone<br><br>Tap on the screen to start"
92"phone<br><br>Tap on the screen to start"
89msgstr ""
93msgstr ""
90
94
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
92msgid "Emergency Call"
104msgid "Emergency Call"
93msgstr ""
105msgstr ""
94
106
95−#: qml/Components/Lockscreen.qml:243
107+#: qml/Components/Lockscreen.qml:268
96msgid "OK"
108msgid "OK"
97msgstr ""
109msgstr ""
98
110
99−#: qml/Components/PassphraseLockscreen.qml:62
111+#: qml/Components/PassphraseLockscreen.qml:61
100#, qt-format
112#, qt-format
101msgid "Hello %1"
113msgid "Hello %1"
102msgstr ""
114msgstr ""
103
115
104−#: qml/Components/PassphraseLockscreen.qml:62
116+#: qml/Components/PassphraseLockscreen.qml:61
105msgid "Hello"
117msgid "Hello"
106msgstr ""
118msgstr ""
107
119
108−#: qml/Components/PinLockscreen.qml:198
120+#: qml/Components/PinLockscreen.qml:197
109msgid "CANCEL"
121msgid "CANCEL"
110msgstr ""
122msgstr ""
111
123
112−#: qml/Components/PinLockscreen.qml:216
124+#: qml/Components/PinLockscreen.qml:215
113msgid "DONE"
125msgid "DONE"
114msgstr ""
126msgstr ""
115
127
116−#: qml/Dash/GenericScopeView.qml:358
128+#: qml/Dash/GenericScopeView.qml:360
117msgid "See less"
129msgid "See less"
118msgstr ""
130msgstr ""
119
131
120−#: qml/Dash/GenericScopeView.qml:358
132+#: qml/Dash/GenericScopeView.qml:360
121msgid "See all"
133msgid "See all"
122msgstr ""
134msgstr ""
123
135
136+#: qml/Dash/GenericScopeView.qml:423 qml/Panel/SearchIndicator.qml:27
137+msgid "Search"
138+msgstr ""
139+
124#: qml/Dash/Previews/PreviewActionCombo.qml:34
140#: qml/Dash/Previews/PreviewActionCombo.qml:34
125msgid "More..."
141msgid "More..."
126msgstr ""
142msgstr ""
@@ -141,15 +157,15 @@ msgstr ""
141msgid "Send"
157msgid "Send"
142msgstr ""
158msgstr ""
143
159
144−#: qml/Dash/ScopesOverview.qml:200
160+#: qml/Dash/ScopesOverview.qml:201
145msgid "Manage Dash"
161msgid "Manage Dash"
146msgstr ""
162msgstr ""
147
163
148−#: qml/Dash/ScopesOverview.qml:405
164+#: qml/Dash/ScopesOverview.qml:408
149msgid "Done"
165msgid "Done"
150msgstr ""
166msgstr ""
151
167
152−#: qml/Dash/ScopesOverview.qml:431
168+#: qml/Dash/ScopesOverview.qml:434
153msgid "Store"
169msgid "Store"
154msgstr ""
170msgstr ""
155
171
@@ -233,11 +249,48 @@ msgstr ""
233msgid "Roaming"
249msgid "Roaming"
234msgstr ""
250msgstr ""
235
251
236−#: qml/Panel/SearchIndicator.qml:27
252+#: qml/Shell.qml:267
237−msgid "Search"
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"
238msgstr ""
263msgstr ""
239
264
240−#: qml/Shell.qml:256
265+#: qml/Shell.qml:270
241#, qt-format
266#, 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."
243msgstr ""
296msgstr ""
qml/Components/Lockscreen.qml+31−8
@@ -57,10 +57,15 @@ Showable {
57
57
58 onRequiredChanged: {
58 onRequiredChanged: {
59 if (required && pinPadLoader.item) {
59 if (required && pinPadLoader.item) {
60− pinPadLoader.item.clear(false);
60+ clear(false)
61 }
61 }
62 }
62 }
63
63
64+ function forceDelay(delay) {
65+ forcedDelayTimer.interval = delay
66+ forcedDelayTimer.start()
67+ }
68+
64 function reset() {
69 function reset() {
65 // This causes the loader below to destry and recreate the source
70 // This causes the loader below to destry and recreate the source
66 pinPadLoader.resetting = true;
71 pinPadLoader.resetting = true;
@@ -71,12 +76,19 @@ Showable {
71 if (pinPadLoader.item) {
76 if (pinPadLoader.item) {
72 pinPadLoader.item.clear(showAnimation);
77 pinPadLoader.item.clear(showAnimation);
73 }
78 }
79+ pinPadLoader.showWrongText = showAnimation
80+ pinPadLoader.waiting = false
74 }
81 }
75
82
76 function showInfoPopup(title, text) {
83 function showInfoPopup(title, text) {
77 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
84 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
78 }
85 }
79
86
87+ Timer {
88+ id: forcedDelayTimer
89+ onTriggered: pinPadLoader.showWrongText = false
90+ }
91+
80 Rectangle {
92 Rectangle {
81 // In case background fails to load or is undefined
93 // In case background fails to load or is undefined
82 id: backgroundBackup
94 id: backgroundBackup
@@ -133,8 +145,6 @@ Showable {
133 }
145 }
134 }
146 }
135
147
136−
137−
138 Loader {
148 Loader {
139 id: pinPadLoader
149 id: pinPadLoader
140 objectName: "pinPadLoader"
150 objectName: "pinPadLoader"
@@ -145,13 +155,24 @@ Showable {
145 verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : -units.gu(4)
155 verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : -units.gu(4)
146 }
156 }
147 property bool resetting: false
157 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")
148
164
149 source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
165 source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
166+ onSourceChanged: {
167+ waiting = false
168+ showWrongText = false
169+ }
150
170
151 Connections {
171 Connections {
152 target: pinPadLoader.item
172 target: pinPadLoader.item
153
173
154 onEntered: {
174 onEntered: {
175+ pinPadLoader.waiting = true
155 root.entered(passphrase);
176 root.entered(passphrase);
156 }
177 }
157
178
@@ -173,17 +194,19 @@ Showable {
173 Binding {
194 Binding {
174 target: pinPadLoader.item
195 target: pinPadLoader.item
175 property: "placeholderText"
196 property: "placeholderText"
197+ value: forcedDelayTimer.running ? pinPadLoader.forcedDelayText :
198+ (pinPadLoader.showWrongText ? root.wrongPlaceholderText :
176− value: root.placeholderText
199+ root.placeholderText)
177 }
200 }
178 Binding {
201 Binding {
179 target: pinPadLoader.item
202 target: pinPadLoader.item
180− property: "wrongPlaceholderText"
203+ property: "username"
181− value: root.wrongPlaceholderText
204+ value: root.username
182 }
205 }
183 Binding {
206 Binding {
184 target: pinPadLoader.item
207 target: pinPadLoader.item
185− property: "username"
208+ property: "entryEnabled"
186− value: root.username
209+ value: !pinPadLoader.waiting && !forcedDelayTimer.running
187 }
210 }
188 }
211 }
189
212
qml/Components/PassphraseLockscreen.qml+3−4
@@ -23,15 +23,14 @@ Item {
23 height: highlightItem.height
23 height: highlightItem.height
24
24
25 property string placeholderText
25 property string placeholderText
26− property string wrongPlaceholderText
27 property string username: ""
26 property string username: ""
27+ property bool entryEnabled: true
28
28
29 signal entered(string passphrase)
29 signal entered(string passphrase)
30 signal cancel()
30 signal cancel()
31
31
32 function clear(playAnimation) {
32 function clear(playAnimation) {
33 pinentryField.text = "";
33 pinentryField.text = "";
34− pinentryField.enabled = true
35 if (playAnimation) {
34 if (playAnimation) {
36 wrongPasswordAnimation.start();
35 wrongPasswordAnimation.start();
37 pinentryField.forceActiveFocus();
36 pinentryField.forceActiveFocus();
@@ -76,11 +75,11 @@ Item {
76 echoMode: TextInput.Password
75 echoMode: TextInput.Password
77 opacity: 0.9
76 opacity: 0.9
78 hasClearButton: false
77 hasClearButton: false
79− placeholderText: wrongPasswordAnimation.running ? root.wrongPlaceholderText : root.placeholderText
78+ enabled: entryEnabled
79+ placeholderText: root.placeholderText
80
80
81 onAccepted: {
81 onAccepted: {
82 if (pinentryField.text) {
82 if (pinentryField.text) {
83− pinentryField.enabled = false;
84 root.entered(pinentryField.text);
83 root.entered(pinentryField.text);
85 }
84 }
86 }
85 }
qml/Components/PinLockscreen.qml+3−4
@@ -25,7 +25,6 @@ Column {
25 spacing: units.gu(3.5)
25 spacing: units.gu(3.5)
26
26
27 property alias placeholderText: pinentryField.placeholderText
27 property alias placeholderText: pinentryField.placeholderText
28− property alias wrongPlaceholderText: pinentryField.wrongPlaceholderText
29 property int padWidth: units.gu(34)
28 property int padWidth: units.gu(34)
30 property int padHeight: units.gu(28)
29 property int padHeight: units.gu(28)
31 property int minPinLength: -1
30 property int minPinLength: -1
@@ -58,7 +57,6 @@ Column {
58 radius: "medium"
57 radius: "medium"
59 property string text: ""
58 property string text: ""
60 property string placeholderText: ""
59 property string placeholderText: ""
61− property string wrongPlaceholderText: ""
62
60
63 function appendChar(character) {
61 function appendChar(character) {
64 if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
62 if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
@@ -90,9 +88,10 @@ Column {
90 id: pinentryFieldPlaceHolder
88 id: pinentryFieldPlaceHolder
91 objectName: "pinentryFieldPlaceHolder"
89 objectName: "pinentryFieldPlaceHolder"
92 anchors.centerIn: parent
90 anchors.centerIn: parent
91+ horizontalAlignment: Text.AlignHCenter
93 color: "#f3f3e7"
92 color: "#f3f3e7"
94 opacity: 0.6
93 opacity: 0.6
95− text: wrongPasswordAnimation.running ? parent.wrongPlaceholderText : parent.placeholderText
94+ text: parent.placeholderText
96 visible: pinentryFieldLabel.text.length == 0
95 visible: pinentryFieldLabel.text.length == 0
97 }
96 }
98
97
@@ -107,7 +106,7 @@ Column {
107 bottom: parent.bottom
106 bottom: parent.bottom
108 bottomMargin: units.gu(1)
107 bottomMargin: units.gu(1)
109 }
108 }
110− visible: !priv.autoConfirm
109+ visible: entryEnabled && !priv.autoConfirm
111 width: height
110 width: height
112 name: "erase"
111 name: "erase"
113 color: "#f3f3e7"
112 color: "#f3f3e7"
qml/Notifications/NotificationMenuItemFactory.qml−1
@@ -122,7 +122,6 @@ Loader {
122
122
123 onEntered: {
123 onEntered: {
124 menuModel.changeState(menuIndex, passphrase);
124 menuModel.changeState(menuIndex, passphrase);
125− entryEnabled = false;
126 }
125 }
127
126
128 onCancel: {
127 onCancel: {
qml/Shell.qml+50−5
@@ -19,7 +19,9 @@ import AccountsService 0.1
19import GSettings 1.0
19import GSettings 1.0
20import Unity.Application 0.1
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
21import Ubuntu.Components 0.1
22+import Ubuntu.Components.Popups 1.0
22import Ubuntu.Gestures 0.1
23import Ubuntu.Gestures 0.1
24+import Ubuntu.SystemImage 0.1
23import Unity.Launcher 0.1
25import Unity.Launcher 0.1
24import Utils 0.1
26import Utils 0.1
25import LightDM 0.1 as LightDM
27import LightDM 0.1 as LightDM
@@ -53,6 +55,10 @@ Item {
53 property bool sideStageEnabled: shell.width >= units.gu(100)
55 property bool sideStageEnabled: shell.width >= units.gu(100)
54 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
56 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55
57
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+
56 function activateApplication(appId) {
62 function activateApplication(appId) {
57 if (ApplicationManager.findApplication(appId)) {
63 if (ApplicationManager.findApplication(appId)) {
58 ApplicationManager.requestFocusApplication(appId);
64 ApplicationManager.requestFocusApplication(appId);
@@ -255,7 +261,15 @@ Item {
255
261
256 onShowPrompt: {
262 onShowPrompt: {
257 if (greeter.narrowMode) {
263 if (greeter.narrowMode) {
264+ var promptText = text.toLowerCase()
265+ if (isDefaultPrompt) {
266+ promptText = lockscreen.alphaNumeric ?
267+ i18n.tr("passphrase") : i18n.tr("passcode")
268+ }
258− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
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")
259 lockscreen.show();
273 lockscreen.show();
260 }
274 }
261 }
275 }
@@ -270,13 +284,40 @@ Item {
270 }
284 }
271
285
272 onAuthenticationComplete: {
286 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+
273 if (LightDM.Greeter.promptless) {
294 if (LightDM.Greeter.promptless) {
274 return;
295 return;
275 }
296 }
297+
276 if (LightDM.Greeter.authenticated) {
298 if (LightDM.Greeter.authenticated) {
277 lockscreen.hide();
299 lockscreen.hide();
278 greeter.login();
300 greeter.login();
279 } else {
301 } 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+
280 lockscreen.clear(true);
321 lockscreen.clear(true);
281 if (greeter.narrowMode) {
322 if (greeter.narrowMode) {
282 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
323 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
@@ -311,6 +352,14 @@ Item {
311 }
352 }
312
353
313 property bool fullyShown: showProgress === 1.0
354 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+
314 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
363 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
315 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
364 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
316
365
@@ -350,10 +399,6 @@ Item {
350 if (greeter.narrowMode) {
399 if (greeter.narrowMode) {
351 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
400 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
352 }
401 }
353− if (!LightDM.Greeter.authenticated) {
354− lockscreen.reset();
355− lockscreen.show();
356− }
357 greeter.fakeActiveForApp = "";
402 greeter.fakeActiveForApp = "";
358 greeter.forceActiveFocus();
403 greeter.forceActiveFocus();
359 }
404 }
tests/mocks/AccountsService/AccountsService.cpp+13−1
@@ -23,7 +23,8 @@
23AccountsService::AccountsService(QObject* parent)
23AccountsService::AccountsService(QObject* parent)
24 : QObject(parent),
24 : QObject(parent),
25 m_backgroundFile(qmlDirectory() + "graphics/phone_background.jpg"),
25 m_backgroundFile(qmlDirectory() + "graphics/phone_background.jpg"),
26− m_statsWelcomeScreen(true)
26+ m_statsWelcomeScreen(true),
27+ m_failedLogins(0)
27{
28{
28}
29}
29
30
@@ -78,3 +79,14 @@ AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() cons
78 else
79 else
79 return PasswordDisplayHint::Keyboard;
80 return PasswordDisplayHint::Keyboard;
80}
81}
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
47 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
47 Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48 READ passwordDisplayHint
48 READ passwordDisplayHint
49 NOTIFY passwordDisplayHintChanged)
49 NOTIFY passwordDisplayHintChanged)
50+ Q_PROPERTY (uint failedLogins
51+ READ failedLogins
52+ WRITE setFailedLogins
53+ NOTIFY failedLoginsChanged)
50
54
51public:
55public:
52 enum PasswordDisplayHint {
56 enum PasswordDisplayHint {
@@ -65,6 +69,8 @@ public:
65 bool statsWelcomeScreen() const;
69 bool statsWelcomeScreen() const;
66 void setStatsWelcomeScreen(bool statsWelcomeScreen);
70 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67 PasswordDisplayHint passwordDisplayHint() const;
71 PasswordDisplayHint passwordDisplayHint() const;
72+ uint failedLogins() const;
73+ void setFailedLogins(uint failedLogins);
68
74
69Q_SIGNALS:
75Q_SIGNALS:
70 void userChanged();
76 void userChanged();
@@ -72,11 +78,13 @@ Q_SIGNALS:
72 void backgroundFileChanged();
78 void backgroundFileChanged();
73 void statsWelcomeScreenChanged();
79 void statsWelcomeScreenChanged();
74 void passwordDisplayHintChanged();
80 void passwordDisplayHintChanged();
81+ void failedLoginsChanged();
75
82
76private:
83private:
77 QString m_backgroundFile;
84 QString m_backgroundFile;
78 QString m_user;
85 QString m_user;
79 bool m_statsWelcomeScreen;
86 bool m_statsWelcomeScreen;
87+ uint m_failedLogins;
80};
88};
81
89
82#endif
90#endif
tests/mocks/LightDM/Greeter.cpp+11
@@ -19,6 +19,7 @@
19#include "Greeter.h"
19#include "Greeter.h"
20#include "GreeterPrivate.h"
20#include "GreeterPrivate.h"
21#include <QtCore/QCoreApplication>
21#include <QtCore/QCoreApplication>
22+#include <QTimer>
22
23
23namespace QLightDM
24namespace QLightDM
24{
25{
@@ -164,4 +165,14 @@ void Greeter::respond(const QString &response)
164 d->handleRespond(response);
165 d->handleRespond(response);
165}
166}
166
167
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+
167}
178}
tests/mocks/LightDM/Greeter.h+3
@@ -96,6 +96,9 @@ Q_SIGNALS:
96 void authenticationComplete();
96 void authenticationComplete();
97 void autologinTimerExpired();
97 void autologinTimerExpired();
98
98
99+protected:
100+ void sendAuthenticationComplete();
101+
99private:
102private:
100 GreeterPrivate *d_ptr;
103 GreeterPrivate *d_ptr;
101 Q_DECLARE_PRIVATE(Greeter)
104 Q_DECLARE_PRIVATE(Greeter)
tests/mocks/LightDM/full/GreeterPrivate.cpp+4−4
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
54 authenticated = true;
54 authenticated = true;
55 Q_EMIT q->authenticationComplete();
55 Q_EMIT q->authenticationComplete();
56 } else if (authenticationUser == "has-pin"){
56 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password: ", Greeter::PromptTypeSecret);
58 } else if (authenticationUser == "auth-error") {
58 } else if (authenticationUser == "auth-error") {
59 authenticated = false;
59 authenticated = false;
60 Q_EMIT q->authenticationComplete();
60 Q_EMIT q->authenticationComplete();
@@ -78,11 +78,11 @@ void GreeterPrivate::handleRespond(const QString &response)
78 Q_EMIT q->showPrompt("otp", Greeter::PromptTypeQuestion);
78 Q_EMIT q->showPrompt("otp", Greeter::PromptTypeQuestion);
79 } else {
79 } else {
80 authenticated = false;
80 authenticated = false;
81− Q_EMIT q->authenticationComplete();
81+ q->sendAuthenticationComplete();
82 }
82 }
83 } else {
83 } else {
84 authenticated = (response == "otp");
84 authenticated = (response == "otp");
85− Q_EMIT q->authenticationComplete();
85+ q->sendAuthenticationComplete();
86 }
86 }
87 return;
87 return;
88 }
88 }
@@ -92,7 +92,7 @@ void GreeterPrivate::handleRespond(const QString &response)
92 } else {
92 } else {
93 authenticated = (response == "password");
93 authenticated = (response == "password");
94 }
94 }
95− Q_EMIT q->authenticationComplete();
95+ q->sendAuthenticationComplete();
96}
96}
97
97
98}
98}
tests/mocks/LightDM/single-passphrase/GreeterPrivate.cpp+2−9
@@ -19,8 +19,6 @@
19#include "../Greeter.h"
19#include "../Greeter.h"
20#include "../GreeterPrivate.h"
20#include "../GreeterPrivate.h"
21
21
22−#include <QDebug>
23−
24namespace QLightDM
22namespace QLightDM
25{
23{
26
24
@@ -34,20 +32,15 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
34void GreeterPrivate::handleAuthenticate()
32void GreeterPrivate::handleAuthenticate()
35{
33{
36 Q_Q(Greeter);
34 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);
41}
36}
42
37
43void GreeterPrivate::handleRespond(const QString &response)
38void GreeterPrivate::handleRespond(const QString &response)
44{
39{
45 Q_Q(Greeter);
40 Q_Q(Greeter);
46
41
47−
48 authenticated = (response == "password");
42 authenticated = (response == "password");
49− qDebug() << "responding" << response << authenticated;
43+ q->sendAuthenticationComplete();
50− Q_EMIT q->authenticationComplete();
51}
44}
52
45
53}
46}
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+3−2
@@ -32,14 +32,15 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
32void GreeterPrivate::handleAuthenticate()
32void GreeterPrivate::handleAuthenticate()
33{
33{
34 Q_Q(Greeter);
34 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password: ", Greeter::PromptTypeSecret);
36}
36}
37
37
38void GreeterPrivate::handleRespond(const QString &response)
38void GreeterPrivate::handleRespond(const QString &response)
39{
39{
40 Q_Q(Greeter);
40 Q_Q(Greeter);
41+
41 authenticated = (response == "1234");
42 authenticated = (response == "1234");
42− Q_EMIT q->authenticationComplete();
43+ q->sendAuthenticationComplete();
43}
44}
44
45
45}
46}
tests/mocks/Ubuntu/CMakeLists.txt+1
@@ -1,4 +1,5 @@
1add_subdirectory(DownloadDaemonListener)
1add_subdirectory(DownloadDaemonListener)
2add_subdirectory(Payments)
2add_subdirectory(Payments)
3+add_subdirectory(SystemImage)
3add_subdirectory(Telephony)
4add_subdirectory(Telephony)
4add_subdirectory(Thumbnailer)
5add_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 {
319 if (data.animation) {
319 if (data.animation) {
320 if (data.alphanumeric) {
320 if (data.alphanumeric) {
321 tryCompare(inputField, "placeholderText", lockscreen.wrongPlaceholderText)
321 tryCompare(inputField, "placeholderText", lockscreen.wrongPlaceholderText)
322− tryCompare(inputField, "placeholderText", lockscreen.placeholderText)
323 } else {
322 } else {
324 var label = findChild(lockscreen, "pinentryFieldPlaceHolder");
323 var label = findChild(lockscreen, "pinentryFieldPlaceHolder");
325 tryCompare(label, "text", lockscreen.wrongPlaceholderText)
324 tryCompare(label, "text", lockscreen.wrongPlaceholderText)
326− tryCompare(label, "text", lockscreen.placeholderText)
327 }
325 }
328 }
326 }
329
327
tests/qmltests/tst_ShellWithPin.qml+56
@@ -16,8 +16,10 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19+import AccountsService 0.1
19import GSettings 1.0
20import GSettings 1.0
20import LightDM 0.1 as LightDM
21import LightDM 0.1 as LightDM
22+import Ubuntu.SystemImage 0.1
21import Unity.Application 0.1
23import Unity.Application 0.1
22import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
23import Powerd 0.1
25import Powerd 0.1
@@ -25,6 +27,7 @@ import Powerd 0.1
25import "../../qml"
27import "../../qml"
26
28
27Item {
29Item {
30+ id: root
28 width: shell.width
31 width: shell.width
29 height: shell.height
32 height: shell.height
30
33
@@ -53,6 +56,12 @@ Item {
53 signalName: "sessionStarted"
56 signalName: "sessionStarted"
54 }
57 }
55
58
59+ SignalSpy {
60+ id: resetSpy
61+ target: SystemImage
62+ signalName: "resettingDevice"
63+ }
64+
56 UT.UnityTestCase {
65 UT.UnityTestCase {
57 name: "ShellWithPin"
66 name: "ShellWithPin"
58 when: windowShown
67 when: windowShown
@@ -64,6 +73,8 @@ Item {
64
73
65 function init() {
74 function init() {
66 swipeAwayGreeter()
75 swipeAwayGreeter()
76+ shell.failedLoginsDelayAttempts = -1
77+ shell.maxFailedLogins = -1
67 }
78 }
68
79
69 function cleanup() {
80 function cleanup() {
@@ -111,6 +122,7 @@ Item {
111 }
122 }
112
123
113 function test_login() {
124 function test_login() {
125+ sessionSpy.clear()
114 tryCompare(sessionSpy, "count", 0)
126 tryCompare(sessionSpy, "count", 0)
115 enterPin("1234")
127 enterPin("1234")
116 tryCompare(sessionSpy, "count", 1)
128 tryCompare(sessionSpy, "count", 1)
@@ -175,5 +187,49 @@ Item {
175 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
187 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
176 tryCompare(lockscreen, "shown", true)
188 tryCompare(lockscreen, "shown", true)
177 }
189 }
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+ }
178 }
234 }
179}
235}