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 ¶ms)
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