Hello Buteo team! The UI design for using Accounts+SSO in the open source MeeGo UX is still under debate. But we wanted to use at least SSO as part of the UX to share credentials between apps, so I came up with a patch that does that without depending on Accounts.
This was done before you agreed to implement feature "[FEA] SyncML credentials stored in Accounts&SSO" [1], which to my knowledge hasn't been completed yet. I see this patch as a stop-gap measure that can be removed and/or deprecated once a full solution is in place both in Buteo and the UX. In the meantime, please consider including it because we have code which is meant to use this feature. The patch was meant to be as unintrusive as possible and therefore adds neither a new API nor a new XML config option. It merely interprets a special value for "Username" and then passes the real credentials at the place where sync plugins expect them, the profile instance passed to the plugins. As before, patch attached and in a git repo [2]. [1] http://bugs.meego.com/show_bug.cgi?id=8027 [2] "shared-credentials" branch, http://meego.gitorious.org/~pohly/meego-middleware/pohlys-buteo-syncfw -- Best Regards Patrick Ohly Senior Software Engineer Intel GmbH Open Source Technology Center Pützstr. 5 Phone: +49-228-2493652 53129 Bonn Germany
>From 2dce4bbc7c66d3f044174adb49bb07d43a1e8109 Mon Sep 17 00:00:00 2001 From: Patrick Ohly <[email protected]> Date: Fri, 12 Nov 2010 15:57:14 +0100 Subject: [PATCH 1/2] client plugins: added automatic retrieval of credentials from SSO If the "Username" value is of the format "sso-provider=<name>", then the msyncd will automatically retrieve the real username and password from SSO. The identity is found via its caption string, which must equal "<name>". This additional lookup is completely transparent to client plugins. The profile which is passed to init() will have "Username" and "Key" set to the values from SSO. If credentials retrieval fails, an error is created without ever calling init(). The SSO interaction happens in the calling application thread. That is easier because signal/slot connections then don't have to work across threads (the work is done by the ClientThread instance, which remains on the application thread). --- libsyncpluginmgr/ClientPlugin.h | 3 + msyncd/ClientThread.cpp | 86 +++++++++++++++++++++++++++++++++----- msyncd/ClientThread.h | 34 ++++++++++++++- msyncd/msyncd.pro | 3 +- 4 files changed, 111 insertions(+), 15 deletions(-) diff --git a/libsyncpluginmgr/ClientPlugin.h b/libsyncpluginmgr/ClientPlugin.h index b9b44fb..61e0b56 100644 --- a/libsyncpluginmgr/ClientPlugin.h +++ b/libsyncpluginmgr/ClientPlugin.h @@ -63,6 +63,9 @@ public: */ virtual bool startSync() = 0; + /*! \brief access to profile owned and used by this instance + */ + SyncProfile &profile() { return iProfile; } protected: diff --git a/msyncd/ClientThread.cpp b/msyncd/ClientThread.cpp index 935666e..8992961 100644 --- a/msyncd/ClientThread.cpp +++ b/msyncd/ClientThread.cpp @@ -29,6 +29,9 @@ using namespace Buteo; ClientThread::ClientThread() : iClientPlugin( 0 ), + iIdentity(NULL), + iService(NULL), + iSession(NULL), iRunning(false) { FUNCTION_CALL_TRACE; @@ -37,6 +40,10 @@ ClientThread::ClientThread() ClientThread::~ClientThread() { FUNCTION_CALL_TRACE; + if (iSession) { + iIdentity->destroySession(iSession); + } + delete iIdentity; } QString ClientThread::getProfileName() const @@ -78,11 +85,30 @@ bool ClientThread::startThread( ClientPlugin* aClientPlugin ) } iClientPlugin = aClientPlugin; + if (iClientPlugin == 0) + { + LOG_CRITICAL("Client plugin is NULL"); + return false; + } - // Move to client thread - iClientPlugin->moveToThread( this ); - - start(); + SyncProfile &profile = iClientPlugin->profile(); + const QString prefix("sso-provider="); + QString username = profile.key("Username"); + if (username.startsWith(prefix)) { + // Look up real username/password in SSO first, + // before starting sync. This is better done + // in the application thread, because this is where + // this instance lives. + iProvider = username.mid(prefix.size()); + iService = new SignOn::AuthService(this); + connect(iService, SIGNAL(identities(const QList<IdentityInfo> &)), + this, SLOT(identities(const QList<IdentityInfo> &))); + iService->queryIdentities(); + } else { + // Move to client thread + iClientPlugin->moveToThread( this ); + start(); + } return true; } @@ -98,18 +124,12 @@ void ClientThread::run() { FUNCTION_CALL_TRACE; - if (iClientPlugin == 0) - { - LOG_CRITICAL("Client plugin is NULL"); - return; - } - if( !iClientPlugin->init() ) { LOG_DEBUG( "Could not initialize client plugin:" << iClientPlugin->getPluginName() ); emit initError( getProfileName(), "", 0 ); return; } - + if( !iClientPlugin->startSync() ) { LOG_DEBUG( "Could not start client plugin:" << iClientPlugin->getPluginName() ); emit initError( getProfileName(), "", 0 ); @@ -132,7 +152,6 @@ void ClientThread::run() } - SyncResults ClientThread::getSyncResults() { FUNCTION_CALL_TRACE; @@ -140,3 +159,46 @@ SyncResults ClientThread::getSyncResults() return iSyncResults; } +void ClientThread::identities(const QList<SignOn::IdentityInfo> &identityList) +{ + FUNCTION_CALL_TRACE; + + for (int i = 0; i < identityList.size(); ++i) { + const SignOn::IdentityInfo &info = identityList.at(i); + if (info.caption() == iProvider) { + iIdentity = SignOn::Identity::existingIdentity(info.id(), this); + // Setup an authentication session using the "password" method + iSession = + iIdentity->createSession(QLatin1String("password")); + connect(iSession, SIGNAL(response(const SessionData &)), + this, SLOT(identityResponse(const SessionData &))); + connect(iSession, SIGNAL(error(const Error &)), + this, SLOT(identityError(const Error &))); + // Get the password! + iSession->process(SessionData(), QLatin1String("password")); + return; + } + } + emit initError( getProfileName(), "credentials not found in SSO", 0 ); +} + +void ClientThread::identityResponse(const SignOn::SessionData &sessionData) +{ + FUNCTION_CALL_TRACE; + + // temporarily set real username/password, then invoke client + SyncProfile &profile = iClientPlugin->profile(); + profile.setKey("Username", sessionData.UserName()); + profile.setKey("Password", sessionData.Secret()); + + // delayed starting of thread + iClientPlugin->moveToThread( this ); + start(); +} + +void ClientThread::identityError(const SignOn::Error &err) +{ + FUNCTION_CALL_TRACE; + + emit initError( getProfileName(), err.message(), 0 ); +} diff --git a/msyncd/ClientThread.h b/msyncd/ClientThread.h index 5f84fa6..b4915f8 100644 --- a/msyncd/ClientThread.h +++ b/msyncd/ClientThread.h @@ -27,8 +27,15 @@ #include <QMutex> #include <SyncResults.h> +#include "SignOn/AuthService" +#include "SignOn/Identity" + namespace Buteo { +// temporary workaround for old libsignon-qt: declares signal without +// namespace, so app must do the same +using namespace SignOn; + class ClientPlugin; /*! \brief Thread for client plugins @@ -102,16 +109,39 @@ private: SyncResults iSyncResults; - bool iRunning; + SignOn::Identity *iIdentity; + SignOn::AuthService *iService; + SignOn::AuthSession *iSession; + QString iProvider; + bool iRunning; + mutable QMutex iMutex; #ifdef SYNCFW_UNIT_TESTS friend class ClientThreadTest; #endif + /*! + * \brief invokes iClientPlugin->startSync() + * + * It should be called when profile is ready for use, with + * credentials set in the Username/Password keys. It is called + * either in run() or, if the Username key starts with the + * "sso-provider=" prefix, after retrieving the credentials from + * SSO (queryIdentities() -> identities() -> session -> + * identityResponse() -> startSync()). + * + * @return true for success (run thread), else failure (running + * thread is no longer necessary) + */ + bool startSync(); - +private slots: + // temporary: IdentityInfo without SignOn:: namespace + void identities(const QList<IdentityInfo> &identityList); + void identityResponse(const SessionData &session); + void identityError(const Error &error); }; } diff --git a/msyncd/msyncd.pro b/msyncd/msyncd.pro index 426cf99..3c2724a 100644 --- a/msyncd/msyncd.pro +++ b/msyncd/msyncd.pro @@ -26,7 +26,8 @@ INCLUDEPATH += . \ /usr/include/accounts-qt \ /usr/include/iphbd/ \ -PKGCONFIG += dbus-1 +PKGCONFIG += dbus-1 libsignon-qt + QMAKE_LIBDIR_QT += ../libsyncprofile/ LIBS += -L../libsyncpluginmgr \ -- 1.7.2.3
>From 97fc306c693769f94eb3215fd0b076b297822e75 Mon Sep 17 00:00:00 2001 From: Patrick Ohly <[email protected]> Date: Wed, 8 Dec 2010 10:30:38 +0100 Subject: [PATCH 2/2] shared credentials: adapted to signon-qt API changes Signals/slots now use explicit SignOn namespace and Error is passed by value. --- msyncd/ClientThread.cpp | 16 ++++++++-------- msyncd/ClientThread.h | 11 +++-------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/msyncd/ClientThread.cpp b/msyncd/ClientThread.cpp index 8992961..88cf1fe 100644 --- a/msyncd/ClientThread.cpp +++ b/msyncd/ClientThread.cpp @@ -101,8 +101,8 @@ bool ClientThread::startThread( ClientPlugin* aClientPlugin ) // this instance lives. iProvider = username.mid(prefix.size()); iService = new SignOn::AuthService(this); - connect(iService, SIGNAL(identities(const QList<IdentityInfo> &)), - this, SLOT(identities(const QList<IdentityInfo> &))); + connect(iService, SIGNAL(identities(const QList<SignOn::IdentityInfo> &)), + this, SLOT(identities(const QList<SignOn::IdentityInfo> &))); iService->queryIdentities(); } else { // Move to client thread @@ -170,12 +170,12 @@ void ClientThread::identities(const QList<SignOn::IdentityInfo> &identityList) // Setup an authentication session using the "password" method iSession = iIdentity->createSession(QLatin1String("password")); - connect(iSession, SIGNAL(response(const SessionData &)), - this, SLOT(identityResponse(const SessionData &))); - connect(iSession, SIGNAL(error(const Error &)), - this, SLOT(identityError(const Error &))); + connect(iSession, SIGNAL(response(const SignOn::SessionData &)), + this, SLOT(identityResponse(const SignOn::SessionData &))); + connect(iSession, SIGNAL(error(SignOn::Error)), + this, SLOT(identityError(SignOn::Error))); // Get the password! - iSession->process(SessionData(), QLatin1String("password")); + iSession->process(SignOn::SessionData(), QLatin1String("password")); return; } } @@ -196,7 +196,7 @@ void ClientThread::identityResponse(const SignOn::SessionData &sessionData) start(); } -void ClientThread::identityError(const SignOn::Error &err) +void ClientThread::identityError(SignOn::Error err) { FUNCTION_CALL_TRACE; diff --git a/msyncd/ClientThread.h b/msyncd/ClientThread.h index b4915f8..5f2ca2a 100644 --- a/msyncd/ClientThread.h +++ b/msyncd/ClientThread.h @@ -32,10 +32,6 @@ namespace Buteo { -// temporary workaround for old libsignon-qt: declares signal without -// namespace, so app must do the same -using namespace SignOn; - class ClientPlugin; /*! \brief Thread for client plugins @@ -138,10 +134,9 @@ private: bool startSync(); private slots: - // temporary: IdentityInfo without SignOn:: namespace - void identities(const QList<IdentityInfo> &identityList); - void identityResponse(const SessionData &session); - void identityError(const Error &error); + void identities(const QList<SignOn::IdentityInfo> &identityList); + void identityResponse(const SignOn::SessionData &session); + void identityError(SignOn::Error error); }; } -- 1.7.2.3
_______________________________________________ MeeGo-dev mailing list [email protected] http://lists.meego.com/listinfo/meego-dev
