1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  * Copyright (C) 2013-2016 Canonical Ltd.
6  *
7  * Contact: Alberto Mardegan <alberto.mardegan@canonical.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 #include <stdarg.h>
24 
25 #include <QByteArray>
26 #include <QDBusArgument>
27 #include <QDBusPendingReply>
28 #include <QTimer>
29 
30 #include "signond/signoncommon.h"
31 
32 #include "debug.h"
33 #include "libsignoncommon.h"
34 #include "identityimpl.h"
35 #include "identityinfo.h"
36 #include "identityinfoimpl.h"
37 #include "authsessionimpl.h"
38 #include "signonerror.h"
39 
40 #define SIGNOND_AUTH_SESSION_CANCEL_TIMEOUT 5000 //ms
41 
42 using namespace SignOn;
43 
44 /* Keep these aligned with the State enum */
45 static const char *stateNames[] = {
46     "PendingRegistration",
47     "NeedsRegistration",
48     "NeedsUpdate",
49     "PendingUpdate",
50     "Removed",
51     "Ready",
52 };
53 
stateName(IdentityImpl::State state)54 static QString stateName(IdentityImpl::State state)
55 {
56     return QLatin1String((state < IdentityImpl::LastState) ?
57                          stateNames[state] : "Unknown");
58 }
59 
IdentityImpl(Identity * parent,const quint32 id)60 IdentityImpl::IdentityImpl(Identity *parent, const quint32 id):
61     QObject(parent),
62     m_parent(parent),
63     m_identityInfo(new IdentityInfo),
64     m_dbusProxy(SIGNOND_IDENTITY_INTERFACE_C,
65                 this),
66     m_tmpIdentityInfo(NULL),
67     m_state(NeedsRegistration),
68     m_infoQueried(true),
69     m_methodsQueried(false),
70     m_signOutRequestedByThisIdentity(false)
71 {
72     m_dbusProxy.connect("infoUpdated", this, SLOT(infoUpdated(int)));
73     m_dbusProxy.connect("unregistered", this, SLOT(remoteObjectDestroyed()));
74     QObject::connect(&m_dbusProxy, SIGNAL(objectPathNeeded()),
75                      this, SLOT(sendRegisterRequest()));
76 
77     m_identityInfo->setId(id);
78     sendRegisterRequest();
79 }
80 
~IdentityImpl()81 IdentityImpl::~IdentityImpl()
82 {
83     if (m_identityInfo)
84         delete m_identityInfo;
85 
86     if (m_tmpIdentityInfo)
87         delete m_tmpIdentityInfo;
88 
89     if (!m_authSessions.empty())
90         foreach (AuthSession *session, m_authSessions)
91             destroySession(session);
92 }
93 
updateState(State state)94 void IdentityImpl::updateState(State state)
95 {
96     TRACE() << "Updating state: " << stateName(state) << this;
97 
98     m_state = state;
99     switch (state)
100     {
101     case NeedsUpdate:
102         updateContents();
103         break;
104     default:
105         break;
106     }
107 }
108 
copyInfo(const IdentityInfo & info)109 void IdentityImpl::copyInfo(const IdentityInfo &info)
110 {
111     *m_identityInfo->impl = *info.impl;
112 }
113 
id() const114 quint32 IdentityImpl::id() const
115 {
116    return m_identityInfo->id();
117 }
118 
createSession(const QString & methodName,QObject * parent)119 AuthSession *IdentityImpl::createSession(const QString &methodName,
120                                          QObject *parent)
121 {
122     foreach (AuthSession *authSession, m_authSessions) {
123         if (authSession->name() == methodName) {
124             qWarning() << QString::fromLatin1(
125                     "Authentication session for method "
126                     "`%1` already requested.").arg(methodName);
127             return 0;
128         }
129     }
130 
131     AuthSession *session = new AuthSession(id(), methodName, parent);
132     m_authSessions.append(session);
133     return session;
134 }
135 
destroySession(AuthSession * session)136 void IdentityImpl::destroySession(AuthSession *session)
137 {
138     session->blockSignals(true);
139     m_authSessions.removeOne(session);
140     session->deleteLater();
141 }
142 
queryAvailableMethods()143 void IdentityImpl::queryAvailableMethods()
144 {
145     TRACE() << "Querying available identity authentication methods.";
146     if (!checkRemoved()) return;
147 
148     if (m_state == Ready) {
149         emit m_parent->methodsAvailable(m_identityInfo->methods());
150         return;
151     }
152 
153     m_methodsQueried = true;
154     updateContents();
155 }
156 
requestCredentialsUpdate(const QString & message)157 void IdentityImpl::requestCredentialsUpdate(const QString &message)
158 {
159     TRACE() << "Requesting credentials update.";
160     if (!checkRemoved()) return;
161 
162     QVariantList args;
163     args << message;
164     m_dbusProxy.queueCall(QLatin1String("requestCredentialsUpdate"),
165                           args,
166                           SLOT(storeCredentialsReply(QDBusPendingCallWatcher*)),
167                           SLOT(errorReply(const QDBusError&)));
168 }
169 
storeCredentials(const IdentityInfo & info)170 void IdentityImpl::storeCredentials(const IdentityInfo &info)
171 {
172     TRACE() << "Storing credentials";
173 
174     if (m_state == Removed) {
175         updateState(NeedsRegistration);
176     }
177 
178     const IdentityInfo *localInfo = info.impl->isEmpty() ? m_identityInfo : &info;
179 
180     if (localInfo->impl->isEmpty()) {
181         emit m_parent->error(
182             Error(Error::StoreFailed,
183                   QLatin1String("Invalid Identity data.")));
184         return;
185     }
186 
187     QVariantList args;
188     QVariantMap map = localInfo->impl->toMap();
189     map.insert(SIGNOND_IDENTITY_INFO_ID, m_identityInfo->id());
190     args << map;
191 
192     m_dbusProxy.queueCall(QLatin1String("store"), args,
193                           SLOT(storeCredentialsReply(QDBusPendingCallWatcher*)),
194                           SLOT(errorReply(const QDBusError&)));
195 }
196 
remove()197 void IdentityImpl::remove()
198 {
199     TRACE() << "Removing credentials.";
200 
201     /* If the Identity is not stored, it makes no sense to request a removal
202      * -- this condition could be removed; there is the case when there is an
203      * ongoing store operation.
204      */
205 
206     if (id() != SIGNOND_NEW_IDENTITY) {
207         m_dbusProxy.queueCall(QLatin1String("remove"),
208                               QVariantList(),
209                               SLOT(removeReply()),
210                               SLOT(errorReply(const QDBusError&)));
211     } else {
212         emit m_parent->error(Error(Error::IdentityNotFound,
213                                    QLatin1String("Remove request failed. The "
214                                                  "identity is not stored")));
215     }
216 }
217 
addReference(const QString & reference)218 void IdentityImpl::addReference(const QString &reference)
219 {
220     TRACE() << "Adding reference to identity";
221     if (!checkRemoved()) return;
222 
223     m_dbusProxy.queueCall(QLatin1String("addReference"),
224                           QVariantList() << QVariant(reference),
225                           SLOT(addReferenceReply()),
226                           SLOT(errorReply(const QDBusError&)));
227 }
228 
removeReference(const QString & reference)229 void IdentityImpl::removeReference(const QString &reference)
230 {
231     TRACE() << "Removing reference from identity";
232     if (!checkRemoved()) return;
233 
234     m_dbusProxy.queueCall(QLatin1String("removeReference"),
235                           QVariantList() << QVariant(reference),
236                           SLOT(removeReferenceReply()),
237                           SLOT(errorReply(const QDBusError&)));
238 }
239 
queryInfo()240 void IdentityImpl::queryInfo()
241 {
242     TRACE() << "Querying info.";
243     if (!checkRemoved()) return;
244 
245     if (m_state == Ready) {
246         emit m_parent->info(IdentityInfo(*m_identityInfo));
247         return;
248     }
249 
250     m_infoQueried = true;
251     updateContents();
252 }
253 
verifyUser(const QString & message)254 void IdentityImpl::verifyUser(const QString &message)
255 {
256     QVariantMap params;
257     params.insert(QLatin1String("QueryMessage"), message);
258     verifyUser(params);
259 }
260 
verifyUser(const QVariantMap & params)261 void IdentityImpl::verifyUser(const QVariantMap &params)
262 {
263     TRACE() << "Verifying user.";
264     if (!checkRemoved()) return;
265 
266     m_dbusProxy.queueCall(QLatin1String("verifyUser"),
267                           QVariantList() << params,
268                           SLOT(verifyUserReply(QDBusPendingCallWatcher*)),
269                           SLOT(errorReply(const QDBusError&)));
270 }
271 
verifySecret(const QString & secret)272 void IdentityImpl::verifySecret(const QString &secret)
273 {
274     TRACE();
275     if (!checkRemoved()) return;
276 
277     m_dbusProxy.queueCall(QLatin1String("verifySecret"),
278                           QVariantList() << QVariant(secret),
279                           SLOT(verifySecretReply(QDBusPendingCallWatcher*)),
280                           SLOT(errorReply(const QDBusError&)));
281 }
282 
signOut()283 void IdentityImpl::signOut()
284 {
285     TRACE() << "Signing out.";
286     if (!checkRemoved()) return;
287 
288     /* if this is a stored identity, inform server about signing out
289        so that other client identity objects having the same id will
290        be able to perform the operation.
291     */
292     if (id() != SIGNOND_NEW_IDENTITY) {
293         m_dbusProxy.queueCall(QLatin1String("signOut"),
294                               QVariantList(),
295                               SLOT(signOutReply()),
296                               SLOT(errorReply(const QDBusError&)));
297         m_signOutRequestedByThisIdentity = true;
298     }
299 
300     clearAuthSessionsCache();
301 }
302 
clearAuthSessionsCache()303 void IdentityImpl::clearAuthSessionsCache()
304 {
305     while (!m_authSessions.empty()) {
306         AuthSession *session = m_authSessions.takeFirst();
307         connect(session,
308                 SIGNAL(error(const SignOn::Error &)),
309                 this,
310                 SLOT(authSessionCancelReply(const SignOn::Error &)));
311 
312         session->cancel();
313         QTimer::singleShot(SIGNOND_AUTH_SESSION_CANCEL_TIMEOUT,
314                            session, SLOT(deleteLater()));
315     }
316 }
317 
authSessionCancelReply(const SignOn::Error & err)318 void IdentityImpl::authSessionCancelReply(const SignOn::Error &err)
319 {
320     TRACE() << "CANCEL SESSION REPLY";
321 
322     bool deleteTheSender = false;
323     switch (err.type()) {
324     /* fall trough */
325     case Error::SessionCanceled:
326     case Error::WrongState:
327         deleteTheSender = true;
328         break;
329     default: break;
330     }
331 
332     if (deleteTheSender) {
333         QObject *sender = QObject::sender();
334         if (sender) {
335             TRACE() << "DELETING SESSION";
336             sender->deleteLater();
337         }
338     }
339 }
340 
storeCredentialsReply(QDBusPendingCallWatcher * call)341 void IdentityImpl::storeCredentialsReply(QDBusPendingCallWatcher *call)
342 {
343     QDBusPendingReply<quint32> reply = *call;
344     quint32 id = reply.argumentAt<0>();
345     TRACE() << "stored id:" << id << "old id:" << this->id();
346     if (m_tmpIdentityInfo) {
347         *m_identityInfo = *m_tmpIdentityInfo;
348         delete m_tmpIdentityInfo;
349         m_tmpIdentityInfo = NULL;
350     }
351 
352     if (id != this->id()) {
353         m_identityInfo->setId(id);
354         foreach (AuthSession *session, m_authSessions)
355             session->impl->setId(id);
356     }
357     emit m_parent->credentialsStored(id);
358 }
359 
removeReply()360 void IdentityImpl::removeReply()
361 {
362     m_identityInfo->impl->clear();
363     updateState(Removed);
364     emit m_parent->removed();
365 }
366 
addReferenceReply()367 void IdentityImpl::addReferenceReply()
368 {
369     emit m_parent->referenceAdded();
370 }
371 
removeReferenceReply()372 void IdentityImpl::removeReferenceReply()
373 {
374     emit m_parent->referenceRemoved();
375 }
376 
getInfoReply(QDBusPendingCallWatcher * call)377 void IdentityImpl::getInfoReply(QDBusPendingCallWatcher *call)
378 {
379     QDBusPendingReply<QVariantMap> reply = *call;
380     QVariantMap infoData = reply.argumentAt<0>();
381     TRACE() << infoData;
382     updateCachedData(infoData);
383     updateState(Ready);
384 
385     if (m_infoQueried) {
386         Q_EMIT m_parent->info(IdentityInfo(*m_identityInfo));
387         m_infoQueried = false;
388     }
389 
390     if (m_methodsQueried) {
391         Q_EMIT m_parent->methodsAvailable(m_identityInfo->methods());
392         m_methodsQueried = false;
393     }
394 }
395 
verifyUserReply(QDBusPendingCallWatcher * call)396 void IdentityImpl::verifyUserReply(QDBusPendingCallWatcher *call)
397 {
398     QDBusPendingReply<bool> reply = *call;
399     bool valid = reply.argumentAt<0>();
400     emit m_parent->userVerified(valid);
401 }
402 
verifySecretReply(QDBusPendingCallWatcher * call)403 void IdentityImpl::verifySecretReply(QDBusPendingCallWatcher *call)
404 {
405     QDBusPendingReply<bool> reply = *call;
406     bool valid = reply.argumentAt<0>();
407     emit m_parent->secretVerified(valid);
408 }
409 
signOutReply()410 void IdentityImpl::signOutReply()
411 {
412     emit m_parent->signedOut();
413 }
414 
infoUpdated(int state)415 void IdentityImpl::infoUpdated(int state)
416 {
417     const char *stateStr;
418     switch ((IdentityState)state) {
419     /* Data updated on the server side. */
420     case IdentityDataUpdated:
421         updateState(NeedsUpdate);
422         stateStr = "NeedsUpdate";
423         break;
424     /* Data removed on the server side. */
425     case IdentityRemoved:
426         updateState(Removed);
427         stateStr = "Removed";
428         break;
429     /* A remote client identity signed out,
430        thus server informed this object to do the same */
431     case IdentitySignedOut:
432         //if this is not the identity that requested the signing out
433         if (!m_signOutRequestedByThisIdentity) {
434             clearAuthSessionsCache();
435             emit m_parent->signedOut();
436         }
437         stateStr = "SignedOut";
438         break;
439     default: stateStr = "Unknown";
440         break;
441     }
442     TRACE() << "SERVER INFO UPDATED." << stateStr <<
443         QString(QLatin1String(" %1 ")).arg(id());
444 }
445 
errorReply(const QDBusError & err)446 void IdentityImpl::errorReply(const QDBusError& err)
447 {
448     TRACE() << err;
449 
450     /* Signon specific errors */
451     if (err.name() == SIGNOND_UNKNOWN_ERR_NAME) {
452         emit m_parent->error(Error(Error::Unknown, err.message()));
453         return;
454     } else if (err.name() == SIGNOND_INTERNAL_SERVER_ERR_NAME) {
455         emit m_parent->error(Error(Error::InternalServer, err.message()));
456         return;
457     } else if (err.name() == SIGNOND_PERMISSION_DENIED_ERR_NAME) {
458         emit m_parent->error(Error(Error::PermissionDenied, err.message()));
459         return;
460     } else if (err.name() == SIGNOND_ENCRYPTION_FAILED_ERR_NAME) {
461         emit m_parent->error(Error(Error::EncryptionFailure, err.message()));
462         return;
463     } else if (err.name() == SIGNOND_METHOD_NOT_AVAILABLE_ERR_NAME) {
464         emit m_parent->error(Error(Error::MethodNotAvailable, err.message()));
465         return;
466     } else if (err.name() == SIGNOND_IDENTITY_NOT_FOUND_ERR_NAME) {
467         emit m_parent->error(Error(Error::IdentityNotFound, err.message()));
468         return;
469     } else if (err.name() == SIGNOND_STORE_FAILED_ERR_NAME) {
470         emit m_parent->error(Error(Error::StoreFailed, err.message()));
471         if (m_tmpIdentityInfo) {
472             delete m_tmpIdentityInfo;
473             m_tmpIdentityInfo = NULL;
474         }
475         return;
476     } else if (err.name() == SIGNOND_REMOVE_FAILED_ERR_NAME) {
477         emit m_parent->error(Error(Error::RemoveFailed, err.message()));
478         return;
479     } else if (err.name() == SIGNOND_SIGNOUT_FAILED_ERR_NAME) {
480         emit m_parent->error(Error(Error::SignOutFailed, err.message()));
481         return;
482     } else if (err.name() == SIGNOND_IDENTITY_OPERATION_CANCELED_ERR_NAME) {
483         emit m_parent->error(Error(Error::IdentityOperationCanceled,
484                                    err.message()));
485         return;
486     } else if (err.name() == SIGNOND_CREDENTIALS_NOT_AVAILABLE_ERR_NAME) {
487         emit m_parent->error(Error(Error::CredentialsNotAvailable,
488                                    err.message()));
489         return;
490     } else if (err.name() == SIGNOND_REFERENCE_NOT_FOUND_ERR_NAME) {
491        emit m_parent->error(Error(Error::ReferenceNotFound, err.message()));
492        return;
493     } else if (err.name() == SIGNOND_FORGOT_PASSWORD_ERR_NAME) {
494        emit m_parent->error(Error(Error::ForgotPassword, err.message()));
495        return;
496     } else {
497         if (m_state == this->PendingRegistration)
498             updateState(NeedsRegistration);
499 
500         TRACE() << "Non internal SSO error reply.";
501     }
502 
503     /* Qt DBUS specific errors */
504     if (err.type() != QDBusError::NoError) {
505         emit m_parent->error(Error(Error::InternalCommunication,
506                                    err.message()));
507         return;
508     }
509 
510     emit m_parent->error(Error(Error::Unknown, err.message()));
511 }
512 
updateContents()513 void IdentityImpl::updateContents()
514 {
515     m_dbusProxy.queueCall(QLatin1String("getInfo"),
516                           QVariantList(),
517                           SLOT(getInfoReply(QDBusPendingCallWatcher*)),
518                           SLOT(errorReply(const QDBusError&)));
519     updateState(PendingUpdate);
520 }
521 
sendRegisterRequest()522 bool IdentityImpl::sendRegisterRequest()
523 {
524     if (m_state == PendingRegistration) return true;
525 
526     QVariantList args;
527     QString registerMethodName = QLatin1String("registerNewIdentity");
528 
529     if (id() != SIGNOND_NEW_IDENTITY) {
530         registerMethodName = QLatin1String("getIdentity");
531         args << m_identityInfo->id();
532     }
533 
534     /* TODO: implement the application security context */
535     args << QLatin1String("*");
536     SignondAsyncDBusProxy *authService =
537         new SignondAsyncDBusProxy(SIGNOND_DAEMON_INTERFACE_C, this);
538     authService->setObjectPath(QDBusObjectPath(SIGNOND_DAEMON_OBJECTPATH));
539 
540     PendingCall *call =
541         authService->queueCall(registerMethodName,
542                                args,
543                                SLOT(registerReply(QDBusPendingCallWatcher*)),
544                                SLOT(errorReply(const QDBusError&)));
545     QObject::connect(call, SIGNAL(finished(QDBusPendingCallWatcher*)),
546                      this, SLOT(deleteServiceProxy()));
547     updateState(PendingRegistration);
548     return true;
549 }
550 
updateCachedData(const QVariantMap & infoData)551 void IdentityImpl::updateCachedData(const QVariantMap &infoData)
552 {
553     m_identityInfo->impl->updateFromMap(infoData);
554 }
555 
registerReply(QDBusPendingCallWatcher * call)556 void IdentityImpl::registerReply(QDBusPendingCallWatcher *call)
557 {
558     QVariantList arguments = call->reply().arguments();
559     if (arguments.count() > 1) {
560         QDBusArgument info = arguments.at(1).value<QDBusArgument>();
561         updateCachedData(qdbus_cast<QVariantMap>(info));
562     }
563 
564     m_dbusProxy.setObjectPath(arguments.at(0).value<QDBusObjectPath>());
565     updateState(Ready);
566 }
567 
deleteServiceProxy()568 void IdentityImpl::deleteServiceProxy()
569 {
570     PendingCall *call = qobject_cast<PendingCall*>(sender());
571     /* This destroys the AsyncDBusProxy which we created just for registering
572      * the identity. */
573     call->parent()->deleteLater();
574 }
575 
remoteObjectDestroyed()576 void IdentityImpl::remoteObjectDestroyed()
577 {
578     TRACE();
579     m_dbusProxy.setObjectPath(QDBusObjectPath());
580     updateState(NeedsRegistration);
581 }
582 
checkRemoved()583 bool IdentityImpl::checkRemoved()
584 {
585     if (m_state == Removed) {
586         Q_EMIT m_parent->error(Error(Error::IdentityNotFound,
587                                      QLatin1String("Removed from database.")));
588         return false;
589     }
590     return true;
591 }
592