Don't send duplicate replies on error
[accounts-sso:jlaako-signon.git] / src / signond / signonidentity.cpp
1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  *
6  * Contact: Aurel Popirtac <ext-aurel.popirtac@nokia.com>
7  * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  */
23
24 #include <iostream>
25
26 #include "signond-common.h"
27 #include "signonidentity.h"
28 #include "signonui_interface.h"
29 #include "SignOn/uisessiondata.h"
30
31 #include "accesscontrolmanager.h"
32 #include "signonidentityadaptor.h"
33
34 #define SSOUI_KEY_CAPTION         QLatin1String("Caption")
35 #define SSOUI_KEY_MESSAGE         QLatin1String("QueryMessage")
36 #define SSOUI_KEY_USERNAME        QLatin1String("UserName")
37 #define SSOUI_KEY_QUERYPASSWORD   QLatin1String("QueryPassword")
38 #define SSOUI_KEY_PASSWORD        QLatin1String("Secret")
39 #define SSOUI_KEY_ERROR           QLatin1String("QueryErrorCode")
40
41 #define SIGNON_RETURN_IF_CAM_UNAVAILABLE(_ret_arg_) do {                          \
42         if (!(CredentialsAccessManager::instance()->credentialsSystemOpened())) { \
43             replyError(internalServerErrName, \
44                        internalServerErrStr + \
45                        QLatin1String("Could not access Signon Database.")); \
46             return _ret_arg_;           \
47         }                               \
48     } while(0)
49
50 using namespace SignOnCrypto;
51
52 namespace SignonDaemonNS {
53
54     const QString internalServerErrName = SIGNOND_INTERNAL_SERVER_ERR_NAME;
55     const QString internalServerErrStr = SIGNOND_INTERNAL_SERVER_ERR_STR;
56
57     SignonIdentity::SignonIdentity(quint32 id, int timeout,
58                                    SignonDaemon *parent)
59             : SignonDisposable(timeout, parent),
60               m_pInfo(NULL),
61               m_pSignonDaemon(parent),
62               m_registered(false)
63     {
64         m_id = id;
65
66         /*
67          * creation of unique name for the given identity
68          * */
69         static quint32 incr = 0;
70         QString objectName = SIGNOND_DAEMON_OBJECTPATH + QLatin1String("/Identity_")
71                              + QString::number(incr++, 16);
72         setObjectName(objectName);
73
74         m_signonui = new SignonUiAdaptor(
75                                         SIGNON_UI_SERVICE,
76                                         SIGNON_UI_DAEMON_OBJECTPATH,
77                                         SIGNOND_BUS,
78                                         this);
79
80         if (!(m_encryptor = new Encryptor))
81             qFatal("Cannot allocate memory for encryptor");
82     }
83
84     SignonIdentity::~SignonIdentity()
85     {
86         if (m_registered)
87         {
88             emit unregistered();
89             QDBusConnection connection = SIGNOND_BUS;
90             connection.unregisterObject(objectName());
91         }
92
93         if (credentialsStored())
94             m_pSignonDaemon->m_storedIdentities.remove(m_id);
95         else
96             m_pSignonDaemon->m_unstoredIdentities.remove(objectName());
97
98         delete m_signonui;
99         delete m_encryptor;
100     }
101
102     bool SignonIdentity::init()
103     {
104         QDBusConnection connection = SIGNOND_BUS;
105
106         if (!connection.isConnected()) {
107             QDBusError err = connection.lastError();
108             TRACE() << "Connection cannot be established:" << err.errorString(err.type()) ;
109             return false;
110         }
111
112         QDBusConnection::RegisterOptions registerOptions = QDBusConnection::ExportAllContents;
113
114 #ifndef SIGNON_DISABLE_ACCESS_CONTROL
115         (void)new SignonIdentityAdaptor(this);
116         registerOptions = QDBusConnection::ExportAdaptors;
117 #endif
118
119         if (!connection.registerObject(objectName(), this, registerOptions)) {
120             TRACE() << "Object cannot be registered: " << objectName();
121             return false;
122         }
123
124         return (m_registered = true);
125     }
126
127     SignonIdentity *SignonIdentity::createIdentity(quint32 id, SignonDaemon *parent)
128     {
129         SignonIdentity *identity =
130             new SignonIdentity(id, parent->identityTimeout(), parent);
131
132         if (!identity->init()) {
133             TRACE() << "The created identity is invalid and will be deleted.\n";
134             delete identity;
135             return NULL;
136         }
137
138         return identity;
139     }
140
141     void SignonIdentity::replyError(const QString &name, const QString &msg)
142     {
143         setDelayedReply(true);
144         QDBusMessage errReply = message().createErrorReply(name, msg);
145         connection().send(errReply);
146     }
147
148     void SignonIdentity::destroy()
149     {
150         if (m_registered)
151         {
152             emit unregistered();
153             QDBusConnection connection = SIGNOND_BUS;
154             connection.unregisterObject(objectName());
155             m_registered = false;
156         }
157
158         deleteLater();
159     }
160
161     SignonIdentityInfo SignonIdentity::queryInfo(bool &ok, bool queryPassword)
162     {
163         ok = true;
164
165         if (m_pInfo) {
166             return *m_pInfo;
167         } else {
168             CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
169             m_pInfo = new SignonIdentityInfo(db->credentials(m_id, queryPassword));
170
171             if (db->lastError().isValid()) {
172                 ok = false;
173                 delete m_pInfo;
174                 m_pInfo = NULL;
175                 return SignonIdentityInfo();
176             }
177         }
178         return *m_pInfo;
179     }
180
181     bool SignonIdentity::addReference(const QString &reference)
182     {
183         TRACE() << "addReference: " << reference;
184
185         SIGNON_RETURN_IF_CAM_UNAVAILABLE(false);
186
187         CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
188         if (db == NULL) {
189             BLAME() << "NULL database handler object.";
190             return false;
191         }
192         QString aegisIdToken = AccessControlManager::idTokenOfPeer(static_cast<QDBusContext>(*this));
193         keepInUse();
194         return db->addReference(m_id, aegisIdToken, reference);
195     }
196
197     bool SignonIdentity::removeReference(const QString &reference)
198     {
199         TRACE() << "removeReference: " << reference;
200
201         SIGNON_RETURN_IF_CAM_UNAVAILABLE(false);
202
203         CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
204         if (db == NULL) {
205             BLAME() << "NULL database handler object.";
206             return false;
207         }
208         QString aegisIdToken = AccessControlManager::idTokenOfPeer(static_cast<QDBusContext>(*this));
209         keepInUse();
210         return db->removeReference(m_id, aegisIdToken, reference);
211     }
212
213     quint32 SignonIdentity::requestCredentialsUpdate(const QString &displayMessage)
214     {
215         SIGNON_RETURN_IF_CAM_UNAVAILABLE(SIGNOND_NEW_IDENTITY);
216
217         bool ok;
218         SignonIdentityInfo info = queryInfo(ok, false);
219
220         if (!ok) {
221             BLAME() << "Identity not found.";
222             replyError(SIGNOND_IDENTITY_NOT_FOUND_ERR_NAME,
223                        SIGNOND_IDENTITY_NOT_FOUND_ERR_STR);
224             return SIGNOND_NEW_IDENTITY;
225         }
226         if (!info.storePassword()) {
227             BLAME() << "Password cannot be stored.";
228             replyError(SIGNOND_STORE_FAILED_ERR_NAME,
229                        SIGNOND_STORE_FAILED_ERR_STR);
230             return SIGNOND_NEW_IDENTITY;
231         }
232
233         //delay dbus reply, ui interaction might take long time to complete
234         setDelayedReply(true);
235         m_message = message();
236
237         //create ui request to ask password
238         QVariantMap uiRequest;
239         uiRequest.insert(SSOUI_KEY_QUERYPASSWORD, true);
240         uiRequest.insert(SSOUI_KEY_USERNAME, info.userName());
241         uiRequest.insert(SSOUI_KEY_MESSAGE, displayMessage);
242         uiRequest.insert(SSOUI_KEY_CAPTION, info.caption());
243
244         TRACE() << "Waiting for reply from signon-ui";
245         QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_signonui->queryDialog(uiRequest),
246                                                 this);
247         connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(queryUiSlot(QDBusPendingCallWatcher*)));
248
249         setAutoDestruct(false);
250         return 0;
251     }
252
253     QList<QVariant> SignonIdentity::queryInfo()
254     {
255         TRACE() << "QUERYING INFO";
256
257         SIGNON_RETURN_IF_CAM_UNAVAILABLE(QList<QVariant>());
258
259         bool ok;
260         SignonIdentityInfo info = queryInfo(ok, false);
261
262         TRACE() << info.serialize();
263         if (!ok) {
264             TRACE();
265             replyError(SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_NAME,
266                        SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_STR +
267                        QLatin1String("Database querying error occurred."));
268             return QList<QVariant>();
269         }
270
271         if (info.isNew()) {
272             TRACE();
273             replyError(SIGNOND_IDENTITY_NOT_FOUND_ERR_NAME,
274                        SIGNOND_IDENTITY_NOT_FOUND_ERR_STR);
275             return QList<QVariant>();
276         }
277
278         TRACE() << "INFO as variant list:" << info.toVariantList();
279         keepInUse();
280         return info.toVariantList();
281     }
282
283     bool SignonIdentity::verifyUser(const QVariantMap &params)
284     {
285         SIGNON_RETURN_IF_CAM_UNAVAILABLE(false);
286
287         bool ok;
288         SignonIdentityInfo info = queryInfo(ok, true);
289
290         if (!ok) {
291             BLAME() << "Identity not found.";
292             replyError(SIGNOND_IDENTITY_NOT_FOUND_ERR_NAME,
293                        SIGNOND_IDENTITY_NOT_FOUND_ERR_STR);
294             return false;
295         }
296         if (!info.storePassword()) {
297             BLAME() << "Password is not stored.";
298             replyError(SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_NAME,
299                        SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_STR);
300             return false;
301         }
302
303         //delay dbus reply, ui interaction might take long time to complete
304         setDelayedReply(true);
305         m_message = message();
306
307         //create ui request to ask password
308         QVariantMap uiRequest;
309         uiRequest.unite(params);
310         uiRequest.insert(SSOUI_KEY_QUERYPASSWORD, true);
311         uiRequest.insert(SSOUI_KEY_USERNAME, info.userName());
312         uiRequest.insert(SSOUI_KEY_CAPTION, info.caption());
313
314         TRACE() << "Waiting for reply from signon-ui";
315         QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
316                 m_signonui->queryDialog(uiRequest), this);
317         connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
318                 SLOT(verifyUiSlot(QDBusPendingCallWatcher*)));
319
320         setAutoDestruct(false);
321         return false;
322     }
323
324     bool SignonIdentity::verifySecret(const QString &secret)
325     {
326         SIGNON_RETURN_IF_CAM_UNAVAILABLE(false);
327
328         bool ok;
329         queryInfo(ok);
330         if (!ok) {
331             TRACE();
332             replyError(SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_NAME,
333                        SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_STR +
334                        QLatin1String("Database querying error occurred."));
335             return false;
336         }
337
338         CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
339         bool ret = db->checkPassword(m_pInfo->id(), m_pInfo->userName(), secret);
340
341         keepInUse();
342         return ret;
343     }
344
345     void SignonIdentity::remove()
346     {
347         SIGNON_RETURN_IF_CAM_UNAVAILABLE();
348
349         CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
350         if ((db == 0) || !db->removeCredentials(m_id)) {
351             TRACE() << "Error occurred while inserting/updating credemtials.";
352             replyError(SIGNOND_REMOVE_FAILED_ERR_NAME,
353                        SIGNOND_REMOVE_FAILED_ERR_STR +
354                        QLatin1String("Database error occurred."));
355         }
356         emit infoUpdated((int)SignOn::IdentityRemoved);
357         keepInUse();
358     }
359
360     bool SignonIdentity::signOut()
361     {
362         TRACE() << "Signout request. Identity ID: " << id();
363         /*
364            - If the identity is stored (thus registered here)
365            signal 'sign out' to all identities subsribed to this object,
366            otherwise the only identity subscribed to this is the newly
367            created client side identity, which called this method.
368            - This is just a safety check, as the client identity - if it is a new one -
369            should not inform server side to sign out.
370         */
371         if (id() != SIGNOND_NEW_IDENTITY) {
372             //clear stored sessiondata
373             CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
374             if ((db == 0) || !db->removeData(m_id)) {
375                 TRACE() << "clear data failed";
376             }
377
378             emit infoUpdated((int)SignOn::IdentitySignedOut);
379         }
380         keepInUse();
381         return true;
382     }
383
384     quint32 SignonIdentity::storeCredentials(const quint32 id,
385                                              const QString &userName,
386                                              const QString &secret,
387                                              const bool storeSecret,
388                                              const QMap<QString, QVariant> &methods,
389                                              const QString &caption,
390                                              const QStringList &realms,
391                                              const QStringList &accessControlList,
392                                              const int type)
393     {
394         keepInUse();
395         SIGNON_RETURN_IF_CAM_UNAVAILABLE(SIGNOND_NEW_IDENTITY);
396
397         /*
398          * TODO: optimize the interaction with security framework: have 1 less call
399          * In order to have this we need to fetch all tokens once, and parse them
400          * in 'decodeString' and 'idTokenOfPid' as argument, but not pidOfPeer
401          * */
402
403         pid_t pidOfPeer = AccessControlManager::pidOfPeer(static_cast<QDBusContext>(*this));
404         QString decodedSecret(m_encryptor->decodeString(secret, pidOfPeer));
405
406         if (m_encryptor->status() != Encryptor::Ok) {
407             replyError(SIGNOND_ENCRYPTION_FAILED_ERR_NAME,
408                        SIGNOND_ENCRYPTION_FAILED_ERR_STR);
409             return SIGNOND_NEW_IDENTITY;
410         }
411
412         QString aegisIdToken = AccessControlManager::idTokenOfPid(pidOfPeer);
413
414         QStringList accessControlListLocal = accessControlList;
415         if (!aegisIdToken.isNull())
416             accessControlListLocal.prepend(aegisIdToken);
417
418         if (m_pInfo == 0) {
419             m_pInfo = new SignonIdentityInfo(id, userName, secret, storeSecret,
420                                              methods, caption, realms,
421                                              accessControlListLocal, type);
422         } else {
423             m_pInfo->setUserName(userName);
424             m_pInfo->setPassword(secret);
425             m_pInfo->setMethods(SignonIdentityInfo::mapVariantToMapList(methods));
426             m_pInfo->setCaption(caption);
427             m_pInfo->setRealms(realms);
428             m_pInfo->setAccessControlList(accessControlListLocal);
429             m_pInfo->setType(type);
430         }
431
432         TRACE() << m_pInfo->serialize();
433         storeCredentials(*m_pInfo, storeSecret);
434
435         if (m_id == SIGNOND_NEW_IDENTITY) {
436             replyError(SIGNOND_STORE_FAILED_ERR_NAME,
437                        SIGNOND_STORE_FAILED_ERR_STR);
438         }
439
440         return m_id;
441     }
442
443     quint32 SignonIdentity::storeCredentials(const SignonIdentityInfo &info, bool storeSecret)
444     {
445         CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
446         if (db == NULL) {
447             BLAME() << "NULL database handler object.";
448             return SIGNOND_NEW_IDENTITY;
449         }
450
451         bool newIdentity = info.isNew();
452
453         if (newIdentity)
454             m_id = db->insertCredentials(info, storeSecret);
455         else
456             db->updateCredentials(info, storeSecret);
457
458         if (db->errorOccurred()) {
459             if (newIdentity)
460                 m_id = SIGNOND_NEW_IDENTITY;
461
462             TRACE() << "Error occurred while inserting/updating credentials.";
463         } else {
464             if (m_pInfo) {
465                 delete m_pInfo;
466                 m_pInfo = NULL;
467             }
468             m_pSignonDaemon->identityStored(this);
469             TRACE() << "FRESH, JUST STORED CREDENTIALS ID:" << m_id;
470             emit infoUpdated((int)SignOn::IdentityDataUpdated);
471         }
472         return m_id;
473     }
474
475     void SignonIdentity::queryUiSlot(QDBusPendingCallWatcher *call)
476     {
477         TRACE();
478         setAutoDestruct(true);
479
480         if (call)
481             call->deleteLater();
482
483         QDBusMessage errReply;
484         QDBusPendingReply<QVariantMap> reply = *call;
485         QVariantMap resultParameters;
486         if (!reply.isError() && reply.count()) {
487             resultParameters = reply.argumentAt<0>();
488         } else {
489             errReply = m_message.createErrorReply(SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_NAME,
490                     SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_STR);
491             SIGNOND_BUS.send(errReply);
492             return;
493         }
494
495         TRACE() << resultParameters;
496
497         if (!resultParameters.contains(SSOUI_KEY_ERROR)) {
498             //no reply code
499             errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
500                     SIGNOND_INTERNAL_SERVER_ERR_STR);
501             SIGNOND_BUS.send(errReply);
502             return;
503         }
504
505         int errorCode = resultParameters.value(SSOUI_KEY_ERROR).toInt();
506         TRACE() << "error: " << errorCode;
507         if (errorCode != QUERY_ERROR_NONE) {
508             if (errorCode == QUERY_ERROR_CANCELED)
509                 errReply = m_message.createErrorReply(SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_NAME,
510                         SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_STR);
511             else
512                 errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
513                         QString(QLatin1String("signon-ui call returned error %1")).arg(errorCode));
514
515             SIGNOND_BUS.send(errReply);
516             return;
517         }
518
519         if (resultParameters.contains(SSOUI_KEY_PASSWORD)) {
520             CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
521             if (db == NULL) {
522                 BLAME() << "NULL database handler object.";
523                 errReply = m_message.createErrorReply(SIGNOND_STORE_FAILED_ERR_NAME,
524                         SIGNOND_STORE_FAILED_ERR_STR);
525                 SIGNOND_BUS.send(errReply);
526                 return;
527             }
528
529             //store new password
530             if (m_pInfo) {
531                 m_pInfo->setPassword(resultParameters[SSOUI_KEY_PASSWORD].toString());
532
533                 quint32 ret = db->updateCredentials(*m_pInfo, true);
534                 delete m_pInfo;
535                 m_pInfo = NULL;
536                 if (ret != SIGNOND_NEW_IDENTITY) {
537                     QDBusMessage dbusreply = m_message.createReply();
538                     dbusreply << quint32(m_id);
539                     SIGNOND_BUS.send(dbusreply);
540                     return;
541                 } else{
542                     BLAME() << "Error during update";
543                 }
544             }
545         }
546
547         //this should not happen, return error
548         errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
549                 SIGNOND_INTERNAL_SERVER_ERR_STR);
550         SIGNOND_BUS.send(errReply);
551         return;
552     }
553
554     void SignonIdentity::verifyUiSlot(QDBusPendingCallWatcher *call)
555     {
556         TRACE();
557         setAutoDestruct(true);
558
559         if (call)
560             call->deleteLater();
561
562         QDBusMessage errReply;
563         QDBusPendingReply<QVariantMap> reply = *call;
564         QVariantMap resultParameters;
565         if (!reply.isError() && reply.count()) {
566             resultParameters = reply.argumentAt<0>();
567         } else {
568             errReply = m_message.createErrorReply(SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_NAME,
569                     SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_STR);
570             SIGNOND_BUS.send(errReply);
571             return;
572         }
573
574         TRACE() << resultParameters;
575
576         if (!resultParameters.contains(SSOUI_KEY_ERROR)) {
577             //no reply code
578             errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
579                     SIGNOND_INTERNAL_SERVER_ERR_STR);
580             SIGNOND_BUS.send(errReply);
581             return;
582         }
583
584         int errorCode = resultParameters.value(SSOUI_KEY_ERROR).toInt();
585         TRACE() << "error: " << errorCode;
586         if (errorCode != QUERY_ERROR_NONE) {
587             if (errorCode == QUERY_ERROR_CANCELED)
588                 errReply = m_message.createErrorReply(SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_NAME,
589                         SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_STR);
590             else if (errorCode == QUERY_ERROR_FORGOT_PASSWORD)
591                 errReply = m_message.createErrorReply(SIGNOND_FORGOT_PASSWORD_ERR_NAME,
592                         SIGNOND_FORGOT_PASSWORD_ERR_STR);
593             else
594                 errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
595                         QString(QLatin1String("signon-ui call returned error %1")).arg(errorCode));
596
597             SIGNOND_BUS.send(errReply);
598             return;
599         }
600
601         if (resultParameters.contains(SSOUI_KEY_PASSWORD)) {
602             CredentialsDB *db = CredentialsAccessManager::instance()->credentialsDB();
603             if (db == NULL) {
604                 BLAME() << "NULL database handler object.";
605                 errReply = m_message.createErrorReply(SIGNOND_STORE_FAILED_ERR_NAME,
606                         SIGNOND_STORE_FAILED_ERR_STR);
607                 SIGNOND_BUS.send(errReply);
608                 return;
609             }
610
611             //compare passwords
612             if (m_pInfo) {
613                 bool ret = m_pInfo->password() == resultParameters[SSOUI_KEY_PASSWORD].toString();
614
615                 delete m_pInfo;
616                 m_pInfo = NULL;
617                 QDBusMessage dbusreply = m_message.createReply();
618                 dbusreply << ret;
619                 SIGNOND_BUS.send(dbusreply);
620                 return;
621             }
622         }
623         //this should not happen, return error
624         errReply = m_message.createErrorReply(SIGNOND_INTERNAL_SERVER_ERR_NAME,
625                 SIGNOND_INTERNAL_SERVER_ERR_STR);
626         SIGNOND_BUS.send(errReply);
627         return;
628     }
629
630 } //namespace SignonDaemonNS