on xenial help sign in
Merge in locking-hash
be06bf0Michael Terry
debian/control+1
@@ -19,6 +19,7 @@ Build-Depends: cmake,
19 libgsettings-qt-dev,
19 libgsettings-qt-dev,
20 libhardware-dev,
20 libhardware-dev,
21 libhud-client2-dev,
21 libhud-client2-dev,
22+ libpam0g-dev,
22 libpay2-dev,
23 libpay2-dev,
23 libpulse-dev,
24 libpulse-dev,
24 libqmenumodel-dev (>= 0.2.8),
25 libqmenumodel-dev (>= 0.2.8),
plugins/AccountsService/AccountsService.cpp+21−1
@@ -26,7 +26,8 @@ AccountsService::AccountsService(QObject* parent)
26 m_service(new AccountsServiceDBusAdaptor(this)),
26 m_service(new AccountsServiceDBusAdaptor(this)),
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{
31{
31 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
33 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -47,6 +48,7 @@ void AccountsService::setUser(const QString &user)
47 updateDemoEdges();
48 updateDemoEdges();
48 updateBackgroundFile();
49 updateBackgroundFile();
49 updateStatsWelcomeScreen();
50 updateStatsWelcomeScreen();
51+ updatePasswordDisplayHint();
50}
52}
51
53
52bool AccountsService::demoEdges() const
54bool AccountsService::demoEdges() const
@@ -70,6 +72,11 @@ bool AccountsService::statsWelcomeScreen() const
70 return m_statsWelcomeScreen;
72 return m_statsWelcomeScreen;
71}
73}
72
74
75+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
76+{
77+ return m_passwordDisplayHint;
78+}
79+
73void AccountsService::updateDemoEdges()
80void AccountsService::updateDemoEdges()
74{
81{
75 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
82 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
@@ -97,6 +104,15 @@ void AccountsService::updateStatsWelcomeScreen()
97 }
104 }
98}
105}
99
106
107+void AccountsService::updatePasswordDisplayHint()
108+{
109+ PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)m_service->getUserProperty(m_user, "com.ubuntu.AccountsService.SecurityPrivacy", "PasswordDisplayHint").toInt();
110+ if (m_passwordDisplayHint != passwordDisplayHint) {
111+ m_passwordDisplayHint = passwordDisplayHint;
112+ Q_EMIT passwordDisplayHintChanged();
113+ }
114+}
115+
100void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
101{
117{
102 if (m_user != user) {
118 if (m_user != user) {
@@ -111,6 +127,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
111 if (changed.contains("StatsWelcomeScreen")) {
127 if (changed.contains("StatsWelcomeScreen")) {
112 updateStatsWelcomeScreen();
128 updateStatsWelcomeScreen();
113 }
129 }
130+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
131+ if (changed.contains("PasswordDisplayHint")) {
132+ updatePasswordDisplayHint();
133+ }
114 }
134 }
115}
135}
116
136
plugins/AccountsService/AccountsService.h+13
@@ -27,6 +27,7 @@ class AccountsServiceDBusAdaptor;
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -41,8 +42,16 @@ class AccountsService: public QObject
41 Q_PROPERTY (bool statsWelcomeScreen
42 Q_PROPERTY (bool statsWelcomeScreen
42 READ statsWelcomeScreen
43 READ statsWelcomeScreen
43 NOTIFY statsWelcomeScreenChanged)
44 NOTIFY statsWelcomeScreenChanged)
45+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46+ READ passwordDisplayHint
47+ NOTIFY passwordDisplayHintChanged)
44
48
45public:
49public:
50+ enum PasswordDisplayHint {
51+ Keyboard,
52+ Numeric,
53+ };
54+
46 explicit AccountsService(QObject *parent = 0);
55 explicit AccountsService(QObject *parent = 0);
47
56
48 QString user() const;
57 QString user() const;
@@ -51,12 +60,14 @@ public:
51 void setDemoEdges(bool demoEdges);
60 void setDemoEdges(bool demoEdges);
52 QString backgroundFile() const;
61 QString backgroundFile() const;
53 bool statsWelcomeScreen() const;
62 bool statsWelcomeScreen() const;
63+ PasswordDisplayHint passwordDisplayHint() const;
54
64
55Q_SIGNALS:
65Q_SIGNALS:
56 void userChanged();
66 void userChanged();
57 void demoEdgesChanged();
67 void demoEdgesChanged();
58 void backgroundFileChanged();
68 void backgroundFileChanged();
59 void statsWelcomeScreenChanged();
69 void statsWelcomeScreenChanged();
70+ void passwordDisplayHintChanged();
60
71
61private Q_SLOTS:
72private Q_SLOTS:
62 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
73 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -66,12 +77,14 @@ private:
66 void updateDemoEdges();
77 void updateDemoEdges();
67 void updateBackgroundFile();
78 void updateBackgroundFile();
68 void updateStatsWelcomeScreen();
79 void updateStatsWelcomeScreen();
80+ void updatePasswordDisplayHint();
69
81
70 AccountsServiceDBusAdaptor *m_service;
82 AccountsServiceDBusAdaptor *m_service;
71 QString m_user;
83 QString m_user;
72 bool m_demoEdges;
84 bool m_demoEdges;
73 QString m_backgroundFile;
85 QString m_backgroundFile;
74 bool m_statsWelcomeScreen;
86 bool m_statsWelcomeScreen;
87+ PasswordDisplayHint m_passwordDisplayHint;
75};
88};
76
89
77#endif
90#endif
plugins/AccountsService/plugin.cpp+1
@@ -33,5 +33,6 @@ void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35 qDBusRegisterMetaType<QList<QVariantMap>>();
35 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37}
38}
plugins/LightDM/plugin.cpp+2
@@ -69,6 +69,8 @@ void LightDMPlugin::registerTypes(const char *uri)
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
70
70
71 Q_ASSERT(uri == QLatin1String("LightDM"));
71 Q_ASSERT(uri == QLatin1String("LightDM"));
72+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
73+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
72 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
74 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
73 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
75 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
74 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
76 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
qml/Components/PassphraseLockscreen.qml+3
@@ -31,8 +31,10 @@ Item {
31
31
32 function clear(playAnimation) {
32 function clear(playAnimation) {
33 pinentryField.text = "";
33 pinentryField.text = "";
34+ pinentryField.enabled = true
34 if (playAnimation) {
35 if (playAnimation) {
35 wrongPasswordAnimation.start();
36 wrongPasswordAnimation.start();
37+ pinentryField.forceActiveFocus();
36 } else {
38 } else {
37 pinentryField.focus = false
39 pinentryField.focus = false
38 }
40 }
@@ -78,6 +80,7 @@ Item {
78
80
79 onAccepted: {
81 onAccepted: {
80 if (pinentryField.text) {
82 if (pinentryField.text) {
83+ pinentryField.enabled = false;
81 root.entered(pinentryField.text);
84 root.entered(pinentryField.text);
82 }
85 }
83 }
86 }
qml/Shell.qml+27−21
@@ -490,6 +490,7 @@ FocusScope {
490 width: parent.width
490 width: parent.width
491 height: parent.height - panel.panelHeight
491 height: parent.height - panel.panelHeight
492 background: shell.background
492 background: shell.background
493+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
493 minPinLength: 4
494 minPinLength: 4
494 maxPinLength: 4
495 maxPinLength: 4
495
496
@@ -504,7 +505,7 @@ FocusScope {
504 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
505 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
505
506
506 Component.onCompleted: {
507 Component.onCompleted: {
507− if (LightDM.Users.count == 1) {
508+ if (greeter.narrowMode) {
508 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
509 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
509 }
510 }
510 }
511 }
@@ -516,14 +517,17 @@ FocusScope {
516 onShowGreeter: greeter.show()
517 onShowGreeter: greeter.show()
517
518
518 onShowPrompt: {
519 onShowPrompt: {
519− if (LightDM.Users.count == 1) {
520+ if (greeter.narrowMode) {
520− // TODO: There's no better way for now to determine if its a PIN or a passphrase.
521− if (text == "PIN") {
522− lockscreen.alphaNumeric = false
523− } else {
524− lockscreen.alphaNumeric = true
525− }
526− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
521+ lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
522+ lockscreen.show();
523+ }
524+ }
525+
526+ onPromptlessChanged: {
527+ if (LightDM.Greeter.promptless) {
528+ lockscreen.hide()
529+ } else {
530+ lockscreen.reset();
527 lockscreen.show();
531 lockscreen.show();
528 }
532 }
529 }
533 }
@@ -537,6 +541,9 @@ FocusScope {
537 greeter.login();
541 greeter.login();
538 } else {
542 } else {
539 lockscreen.clear(true);
543 lockscreen.clear(true);
544+ if (greeter.narrowMode) {
545+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
546+ }
540 }
547 }
541 }
548 }
542 }
549 }
@@ -590,25 +597,24 @@ FocusScope {
590
597
591 function login() {
598 function login() {
592 enabled = false;
599 enabled = false;
593− LightDM.Greeter.startSessionSync();
600+ if (LightDM.Greeter.startSessionSync()) {
594− sessionStarted();
601+ sessionStarted();
595− greeter.hide();
602+ greeter.hide();
596− lockscreen.hide();
603+ lockscreen.hide();
597− launcher.hide();
604+ launcher.hide();
605+ }
598 enabled = true;
606 enabled = true;
599 }
607 }
600
608
601 onShownChanged: {
609 onShownChanged: {
602 if (shown) {
610 if (shown) {
603− lockscreen.reset();
611+ if (greeter.narrowMode) {
604− if (!LightDM.Greeter.promptless) {
605− lockscreen.show();
606− }
607− // If there is only one user, we start authenticating with that one here.
608− // If there are more users, the Greeter will handle that
609− if (LightDM.Users.count == 1) {
610 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
612 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
611 }
613 }
614+ if (!LightDM.Greeter.promptless) {
615+ lockscreen.reset();
616+ lockscreen.show();
617+ }
612 greeter.fakeActiveForApp = "";
618 greeter.fakeActiveForApp = "";
613 greeter.forceActiveFocus();
619 greeter.forceActiveFocus();
614 }
620 }
run.sh+15−6
@@ -8,6 +8,7 @@ GDB=false
8FAKE=false
8FAKE=false
9PINLOCK=false
9PINLOCK=false
10KEYLOCK=false
10KEYLOCK=false
11+USE_MOCKS=false
11MOUSE_TOUCH=true
12MOUSE_TOUCH=true
12
13
13usage() {
14usage() {
@@ -31,9 +32,9 @@ eval set -- "$ARGS"
31while [ $# -gt 0 ]
32while [ $# -gt 0 ]
32do
33do
33 case "$1" in
34 case "$1" in
34− -f|--fake) FAKE=true;;
35+ -f|--fake) FAKE=true; USE_MOCKS=true;;
35− -p|--pinlock) PINLOCK=true;;
36+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
36− -k|--keylock) KEYLOCK=true;;
37+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
37 -g|--gdb) GDB=true;;
38 -g|--gdb) GDB=true;;
38 -h|--help) usage;;
39 -h|--help) usage;;
39 -m|--nomousetouch) MOUSE_TOUCH=false;;
40 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -43,20 +44,28 @@ do
43done
44done
44
45
45if $FAKE; then
46if $FAKE; then
46− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
48fi
48fi
49
49
50if $PINLOCK; then
50if $PINLOCK; then
51− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
52 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
51 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
53fi
52fi
54
53
55if $KEYLOCK; then
54if $KEYLOCK; then
56− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
57 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
55 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
58fi
56fi
59
57
58+if $USE_MOCKS; then
59+ rm -f $PWD/builddir/nonmirplugins/LightDM # undo symlink (from below) for cleanliness
60+ export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
61+else
62+ # Still fake no-login user for convenience (it's annoying to be prompted for your password when testing)
63+ # And in particular, just link our LightDM mock into the nonmirplugins folder. We don't want the rest of
64+ # our plugins to be used.
65+ ln -s $PWD/builddir/tests/mocks/LightDM $PWD/builddir/nonmirplugins/
66+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
67+fi
68+
60QML_PHONE_SHELL_ARGS=""
69QML_PHONE_SHELL_ARGS=""
61if $MOUSE_TOUCH; then
70if $MOUSE_TOUCH; then
62 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
71 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
tests/mocks/AccountsService/AccountsService.cpp+12−2
@@ -29,12 +29,14 @@ AccountsService::AccountsService(QObject* parent)
29
29
30QString AccountsService::user() const
30QString AccountsService::user() const
31{
31{
32− return "testuser";
32+ return m_user;
33}
33}
34
34
35void AccountsService::setUser(const QString &user)
35void AccountsService::setUser(const QString &user)
36{
36{
37− Q_UNUSED(user)
37+ m_user = user;
38+ Q_EMIT userChanged();
39+ Q_EMIT passwordDisplayHintChanged();
38}
40}
39
41
40bool AccountsService::demoEdges() const
42bool AccountsService::demoEdges() const
@@ -68,3 +70,11 @@ void AccountsService::setStatsWelcomeScreen(bool statsWelcomeScreen)
68 m_statsWelcomeScreen = statsWelcomeScreen;
70 m_statsWelcomeScreen = statsWelcomeScreen;
69 statsWelcomeScreenChanged();
71 statsWelcomeScreenChanged();
70}
72}
73+
74+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
75+{
76+ if (m_user == "has-pin")
77+ return PasswordDisplayHint::Numeric;
78+ else
79+ return PasswordDisplayHint::Keyboard;
80+}
tests/mocks/AccountsService/AccountsService.h+12
@@ -27,6 +27,7 @@
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -43,8 +44,16 @@ class AccountsService: public QObject
43 READ statsWelcomeScreen
44 READ statsWelcomeScreen
44 WRITE setStatsWelcomeScreen // only available in mock
45 WRITE setStatsWelcomeScreen // only available in mock
45 NOTIFY statsWelcomeScreenChanged)
46 NOTIFY statsWelcomeScreenChanged)
47+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48+ READ passwordDisplayHint
49+ NOTIFY passwordDisplayHintChanged)
46
50
47public:
51public:
52+ enum PasswordDisplayHint {
53+ Keyboard,
54+ Numeric,
55+ };
56+
48 explicit AccountsService(QObject *parent = 0);
57 explicit AccountsService(QObject *parent = 0);
49
58
50 QString user() const;
59 QString user() const;
@@ -55,15 +64,18 @@ public:
55 void setBackgroundFile(const QString &backgroundFile);
64 void setBackgroundFile(const QString &backgroundFile);
56 bool statsWelcomeScreen() const;
65 bool statsWelcomeScreen() const;
57 void setStatsWelcomeScreen(bool statsWelcomeScreen);
66 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67+ PasswordDisplayHint passwordDisplayHint() const;
58
68
59Q_SIGNALS:
69Q_SIGNALS:
60 void userChanged();
70 void userChanged();
61 void demoEdgesChanged();
71 void demoEdgesChanged();
62 void backgroundFileChanged();
72 void backgroundFileChanged();
63 void statsWelcomeScreenChanged();
73 void statsWelcomeScreenChanged();
74+ void passwordDisplayHintChanged();
64
75
65private:
76private:
66 QString m_backgroundFile;
77 QString m_backgroundFile;
78+ QString m_user;
67 bool m_statsWelcomeScreen;
79 bool m_statsWelcomeScreen;
68};
80};
69
81
tests/mocks/AccountsService/CMakeLists.txt+4−1
@@ -5,4 +5,7 @@ add_library(MockAccountsService-qml MODULE
5
5
6qt5_use_modules(MockAccountsService-qml DBus Qml)
6qt5_use_modules(MockAccountsService-qml DBus Qml)
7
7
8−add_unity8_mock(AccountsService 0.1 AccountsService TARGETS MockAccountsService-qml)
8+add_unity8_mock(AccountsService 0.1 AccountsService
9+ PREFIX mocks
10+ TARGETS MockAccountsService-qml
11+ )
tests/mocks/AccountsService/plugin.cpp+1
@@ -32,5 +32,6 @@ static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
32void AccountsServicePlugin::registerTypes(const char *uri)
32void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
35 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36}
37}
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
24namespace QLightDM
24namespace QLightDM
25{
25{
26class Greeter;
26class Greeter;
27+class GreeterImpl;
27
28
28class GreeterPrivate
29class GreeterPrivate
29{
30{
@@ -40,6 +41,7 @@ public:
40 void handleRespond(const QString &response);
41 void handleRespond(const QString &response);
41
42
42protected:
43protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
43 Greeter * const q_ptr;
45 Greeter * const q_ptr;
44
46
45private:
47private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
12
12
13include_directories(
13include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
14 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15)
16)
16
17
17target_link_libraries(MockLightDM-demo
18target_link_libraries(MockLightDM-demo
18 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
20+ -lpam
19− )
21+)
20
22
21−qt5_use_modules(MockLightDM-demo Gui)
23+qt5_use_modules(MockLightDM-demo Concurrent Gui)
tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30
@@ -18,53 +18,225 @@
18
18
19#include "../Greeter.h"
19#include "../Greeter.h"
20#include "../GreeterPrivate.h"
20#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
21+#include <QFuture>
22−#include <QtCore/QSettings>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
23
28
24namespace QLightDM
29namespace QLightDM
25{
30{
26
31
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ typedef QFutureInterface<QString> ResponseFuture;
37+
38+public:
39+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
40+ : QObject(parent),
41+ greeter(parent),
42+ greeterPrivate(greeterPrivate),
43+ pamHandle(NULL)
44+ {
45+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
46+
47+ connect(&futureWatcher, SIGNAL(finished()),
48+ this, SLOT(finishPam()));
49+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
50+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
51+ // This next connect is how we pass ResponseFutures between threads
52+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
53+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
54+ }
55+
56+ void start(QString username)
57+ {
58+ // Clear out any existing PAM interactions first (we can't simply
59+ // cancel our QFuture because QtConcurrent::run doesn't support cancel)
60+ if (pamHandle != NULL) {
61+ pam_handle *handle = pamHandle;
62+ pamHandle = NULL; // to disable normal finishPam() handling
63+ while (respond(QString())); // clear our local queue of QFutures
64+ pam_end(handle, PAM_CONV_ERR);
65+ }
66+
67+ // Now actually start a new conversation with PAM
68+ pam_conv conversation;
69+ conversation.conv = converseWithPam;
70+ conversation.appdata_ptr = static_cast<void*>(this);
71+
72+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
73+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
74+ } else {
75+ greeterPrivate->authenticated = false;
76+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
77+ Q_EMIT greeter->authenticationComplete();
78+ }
79+ }
80+
81+ static int authenticateWithPam(pam_handle* pamHandle)
82+ {
83+ int pamStatus = pam_authenticate(pamHandle, 0);
84+ if (pamStatus == PAM_SUCCESS) {
85+ pamStatus = pam_acct_mgmt(pamHandle, 0);
86+ }
87+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
88+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
89+ }
90+ if (pamStatus == PAM_SUCCESS) {
91+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
92+ }
93+ return pamStatus;
94+ }
95+
96+ static int converseWithPam(int num_msg, const pam_message** msg,
97+ pam_response** resp, void* appdata_ptr)
98+ {
99+ if (num_msg <= 0)
100+ return PAM_CONV_ERR;
101+
102+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
103+ if (!tmp_response)
104+ return PAM_CONV_ERR;
105+
106+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
107+
108+ int count;
109+ QVector<ResponseFuture> responses;
110+
111+ for (count = 0; count < num_msg; ++count)
112+ {
113+ switch (msg[count]->msg_style)
114+ {
115+ case PAM_PROMPT_ECHO_ON:
116+ {
117+ QString message(msg[count]->msg);
118+ responses.append(ResponseFuture());
119+ responses.last().reportStarted();
120+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
121+ break;
122+ }
123+ case PAM_PROMPT_ECHO_OFF:
124+ {
125+ QString message(msg[count]->msg);
126+ responses.append(ResponseFuture());
127+ responses.last().reportStarted();
128+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
129+ break;
130+ }
131+ case PAM_TEXT_INFO:
132+ {
133+ QString message(msg[count]->msg);
134+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
135+ break;
136+ }
137+ default:
138+ {
139+ QString message(msg[count]->msg);
140+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
141+ break;
142+ }
143+ }
144+ }
145+
146+ int i = 0;
147+ bool raise_error = false;
148+
149+ for (auto &response : responses)
150+ {
151+ pam_response* resp_item = &tmp_response[i++];
152+ resp_item->resp_retcode = 0;
153+ resp_item->resp = strdup(response.future().result().toUtf8());
154+
155+ if (!resp_item->resp)
156+ {
157+ raise_error = true;
158+ break;
159+ }
160+ }
161+
162+ if (raise_error)
163+ {
164+ for (int i = 0; i < count; ++i)
165+ free(tmp_response[i].resp);
166+
167+ free(tmp_response);
168+ return PAM_CONV_ERR;
169+ }
170+ else
171+ {
172+ *resp = tmp_response;
173+ return PAM_SUCCESS;
174+ }
175+ }
176+
177+public Q_SLOTS:
178+ bool respond(QString response)
179+ {
180+ if (!futures.isEmpty()) {
181+ futures.dequeue().reportFinished(&response);
182+ return true;
183+ } else {
184+ return false;
185+ }
186+ }
187+
188+Q_SIGNALS:
189+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
190+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
191+
192+private Q_SLOTS:
193+ void finishPam()
194+ {
195+ if (pamHandle == NULL) {
196+ return;
197+ }
198+
199+ int pamStatus = futureWatcher.result();
200+
201+ pam_end(pamHandle, pamStatus);
202+ pamHandle = NULL;
203+
204+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
205+ Q_EMIT greeter->authenticationComplete();
206+ }
207+
208+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
209+ {
210+ futures.enqueue(future);
211+ Q_EMIT greeter->showPrompt(text, type);
212+ }
213+
214+private:
215+ Greeter *greeter;
216+ GreeterPrivate *greeterPrivate;
217+ pam_handle* pamHandle;
218+ QFutureWatcher<int> futureWatcher;
219+ QQueue<ResponseFuture> futures;
220+};
221+
27GreeterPrivate::GreeterPrivate(Greeter* parent)
222GreeterPrivate::GreeterPrivate(Greeter* parent)
28 : authenticated(false),
223 : authenticated(false),
29 authenticationUser(),
224 authenticationUser(),
225+ m_impl(new GreeterImpl(parent, this)),
30 q_ptr(parent)
226 q_ptr(parent)
31{
227{
32}
228}
33
229
34void GreeterPrivate::handleAuthenticate()
230void GreeterPrivate::handleAuthenticate()
35{
231{
36− Q_Q(Greeter);
37−
38− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
39− settings.beginGroup(authenticationUser);
232+ m_impl->start(authenticationUser);
40− QVariant password = settings.value("password", "none");
41−
42− if (password == "pin") {
43− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
44− } else if (password == "keyboard") {
45− Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
46− } else {
47− authenticated = true;
48− Q_EMIT q->authenticationComplete();
49− }
50}
233}
51
234
52void GreeterPrivate::handleRespond(const QString &response)
235void GreeterPrivate::handleRespond(const QString &response)
53{
236{
54− Q_Q(Greeter);
237+ m_impl->respond(response);
55−
56− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
57− settings.beginGroup(authenticationUser);
58− QVariant password = settings.value("password", "none");
59−
60− QString passwordValue;
61− if (password == "pin") {
62− passwordValue = settings.value("passwordValue", "1234").toString();
63− } else {
64− passwordValue = settings.value("passwordValue", "password").toString();
65− }
66− authenticated = (response == passwordValue);
67− Q_EMIT q->authenticationComplete();
68}
238}
69
239
70}
240}
241+
242+#include "GreeterPrivate.moc"
tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1
@@ -29,7 +29,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
29 : q_ptr(parent)
29 : q_ptr(parent)
30{
30{
31 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
31 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
32− QStringList users = settings.value("users", QStringList() << "phablet").toStringList();
32+ QStringList users = settings.value("users", QStringList() << qgetenv("USER")).toStringList();
33
33
34 Q_FOREACH(const QString &user, users)
34 Q_FOREACH(const QString &user, users)
35 {
35 {
tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1
@@ -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("PIN", 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();
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1
@@ -32,7 +32,7 @@ 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("PIN", 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)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1
@@ -26,7 +26,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
26{
26{
27 entries =
27 entries =
28 {
28 {
29− { "single", "Has PIN", 0, 0, false, false, 0, 0 },
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
30 };
30 };
31}
31}
32
32
tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
@@ -18,8 +18,9 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Components"
20import "../../../qml/Components"
21−import Ubuntu.Components 0.1
21+import AccountsService 0.1
22import LightDM 0.1 as LightDM
22import LightDM 0.1 as LightDM
23+import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
25Rectangle {
26Rectangle {
@@ -53,11 +54,16 @@ Rectangle {
53 }
54 }
54 }
55 }
55
56
57+ function setUser(username) {
58+ AccountsService.user = username
59+ LightDM.Greeter.authenticate(username)
60+ }
61+
56 Connections {
62 Connections {
57 target: LightDM.Greeter
63 target: LightDM.Greeter
58
64
59 onShowPrompt: {
65 onShowPrompt: {
60− if (text === "PIN") {
66+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
61 pinPadCheckBox.checked = false
67 pinPadCheckBox.checked = false
62 } else {
68 } else {
63 pinPadCheckBox.checked = true
69 pinPadCheckBox.checked = true
@@ -139,12 +145,12 @@ Rectangle {
139 Button {
145 Button {
140 text: "start auth (1234)"
146 text: "start auth (1234)"
141 width: parent.width
147 width: parent.width
142− onClicked: LightDM.Greeter.authenticate("has-pin")
148+ onClicked: setUser("has-pin")
143 }
149 }
144 Button {
150 Button {
145 text: "start auth (password)"
151 text: "start auth (password)"
146 width: parent.width
152 width: parent.width
147− onClicked: LightDM.Greeter.authenticate("has-password")
153+ onClicked: setUser("has-password")
148 }
154 }
149
155
150 TextField {
156 TextField {
@@ -255,7 +261,7 @@ Rectangle {
255 enteredLabel.text = ""
261 enteredLabel.text = ""
256 minPinLengthTextField.text = data.minPinLength
262 minPinLengthTextField.text = data.minPinLength
257 maxPinLengthTextField.text = data.maxPinLength
263 maxPinLengthTextField.text = data.maxPinLength
258− LightDM.Greeter.authenticate(data.username)
264+ setUser(data.username)
259 waitForLockscreenReady();
265 waitForLockscreenReady();
260
266
261 var inputField = findChild(lockscreen, "pinentryField")
267 var inputField = findChild(lockscreen, "pinentryField")