1 /*
2  * Copyright (C) 2016 Canonical Ltd.
3  *
4  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
5  *
6  * This file is part of signond
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1 as published by the Free Software Foundation.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  */
22 
23 #include <QDebug>
24 #include <QDir>
25 #include <QProcess>
26 #include <QSignalSpy>
27 #include <QTemporaryDir>
28 #include <QTest>
29 #include <libqtdbusmock/DBusMock.h>
30 #include <signal.h>
31 #include <sys/types.h>
32 
33 #include "SignOn/UiSessionData"
34 #include "signond/signoncommon.h"
35 
36 #include "fake_signonui.h"
37 
38 namespace QTest {
39 template<>
toString(const QSet<QString> & set)40 char *toString(const QSet<QString> &set)
41 {
42     QByteArray ba = "QSet<QString>(";
43     QStringList list = set.toList();
44     ba += list.join(", ");
45     ba += ")";
46     return qstrdup(ba.data());
47 }
48 } // QTest namespace
49 
50 using namespace QtDBusMock;
51 
52 struct SignondSecurityContextTest
53 {
54 public:
SignondSecurityContextTestSignondSecurityContextTest55     SignondSecurityContextTest(const QString &systemContext = QStringLiteral("*"), const QString &applicationContext = QStringLiteral("*")):
56         m_systemContext(systemContext),
57         m_applicationContext(applicationContext)
58     {
59     }
60 
operator ==SignondSecurityContextTest61     bool operator==(const SignondSecurityContextTest &sec) const
62     {
63         if (m_systemContext != sec.m_systemContext)
64             return false;
65 
66         return m_applicationContext == sec.m_applicationContext;
67     }
68 
operator <SignondSecurityContextTest69     bool operator<(const SignondSecurityContextTest &sec) const
70     {
71         if (m_systemContext != sec.m_systemContext)
72             return m_systemContext < sec.m_systemContext;
73 
74         return m_applicationContext < sec.m_applicationContext;
75     }
76 
77     QString m_systemContext;
78     QString m_applicationContext;
79 };
80 typedef QList<SignondSecurityContextTest> SignondSecurityContextTestList;
81 
operator <<(QDebug & dbg,const SignondSecurityContextTestList & securityContextList)82 QDebug operator<<(QDebug &dbg, const SignondSecurityContextTestList&securityContextList)
83 {
84     dbg << "{ ";
85     for (const SignondSecurityContextTest &secCtx : securityContextList) {
86         dbg << " ( "
87             << secCtx.m_systemContext
88             << ", "
89             << secCtx.m_applicationContext
90             << " )";
91     }
92 
93     dbg << " }";
94     return dbg;
95 }
96 
operator <<(QDBusArgument & argument,const SignondSecurityContextTest & securityContext)97 QDBusArgument &operator<<(QDBusArgument &argument, const SignondSecurityContextTest &securityContext)
98 {
99     argument.beginStructure();
100     argument << securityContext.m_systemContext << securityContext.m_applicationContext;
101     argument.endStructure();
102     return argument;
103 }
104 
operator >>(const QDBusArgument & argument,SignondSecurityContextTest & securityContext)105 const QDBusArgument &operator>>(const QDBusArgument &argument, SignondSecurityContextTest &securityContext)
106 {
107     argument.beginStructure();
108     argument >> securityContext.m_systemContext >> securityContext.m_applicationContext;
109     argument.endStructure();
110     return argument;
111 }
112 
113 Q_DECLARE_METATYPE(SignondSecurityContextTest)
114 Q_DECLARE_METATYPE(SignondSecurityContextTestList)
115 
116 class SignondTest: public QObject
117 {
118     Q_OBJECT
119 
120 public:
121     SignondTest();
122 
123 private Q_SLOTS:
124     void initTestCase();
125     void cleanup();
126     void testStart();
127     void testQueryMethods();
128     void testQueryMechanisms_data();
129     void testQueryMechanisms();
130     void testIdentityCreation();
131     void testIdentityRemoval();
132     void testIdentityReferences();
133     void testAuthSessionMechanisms_data();
134     void testAuthSessionMechanisms();
135     void testAuthSessionProcess();
136     void testAuthSessionProcessFromOtherProcess();
137     void testAuthSessionProcessUi();
138     void testAuthSessionCloseUi_data();
139     void testAuthSessionCloseUi();
140 
141 private:
142     void setupEnvironment();
143     bool signondIsRunning();
144     bool killSignond();
145     void clearBaseDir();
connection()146     const QDBusConnection &connection() { return m_dbus.sessionConnection(); }
methodCall(const QString & path,const QString & interface,const QString & method)147     QDBusMessage methodCall(const QString &path, const QString &interface,
148                             const QString &method) {
149         return QDBusMessage::createMethodCall(SIGNOND_SERVICE, path,
150                                               interface, method);
151     }
152     bool replyIsValid(const QDBusMessage &reply);
153     QString createIdentity(const QVariantMap &data, uint *id = 0);
154 
155 private:
156     QTemporaryDir m_baseDir;
157     QtDBusTest::DBusTestRunner m_dbus;
158     QtDBusMock::DBusMock m_mock;
159     FakeSignOnUi m_signonUi;
160 };
161 
mapIsSuperset(const QVariantMap & superSet,const QVariantMap & set)162 static bool mapIsSuperset(const QVariantMap &superSet, const QVariantMap &set)
163 {
164     QMapIterator<QString, QVariant> it(set);
165     while (it.hasNext()) {
166         it.next();
167         if (!superSet.contains(it.key())) {
168             qDebug() << "Missing key" << it.key();
169             return false;
170         }
171 
172         if (it.key() == SIGNOND_IDENTITY_INFO_ACL) {
173             QDBusArgument container = superSet.value(it.key()).value<QDBusArgument>();
174             SignondSecurityContextTestList accessControlList = qdbus_cast<SignondSecurityContextTestList>(container);
175             SignondSecurityContextTestList sub_acl = it.value().value<SignondSecurityContextTestList>();
176             if (accessControlList != sub_acl) {
177 
178                 qDebug() << it.key() << "is" << accessControlList << " expecting " << sub_acl;
179                 return false;
180             }
181 
182             continue;
183         }
184 
185         if (superSet.value(it.key()) != it.value()) {
186             qDebug() << it.key() << "is" << superSet.value(it.key()) << " expecting " << it.value();
187             return false;
188         }
189     }
190 
191     return true;
192 }
193 
SignondTest()194 SignondTest::SignondTest():
195     QObject(0),
196     m_dbus((setupEnvironment(), TEST_DBUS_CONFIG_FILE)),
197     m_mock(m_dbus),
198     m_signonUi(&m_mock)
199 {
200     DBusMock::registerMetaTypes();
201     qDBusRegisterMetaType<SignondSecurityContextTest>();
202     qDBusRegisterMetaType<SignondSecurityContextTestList>();
203     QMetaType::registerComparators<SignondSecurityContextTest>();
204 }
205 
setupEnvironment()206 void SignondTest::setupEnvironment()
207 {
208     QVERIFY(m_baseDir.isValid());
209     QByteArray baseDirPath = m_baseDir.path().toUtf8();
210     QDir baseDir(m_baseDir.path());
211 
212     qunsetenv("XDG_DATA_DIR");
213     qputenv("BUILDDIR", BUILDDIR);
214     qputenv("HOME", baseDirPath);
215     qputenv("XDG_RUNTIME_DIR", baseDirPath + "/runtime-dir");
216     baseDir.mkpath("runtime-dir");
217     qputenv("SSO_STORAGE_PATH", baseDirPath);
218     qputenv("SSO_EXTENSIONS_DIR", baseDirPath + "/non-existing-dir");
219     qputenv("SSO_USE_PEER_BUS", "0");
220     qputenv("SSO_LOGGING_LEVEL", "2");
221     qputenv("SSO_PLUGINS_DIR", BUILDDIR "/src/plugins/test");
222     QByteArray ldLibraryPath = qgetenv("LD_LIBRARY_PATH");
223     qputenv("LD_LIBRARY_PATH",
224             BUILDDIR "/lib/plugins:"
225             BUILDDIR "/lib/plugins/signon-plugins-common:"
226             BUILDDIR "/lib/signond/SignOn:" +
227             ldLibraryPath);
228     QByteArray execPath = qgetenv("PATH");
229     qputenv("PATH",
230             BUILDDIR "/src/remotepluginprocess:" +
231             execPath);
232 
233     /* Make sure we accidentally don't talk to the developer's signond running
234      * in the session bus */
235     qunsetenv("DBUS_SESSION_BUS_ADDRESS");
236 }
237 
replyIsValid(const QDBusMessage & msg)238 bool SignondTest::replyIsValid(const QDBusMessage &msg)
239 {
240     if (msg.type() == QDBusMessage::ErrorMessage) {
241         qDebug() << "Error name:" << msg.errorName();
242         qDebug() << "Error text:" << msg.errorMessage();
243     }
244     return msg.type() == QDBusMessage::ReplyMessage;
245 }
246 
signondIsRunning()247 bool SignondTest::signondIsRunning()
248 {
249     return connection().
250         interface()->isServiceRegistered(SIGNOND_SERVICE).value();
251 }
252 
killSignond()253 bool SignondTest::killSignond()
254 {
255     uint pid = connection().interface()->servicePid(SIGNOND_SERVICE).value();
256     if (pid == 0) return true;
257     return kill(pid, SIGTERM) == 0 || errno == ESRCH;
258 }
259 
clearBaseDir()260 void SignondTest::clearBaseDir()
261 {
262     QDir baseDir(m_baseDir.path());
263     baseDir.removeRecursively();
264     baseDir.mkpath(".");
265 }
266 
createIdentity(const QVariantMap & data,uint * id)267 QString SignondTest::createIdentity(const QVariantMap &data, uint *id)
268 {
269     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
270                                   SIGNOND_DAEMON_INTERFACE,
271                                   "registerNewIdentity");
272     msg << QString("application_security_context");
273     QDBusMessage reply = connection().call(msg);
274     if (!replyIsValid(reply)) return QString();
275 
276     QString objectPath =
277         reply.arguments()[0].value<QDBusObjectPath>().path();
278 
279     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "store");
280     msg << data;
281     reply = connection().call(msg);
282     if (!replyIsValid(reply)) return QString();
283 
284     if (id) {
285         *id = reply.arguments()[0].toUInt();
286     }
287     return objectPath;
288 }
289 
initTestCase()290 void SignondTest::initTestCase()
291 {
292     m_dbus.startServices();
293 }
294 
cleanup()295 void SignondTest::cleanup()
296 {
297     if (QTest::currentTestFailed()) {
298         m_baseDir.setAutoRemove(false);
299         qDebug() << "Base dir:" << m_baseDir.path();
300     }
301 }
302 
testStart()303 void SignondTest::testStart()
304 {
305     QVERIFY(killSignond());
306     QTRY_VERIFY(!signondIsRunning());
307 
308     QDBusMessage reply =
309         connection().interface()->call("StartServiceByName",
310                                        SIGNOND_SERVICE, uint(0));
311     QVERIFY(replyIsValid(reply));
312     QTRY_VERIFY(signondIsRunning());
313 }
314 
testQueryMethods()315 void SignondTest::testQueryMethods()
316 {
317     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
318                                   SIGNOND_DAEMON_INTERFACE,
319                                   "queryMethods");
320     QDBusMessage reply = connection().call(msg);
321     QVERIFY(replyIsValid(reply));
322     QCOMPARE(reply.arguments().count(), 1);
323     QStringList methods = reply.arguments()[0].toStringList();
324     QStringList expectedMethods { "ssotest", "ssotest2" };
325     QCOMPARE(methods.toSet(), expectedMethods.toSet());
326 }
327 
testQueryMechanisms_data()328 void SignondTest::testQueryMechanisms_data()
329 {
330     QTest::addColumn<QString>("method");
331     QTest::addColumn<QStringList>("expectedMechanisms");
332 
333     QTest::newRow("ssotest") <<
334         "ssotest" << QStringList { "mech1", "mech2", "mech3", "BLOB" };
335     QTest::newRow("ssotest2") <<
336         "ssotest2" << QStringList { "mech1", "mech2", "mech3" };
337 }
338 
testQueryMechanisms()339 void SignondTest::testQueryMechanisms()
340 {
341     QFETCH(QString, method);
342     QFETCH(QStringList, expectedMechanisms);
343 
344     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
345                                   SIGNOND_DAEMON_INTERFACE,
346                                   "queryMechanisms");
347     msg << method;
348     QDBusMessage reply = connection().call(msg);
349     QVERIFY(replyIsValid(reply));
350 
351     QCOMPARE(reply.arguments().count(), 1);
352     QStringList mechanisms = reply.arguments()[0].toStringList();
353     QCOMPARE(mechanisms.toSet(), expectedMechanisms.toSet());
354 }
355 
testIdentityCreation()356 void SignondTest::testIdentityCreation()
357 {
358     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
359                                   SIGNOND_DAEMON_INTERFACE,
360                                   "registerNewIdentity");
361     msg << QString("application_security_context");
362     QDBusMessage reply = connection().call(msg);
363     QVERIFY(replyIsValid(reply));
364 
365     QCOMPARE(reply.arguments().count(), 1);
366 
367     QString objectPath =
368         reply.arguments()[0].value<QDBusObjectPath>().path();
369     QVERIFY(objectPath.startsWith('/'));
370 
371     SignondSecurityContextTestList acl = { SignondSecurityContextTest() };
372     QVariantMap identityData {
373         { SIGNOND_IDENTITY_INFO_USERNAME, "John" },
374         { SIGNOND_IDENTITY_INFO_CAPTION, "John's account" },
375         { SIGNOND_IDENTITY_INFO_ACL, QVariant::fromValue(acl) },
376     };
377     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "store");
378     msg << identityData;
379     reply = connection().call(msg);
380     QVERIFY(replyIsValid(reply));
381 
382     uint id = reply.arguments()[0].toUInt();
383     QVERIFY(id != 0);
384 
385     /* Read the current identity info */
386     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "getInfo");
387     reply = connection().call(msg);
388     QVERIFY(replyIsValid(reply));
389 
390     QVariantMap storedData = QDBusReply<QVariantMap>(reply).value();
391     QVERIFY(mapIsSuperset(storedData, identityData));
392 
393     /* Reload the identity */
394     msg = methodCall(SIGNOND_DAEMON_OBJECTPATH, SIGNOND_DAEMON_INTERFACE,
395                      "getIdentity");
396     msg << id;
397     msg << QString("application_security_context");
398     reply = connection().call(msg);
399     QVERIFY(replyIsValid(reply));
400 
401     QCOMPARE(reply.arguments().count(), 2);
402     objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
403     QVERIFY(objectPath.startsWith('/'));
404     storedData =
405         qdbus_cast<QVariantMap>(reply.arguments()[1].value<QDBusArgument>());
406     QVERIFY(mapIsSuperset(storedData, identityData));
407 
408     /* Query identities */
409     msg = methodCall(SIGNOND_DAEMON_OBJECTPATH, SIGNOND_DAEMON_INTERFACE,
410                      "queryIdentities");
411     msg << QVariantMap();
412     msg << QString("application_security_context");
413     reply = connection().call(msg);
414     QVERIFY(replyIsValid(reply));
415 
416     QCOMPARE(reply.arguments().count(), 1);
417     QList<QVariantMap> identities =
418         qdbus_cast<QList<QVariantMap>>(reply.arguments()[0].value<QDBusArgument>());
419     QCOMPARE(identities.count(), 1);
420     storedData = identities[0];
421     QVERIFY(mapIsSuperset(storedData, identityData));
422 }
423 
testIdentityRemoval()424 void SignondTest::testIdentityRemoval()
425 {
426     m_signonUi.mockedService().ClearCalls().waitForFinished();
427 
428     SignondSecurityContextTestList acl = { SignondSecurityContextTest() };
429     QVariantMap identityData {
430         { SIGNOND_IDENTITY_INFO_USERNAME, "John Deleteme" },
431         { SIGNOND_IDENTITY_INFO_CAPTION, "John's account" },
432         { SIGNOND_IDENTITY_INFO_ACL, QVariant::fromValue(acl) },
433     };
434     uint id;
435     QString objectPath = createIdentity(identityData, &id);
436     QVERIFY(objectPath.startsWith('/'));
437     QVERIFY(id > 0);
438 
439     QDBusMessage msg =
440         methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "remove");
441     QDBusMessage reply = connection().call(msg);
442     QVERIFY(replyIsValid(reply));
443 
444     /* Check that signonui has been asked to remove its data */
445     QList<MethodCall> calls =
446         m_signonUi.mockedService().GetMethodCalls("removeIdentityData");
447     QCOMPARE(calls.count(), 1);
448     MethodCall call = calls.first();
449     QCOMPARE(call.args().count(), 1);
450     QCOMPARE(call.args().first().toUInt(), id);
451 
452     /* Read the current identity info */
453     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "getInfo");
454     reply = connection().call(msg);
455     QCOMPARE(reply.type(), QDBusMessage::ErrorMessage);
456 
457     /* Load the identity again */
458     msg = methodCall(SIGNOND_DAEMON_OBJECTPATH, SIGNOND_DAEMON_INTERFACE,
459                      "getIdentity");
460     msg << id;
461     msg << QString("application_security_context");
462     reply = connection().call(msg);
463     QCOMPARE(reply.type(), QDBusMessage::ErrorMessage);
464 }
465 
testIdentityReferences()466 void SignondTest::testIdentityReferences()
467 {
468     SignondSecurityContextTestList acl = { SignondSecurityContextTest() };
469     QVariantMap identityData {
470         { SIGNOND_IDENTITY_INFO_USERNAME, "John" },
471         { SIGNOND_IDENTITY_INFO_CAPTION, "John's account" },
472         { SIGNOND_IDENTITY_INFO_ACL, QVariant::fromValue(acl) },
473     };
474     QString objectPath = createIdentity(identityData);
475 
476     /* Read the current identity info */
477     QDBusMessage msg =
478         methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "getInfo");
479     QDBusMessage reply = connection().call(msg);
480     QVERIFY(replyIsValid(reply));
481 
482     QVariantMap storedData = QDBusReply<QVariantMap>(reply).value();
483     QCOMPARE(storedData.value(SIGNOND_IDENTITY_INFO_REFCOUNT).toInt(), 0);
484 
485     /* Add a reference */
486     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "addReference");
487     msg << "hello";
488     reply = connection().call(msg);
489     QVERIFY(replyIsValid(reply));
490 
491     /* Check new refcount */
492     msg = methodCall(objectPath, SIGNOND_IDENTITY_INTERFACE, "getInfo");
493     reply = connection().call(msg);
494     QVERIFY(replyIsValid(reply));
495     storedData = QDBusReply<QVariantMap>(reply).value();
496     QEXPECT_FAIL("", "refcount not updated: https://gitlab.com/accounts-sso/signond/issues/1", Continue);
497     QCOMPARE(storedData.value(SIGNOND_IDENTITY_INFO_REFCOUNT).toInt(), 1);
498 }
499 
testAuthSessionMechanisms_data()500 void SignondTest::testAuthSessionMechanisms_data()
501 {
502     QTest::addColumn<QString>("method");
503     QTest::addColumn<QStringList>("wantedMechanisms");
504     QTest::addColumn<QStringList>("expectedMechanisms");
505 
506     QTest::newRow("ssotest - any") <<
507         "ssotest" <<
508         QStringList {} <<
509         QStringList { "mech1", "mech2", "mech3", "BLOB" };
510     QTest::newRow("ssotest - subset") <<
511         "ssotest" <<
512         QStringList { "BLOB", "mech1" } <<
513         QStringList { "BLOB", "mech1" };
514     QTest::newRow("ssotest2 - any") <<
515         "ssotest2" <<
516         QStringList {} <<
517         QStringList { "mech1", "mech2", "mech3" };
518     QTest::newRow("ssotest2 - mix") <<
519         "ssotest2" <<
520         QStringList { "BLOB", "mech1" } <<
521         QStringList { "mech1" };
522 }
523 
testAuthSessionMechanisms()524 void SignondTest::testAuthSessionMechanisms()
525 {
526     QFETCH(QString, method);
527     QFETCH(QStringList, wantedMechanisms);
528     QFETCH(QStringList, expectedMechanisms);
529 
530     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
531                                   SIGNOND_DAEMON_INTERFACE,
532                                   "getAuthSessionObjectPath");
533     msg << uint(0);
534     msg << QString("*");
535     msg << method;
536     QDBusMessage reply = connection().call(msg);
537     QVERIFY(replyIsValid(reply));
538 
539     QCOMPARE(reply.arguments().count(), 1);
540 
541     QString objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
542     QVERIFY(objectPath.startsWith('/'));
543 
544     /* Check the available mechanisms */
545     msg = methodCall(objectPath, SIGNOND_AUTH_SESSION_INTERFACE,
546                      "queryAvailableMechanisms");
547     msg << wantedMechanisms;
548     reply = connection().call(msg);
549     QVERIFY(replyIsValid(reply));
550 
551     QStringList mechanisms = QDBusReply<QStringList>(reply).value();
552     QCOMPARE(mechanisms.toSet(), expectedMechanisms.toSet());
553 }
554 
testAuthSessionProcess()555 void SignondTest::testAuthSessionProcess()
556 {
557     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
558                                   SIGNOND_DAEMON_INTERFACE,
559                                   "getAuthSessionObjectPath");
560     msg << uint(0);
561     msg << QString("*");
562     msg << QString("ssotest");
563     QDBusMessage reply = connection().call(msg);
564     QVERIFY(replyIsValid(reply));
565 
566     QCOMPARE(reply.arguments().count(), 1);
567 
568     QString objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
569     QVERIFY(objectPath.startsWith('/'));
570 
571     QVariantMap sessionData {
572         { "Some key", "its value" },
573         { "height", 123 },
574     };
575     /* Read the current identity info */
576     msg = methodCall(objectPath, SIGNOND_AUTH_SESSION_INTERFACE, "process");
577     msg << sessionData;
578     msg << QString("mech1");
579     reply = connection().call(msg);
580     QVERIFY(replyIsValid(reply));
581 
582     QVariantMap response = QDBusReply<QVariantMap>(reply).value();
583     QVariantMap expectedResponse = sessionData;
584     expectedResponse["Realm"] = "testRealm_after_test";
585     QCOMPARE(response, expectedResponse);
586 }
587 
testAuthSessionProcessFromOtherProcess()588 void SignondTest::testAuthSessionProcessFromOtherProcess()
589 {
590     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
591                                   SIGNOND_DAEMON_INTERFACE,
592                                   "getAuthSessionObjectPath");
593     msg << uint(0);
594     msg << QString("*");
595     msg << QString("ssotest");
596     QDBusMessage reply = connection().call(msg);
597     QVERIFY(replyIsValid(reply));
598 
599     QCOMPARE(reply.arguments().count(), 1);
600 
601     QString objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
602     QVERIFY(objectPath.startsWith('/'));
603 
604     /* Pass this object path to another process, and verify that it's not
605      * allowed to use the session */
606     QProcess sessionTool;
607     sessionTool.start("./session_tool", { "--sessionPath", objectPath });
608     QVERIFY(sessionTool.waitForStarted());
609     QVERIFY(sessionTool.waitForFinished());
610 
611     QByteArray output = sessionTool.readAllStandardOutput();
612     QVERIFY(output.startsWith("Error:"));
613 
614     QString errorName = output.mid(6);
615     QCOMPARE(errorName, SIGNOND_PERMISSION_DENIED_ERR_NAME);
616 }
617 
testAuthSessionProcessUi()618 void SignondTest::testAuthSessionProcessUi()
619 {
620     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
621                                   SIGNOND_DAEMON_INTERFACE,
622                                   "getAuthSessionObjectPath");
623     msg << uint(0);
624     msg << QString("*");
625     msg << QString("ssotest");
626     QDBusMessage reply = connection().call(msg);
627     QVERIFY(replyIsValid(reply));
628     QString objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
629     QVERIFY(objectPath.startsWith('/'));
630 
631     /* prepare SignOnUi */
632     m_signonUi.mockedService().ClearCalls().waitForFinished();
633     QVariantMap uiReply {
634         { "data",
635             QVariantMap {
636                 { "UserName", "the user" },
637                 { "Secret", "s3c'r3t" },
638                 { "QueryErrorCode", 0 },
639             }
640         },
641     };
642     m_signonUi.setNextReply(uiReply);
643 
644     /* Start the authentication, using a mechanism requiring UI interaction */
645     QVariantMap sessionData {
646         { "Some key", "its value" },
647         { "height", 123 },
648     };
649     msg = methodCall(objectPath, SIGNOND_AUTH_SESSION_INTERFACE, "process");
650     msg << sessionData;
651     msg << QString("mech2");
652     reply = connection().call(msg);
653     QVERIFY(replyIsValid(reply));
654 
655     QVariantMap response = QDBusReply<QVariantMap>(reply).value();
656     QVariantMap expectedResponse {
657         { "UserName", "the user" },
658     };
659     QCOMPARE(response, expectedResponse);
660 }
661 
testAuthSessionCloseUi_data()662 void SignondTest::testAuthSessionCloseUi_data()
663 {
664     QTest::addColumn<QVariantMap>("uiReply");
665     QTest::addColumn<bool>("expectedCancellation");
666 
667     QTest::newRow("no UI") <<
668         QVariantMap {} <<
669         false;
670 
671     QTest::newRow("with UI") <<
672         QVariantMap {
673             { "data",
674                 QVariantMap {
675                     { "UserName", "the user" },
676                     { "Secret", "s3c'r3t" },
677                     { "QueryErrorCode", 0 },
678                 }
679             }
680         } <<
681         true;
682 
683     QTest::newRow("with UI error") <<
684         QVariantMap {
685             { "error", "some.Dbus.Error" }
686         } <<
687         false;
688 
689     QTest::newRow("with UI canceled") <<
690         QVariantMap {
691             { "data",
692                 QVariantMap {
693                     { "UserName", "the user" },
694                     { "Secret", "s3c'r3t" },
695                     { "QueryErrorCode", SignOn::QUERY_ERROR_CANCELED },
696                 }
697             }
698         } <<
699         false;
700 }
701 
testAuthSessionCloseUi()702 void SignondTest::testAuthSessionCloseUi()
703 {
704     QFETCH(QVariantMap, uiReply);
705     QFETCH(bool, expectedCancellation);
706 
707     QDBusMessage msg = methodCall(SIGNOND_DAEMON_OBJECTPATH,
708                                   SIGNOND_DAEMON_INTERFACE,
709                                   "getAuthSessionObjectPath");
710     msg << uint(0);
711     msg << QString("*");
712     msg << QString("ssotest");
713     QDBusMessage reply = connection().call(msg);
714     QVERIFY(replyIsValid(reply));
715     QString objectPath = reply.arguments()[0].value<QDBusObjectPath>().path();
716     QVERIFY(objectPath.startsWith('/'));
717 
718     /* prepare SignOnUi */
719     OrgFreedesktopDBusMockInterface &mockInterface =
720         m_signonUi.mockedService();
721     mockInterface.ClearCalls().waitForFinished();
722     m_signonUi.setNextReply(uiReply);
723 
724     /* Start the authentication; if uiReply is empty, don't invoke signonUI */
725     QString mechanism = uiReply.isEmpty() ? "mech1" : "mech2";
726     QVariantMap sessionData {
727         { "Some key", "its value" },
728         { "height", 123 },
729     };
730     msg = methodCall(objectPath, SIGNOND_AUTH_SESSION_INTERFACE, "process");
731     msg << sessionData;
732     msg << mechanism;
733     connection().call(msg);
734 
735     /* Check whether signonui has been asked to close */
736     QTRY_COMPARE(mockInterface.GetMethodCalls("cancelUiRequest").value().count(),
737                  expectedCancellation ? 1 : 0);
738 }
739 
740 QTEST_GUILESS_MAIN(SignondTest);
741 
742 #include "tst_signond.moc"
743