signon: move libaccounts-based plugins into their own source file

The libaccounts-based backends ("gsso" and "uoa") are going to use more
of libaccounts-glib, making sharing of code with the plain "signon"
plugin much harder to maintain.
This commit is contained in:
Alberto Mardegan 2014-12-05 16:33:45 +02:00 committed by Patrick Ohly
parent 75115022c5
commit ab4524ab9f
3 changed files with 287 additions and 176 deletions

View File

@ -0,0 +1,278 @@
/*
* Copyright (C) 2013 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <config.h>
#include <syncevo/IdentityProvider.h>
#ifdef USE_SIGNON
#ifdef USE_GSSO
#include "libgsignon-glib/signon-auth-service.h"
#include "libgsignon-glib/signon-identity.h"
#elif defined USE_UOA
#include "libsignon-glib/signon-auth-service.h"
#include "libsignon-glib/signon-identity.h"
#endif // USE_GSSO
#include "libaccounts-glib/ag-account.h"
#include "libaccounts-glib/ag-account-service.h"
#include <libaccounts-glib/ag-auth-data.h>
#include <libaccounts-glib/ag-service.h>
#include <libaccounts-glib/ag-manager.h>
#include <syncevo/GLibSupport.h>
#include <syncevo/GVariantSupport.h>
#include <pcrecpp.h>
#include <boost/lambda/core.hpp>
SE_GOBJECT_TYPE(SignonAuthService)
SE_GOBJECT_TYPE(SignonAuthSession)
SE_GOBJECT_TYPE(SignonIdentity)
SE_GOBJECT_TYPE(AgAccount)
SE_GOBJECT_TYPE(AgAccountService)
SE_GOBJECT_TYPE(AgManager)
SE_GLIB_TYPE(AgService, ag_service)
SE_GLIB_TYPE(AgAuthData, ag_auth_data)
#endif // USE_SIGNON
#include <syncevo/declarations.h>
SE_BEGIN_CXX
#ifdef USE_SIGNON
typedef GListCXX<AgService, GList, ag_service_unref> ServiceListCXX;
class SignonAuthProvider : public AuthProvider
{
SignonAuthSessionCXX m_authSession;
GHashTableCXX m_sessionData;
std::string m_mechanism;
public:
SignonAuthProvider(const SignonAuthSessionCXX &authSession,
const GHashTableCXX &sessionData,
const std::string &mechanism) :
m_authSession(authSession),
m_sessionData(sessionData),
m_mechanism(mechanism)
{}
virtual bool methodIsSupported(AuthMethod method) const { return method == AUTH_METHOD_OAUTH2; }
virtual Credentials getCredentials() const { SE_THROW("only OAuth2 is supported"); }
virtual std::string getOAuth2Bearer(int failedTokens,
const PasswordUpdateCallback &passwordUpdateCallback) const
{
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token, attempt %d", failedTokens);
// Retry login if even the refreshed token failed.
g_hash_table_insert(m_sessionData, g_strdup("UiPolicy"),
g_variant_ref_sink(g_variant_new_uint32(failedTokens >= 2 ? SIGNON_POLICY_REQUEST_PASSWORD : 0)));
// We get assigned a plain pointer to an instance that we'll own,
// so we have to use the "steal" variant to enable that assignment.
GVariantStealCXX resultDataVar;
GErrorCXX gerror;
GVariantCXX sessionDataVar(HashTable2Variant(m_sessionData));
PlainGStr buffer(g_variant_print(sessionDataVar, true));
SE_LOG_DEBUG(NULL, "asking for OAuth2 token with method %s, mechanism %s and parameters %s",
signon_auth_session_get_method(m_authSession),
m_mechanism.c_str(),
buffer.get());
#define signon_auth_session_process_async_finish signon_auth_session_process_finish
SYNCEVO_GLIB_CALL_SYNC(resultDataVar, gerror, signon_auth_session_process_async,
m_authSession, sessionDataVar, m_mechanism.c_str(), NULL);
buffer.reset(resultDataVar ? g_variant_print(resultDataVar, true) : NULL);
SE_LOG_DEBUG(NULL, "OAuth2 token result: %s, %s",
buffer.get() ? buffer.get() : "<<null>>",
gerror ? gerror->message : "???");
if (!resultDataVar || gerror) {
SE_THROW_EXCEPTION_STATUS(StatusException,
StringPrintf("could not obtain OAuth2 token: %s", gerror ? gerror->message : "???"),
STATUS_FORBIDDEN);
}
GHashTableCXX resultData(Variant2HashTable(resultDataVar));
GVariant *tokenVar = static_cast<GVariant *>(g_hash_table_lookup(resultData, (gpointer)"AccessToken"));
if (!tokenVar) {
SE_THROW("no AccessToken in OAuth2 response");
}
const char *token = g_variant_get_string(tokenVar, NULL);
if (!token) {
SE_THROW("AccessToken did not contain a string value");
}
return token;
}
virtual std::string getUsername() const { return ""; }
};
class StoreIdentityData
{
public:
StoreIdentityData() : m_running(true) {}
bool m_running;
guint32 m_id;
GErrorCXX m_gerror;
};
static void StoreIdentityCB(SignonIdentity *self,
guint32 id,
const GError *error,
gpointer userData)
{
StoreIdentityData *data = reinterpret_cast<StoreIdentityData *>(userData);
data->m_running = false;
data->m_id = id;
data->m_gerror = error;
}
boost::shared_ptr<AuthProvider> createSignonAuthProvider(const InitStateString &username,
const InitStateString &password)
{
boost::shared_ptr<AuthProvider> provider;
// Split username into <account ID> and <service name>.
// Be flexible and allow leading/trailing white space.
// Comma is optional.
static const pcrecpp::RE re("^\\s*(\\d+)\\s*,?\\s*(.*)\\s*$");
AgAccountId accountID;
std::string serviceName;
if (!re.FullMatch(username, &accountID, &serviceName)) {
SE_THROW(StringPrintf("username must have the format gsso:<account ID>,<service name>: %s",
username.c_str()));
}
SE_LOG_DEBUG(NULL, "looking up account ID %d and service '%s'",
accountID,
serviceName.c_str());
AgManagerCXX manager(ag_manager_new(), TRANSFER_REF);
GErrorCXX gerror;
AgAccountCXX account(ag_manager_load_account(manager, accountID, gerror), TRANSFER_REF);
if (!account) {
gerror.throwError(SE_HERE, StringPrintf("loading account with ID %d from %s failed",
accountID,
username.c_str()));
}
if (!ag_account_get_enabled(account)) {
SE_THROW(StringPrintf("account with ID %d from %s is disabled, refusing to use it",
accountID,
username.c_str()));
}
AgAccountServiceCXX accountService;
if (serviceName.empty()) {
accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, NULL));
} else {
ServiceListCXX services(ag_account_list_enabled_services(account));
BOOST_FOREACH (AgService *service, services) {
const char *name = ag_service_get_name(service);
SE_LOG_DEBUG(NULL, "enabled service: %s", name);
if (serviceName == name) {
accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, service));
// Do *not* select the service for reading/writing properties.
// AgAccountService does this internally, and when we create
// a new identity below, we want it to be shared by all
// services so that the user only needs to log in once.
// ag_account_select_service(account, service);
break;
}
}
}
if (!accountService) {
SE_THROW(StringPrintf("service '%s' in account with ID %d not found or not enabled",
serviceName.c_str(),
accountID));
}
AgAuthDataCXX authData(ag_account_service_get_auth_data(accountService), TRANSFER_REF);
// SignonAuthServiceCXX authService(signon_auth_service_new(), TRANSFER_REF);
guint signonID = ag_auth_data_get_credentials_id(authData);
const char *method = ag_auth_data_get_method(authData);
const char *mechanism = ag_auth_data_get_mechanism(authData);
GVariantCXX sessionDataVar(ag_auth_data_get_login_parameters(authData, NULL), TRANSFER_REF);
GHashTableCXX sessionData(Variant2HashTable(sessionDataVar));
#ifdef USE_GSSO
GVariant *realmsVariant = (GVariant *)g_hash_table_lookup(sessionData, "Realms");
PlainGStrArray realms(g_variant_dup_strv(realmsVariant, NULL));
#endif
// Check that the service has a credentials ID. If not, create it and
// store its ID permanently.
if (!signonID) {
SE_LOG_DEBUG(NULL, "have to create signon identity");
SignonIdentityCXX identity(signon_identity_new(), TRANSFER_REF);
boost::shared_ptr<SignonIdentityInfo> identityInfo(signon_identity_info_new(), signon_identity_info_free);
signon_identity_info_set_caption(identityInfo.get(),
StringPrintf("created by SyncEvolution for account #%d and service %s",
accountID,
serviceName.empty() ? "<<none>>" : serviceName.c_str()).c_str());
const gchar *mechanisms[] = { mechanism ? mechanism : "*", NULL };
signon_identity_info_set_method(identityInfo.get(), method, mechanisms);
#ifdef USE_GSSO
if (realms) {
signon_identity_info_set_realms(identityInfo.get(), realms);
}
#endif
StoreIdentityData data;
signon_identity_store_credentials_with_info(identity, identityInfo.get(),
StoreIdentityCB, &data);
GRunWhile(boost::lambda::var(data.m_running));
if (!data.m_id || data.m_gerror) {
SE_THROW(StringPrintf("failed to create signon identity: %s",
data.m_gerror ? data.m_gerror->message : "???"));
}
// Now store in account.
static const char CREDENTIALS_ID[] = "CredentialsId";
ag_account_set_variant(account, CREDENTIALS_ID, g_variant_new_uint32(data.m_id));
#define ag_account_store_async_finish ag_account_store_finish
gboolean res;
SYNCEVO_GLIB_CALL_SYNC(res, gerror, ag_account_store_async,
account, NULL);
if (!res) {
gerror.throwError(SE_HERE, "failed to store account");
}
authData = AgAuthDataCXX::steal(ag_account_service_get_auth_data(accountService));
signonID = ag_auth_data_get_credentials_id(authData);
if (!signonID) {
SE_THROW("still no signonID?!");
}
method = ag_auth_data_get_method(authData);
mechanism = ag_auth_data_get_mechanism(authData);
}
SignonIdentityCXX identity(signon_identity_new_from_db(signonID), TRANSFER_REF);
SE_LOG_DEBUG(NULL, "using signond identity %d", signonID);
SignonAuthSessionCXX authSession(signon_identity_create_session(identity, method, gerror), TRANSFER_REF);
// TODO (?): retrieve start URL from account system
provider.reset(new SignonAuthProvider(authSession, sessionData, mechanism));
return provider;
}
#endif // USE_SIGNON
SE_END_CXX

View File

@ -38,7 +38,6 @@ MOSTLYCLEANFILES += $(src_backends_signon_libs)
src_backends_signon_common_sources = \
src/backends/signon/signon.h \
src/backends/signon/signon.cpp \
$(NONE)
if ENABLE_MODULES
@ -57,7 +56,9 @@ src_backends_signon_common_cppflags = -DUSE_SIGNON -I$(top_srcdir)/test $(BACKEN
src_backends_signon_common_dependencies = src/syncevo/libsyncevolution.la
if USE_GSSO
src_backends_signon_providergsso_la_SOURCES = $(src_backends_signon_common_sources)
src_backends_signon_providergsso_la_SOURCES = \
src/backends/signon/signon-accounts.cpp \
$(src_backends_signon_common_sources)
src_backends_signon_providergsso_la_LIBADD = $(GSSO_LIBS) $(ACCOUNTS_LIBS) $(src_backends_signon_common_libadd)
src_backends_signon_providergsso_la_LDFLAGS = $(src_backends_signon_common_ldflags)
src_backends_signon_providergsso_la_CXXFLAGS = $(GSSO_CFLAGS) $(ACCOUNTS_CFLAGS) $(src_backends_signon_common_cxxflags)
@ -66,7 +67,9 @@ src_backends_signon_providergsso_la_DEPENDENCIES = $(src_backends_signon_common_
endif
if USE_UOA
src_backends_signon_provideruoa_la_SOURCES = $(src_backends_signon_common_sources)
src_backends_signon_provideruoa_la_SOURCES = \
src/backends/signon/signon-accounts.cpp \
$(src_backends_signon_common_sources)
src_backends_signon_provideruoa_la_LIBADD = $(UOA_LIBS) $(ACCOUNTS_LIBS) $(src_backends_signon_common_libadd)
src_backends_signon_provideruoa_la_LDFLAGS = $(src_backends_signon_common_ldflags)
src_backends_signon_provideruoa_la_CXXFLAGS = $(UOA_CFLAGS) $(ACCOUNTS_CFLAGS) $(src_backends_signon_common_cxxflags)
@ -75,7 +78,9 @@ src_backends_signon_provideruoa_la_DEPENDENCIES = $(src_backends_signon_common_d
endif
if USE_SIGNON
src_backends_signon_providersignon_la_SOURCES = $(src_backends_signon_common_sources)
src_backends_signon_providersignon_la_SOURCES = \
src/backends/signon/signon.cpp \
$(src_backends_signon_common_sources)
src_backends_signon_providersignon_la_LIBADD = $($(DEFAULT_SIGNON)_LIBS) $(ACCOUNTS_LIBS) $(src_backends_signon_common_libadd)
src_backends_signon_providersignon_la_LDFLAGS = $(src_backends_signon_common_ldflags)
src_backends_signon_providersignon_la_CXXFLAGS = $($(DEFAULT_SIGNON)_CFLAGS) $(ACCOUNTS_CFLAGS) $(src_backends_signon_common_cxxflags)

View File

@ -29,13 +29,6 @@
#include "libsignon-glib/signon-auth-service.h"
#include "libsignon-glib/signon-identity.h"
#endif // USE_GSSO
#ifdef USE_ACCOUNTS
#include "libaccounts-glib/ag-account.h"
#include "libaccounts-glib/ag-account-service.h"
#include <libaccounts-glib/ag-auth-data.h>
#include <libaccounts-glib/ag-service.h>
#include <libaccounts-glib/ag-manager.h>
#endif
#include <syncevo/GLibSupport.h>
#include <syncevo/GVariantSupport.h>
@ -47,14 +40,6 @@ SE_GOBJECT_TYPE(SignonAuthService)
SE_GOBJECT_TYPE(SignonAuthSession)
SE_GOBJECT_TYPE(SignonIdentity)
#ifdef USE_ACCOUNTS
SE_GOBJECT_TYPE(AgAccount)
SE_GOBJECT_TYPE(AgAccountService)
SE_GOBJECT_TYPE(AgManager)
SE_GLIB_TYPE(AgService, ag_service)
SE_GLIB_TYPE(AgAuthData, ag_auth_data)
#endif
#endif // USE_SIGNON
#include <syncevo/declarations.h>
@ -62,10 +47,6 @@ SE_BEGIN_CXX
#ifdef USE_SIGNON
#ifdef USE_ACCOUNTS
typedef GListCXX<AgService, GList, ag_service_unref> ServiceListCXX;
#endif
class SignonAuthProvider : public AuthProvider
{
SignonAuthSessionCXX m_authSession;
@ -131,156 +112,6 @@ public:
virtual std::string getUsername() const { return ""; }
};
#ifdef USE_ACCOUNTS
class StoreIdentityData
{
public:
StoreIdentityData() : m_running(true) {}
bool m_running;
guint32 m_id;
GErrorCXX m_gerror;
};
static void StoreIdentityCB(SignonIdentity *self,
guint32 id,
const GError *error,
gpointer userData)
{
StoreIdentityData *data = reinterpret_cast<StoreIdentityData *>(userData);
data->m_running = false;
data->m_id = id;
data->m_gerror = error;
}
boost::shared_ptr<AuthProvider> createSignonAuthProvider(const InitStateString &username,
const InitStateString &password)
{
boost::shared_ptr<AuthProvider> provider;
// Split username into <account ID> and <service name>.
// Be flexible and allow leading/trailing white space.
// Comma is optional.
static const pcrecpp::RE re("^\\s*(\\d+)\\s*,?\\s*(.*)\\s*$");
AgAccountId accountID;
std::string serviceName;
if (!re.FullMatch(username, &accountID, &serviceName)) {
SE_THROW(StringPrintf("username must have the format gsso:<account ID>,<service name>: %s",
username.c_str()));
}
SE_LOG_DEBUG(NULL, "looking up account ID %d and service '%s'",
accountID,
serviceName.c_str());
AgManagerCXX manager(ag_manager_new(), TRANSFER_REF);
GErrorCXX gerror;
AgAccountCXX account(ag_manager_load_account(manager, accountID, gerror), TRANSFER_REF);
if (!account) {
gerror.throwError(SE_HERE, StringPrintf("loading account with ID %d from %s failed",
accountID,
username.c_str()));
}
if (!ag_account_get_enabled(account)) {
SE_THROW(StringPrintf("account with ID %d from %s is disabled, refusing to use it",
accountID,
username.c_str()));
}
AgAccountServiceCXX accountService;
if (serviceName.empty()) {
accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, NULL));
} else {
ServiceListCXX services(ag_account_list_enabled_services(account));
BOOST_FOREACH (AgService *service, services) {
const char *name = ag_service_get_name(service);
SE_LOG_DEBUG(NULL, "enabled service: %s", name);
if (serviceName == name) {
accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, service));
// Do *not* select the service for reading/writing properties.
// AgAccountService does this internally, and when we create
// a new identity below, we want it to be shared by all
// services so that the user only needs to log in once.
// ag_account_select_service(account, service);
break;
}
}
}
if (!accountService) {
SE_THROW(StringPrintf("service '%s' in account with ID %d not found or not enabled",
serviceName.c_str(),
accountID));
}
AgAuthDataCXX authData(ag_account_service_get_auth_data(accountService), TRANSFER_REF);
// SignonAuthServiceCXX authService(signon_auth_service_new(), TRANSFER_REF);
guint signonID = ag_auth_data_get_credentials_id(authData);
const char *method = ag_auth_data_get_method(authData);
const char *mechanism = ag_auth_data_get_mechanism(authData);
GVariantCXX sessionDataVar(ag_auth_data_get_login_parameters(authData, NULL), TRANSFER_REF);
GHashTableCXX sessionData(Variant2HashTable(sessionDataVar));
#ifdef USE_GSSO
GVariant *realmsVariant = (GVariant *)g_hash_table_lookup(sessionData, "Realms");
PlainGStrArray realms(g_variant_dup_strv(realmsVariant, NULL));
#endif
// Check that the service has a credentials ID. If not, create it and
// store its ID permanently.
if (!signonID) {
SE_LOG_DEBUG(NULL, "have to create signon identity");
SignonIdentityCXX identity(signon_identity_new(), TRANSFER_REF);
boost::shared_ptr<SignonIdentityInfo> identityInfo(signon_identity_info_new(), signon_identity_info_free);
signon_identity_info_set_caption(identityInfo.get(),
StringPrintf("created by SyncEvolution for account #%d and service %s",
accountID,
serviceName.empty() ? "<<none>>" : serviceName.c_str()).c_str());
const gchar *mechanisms[] = { mechanism ? mechanism : "*", NULL };
signon_identity_info_set_method(identityInfo.get(), method, mechanisms);
#ifdef USE_GSSO
if (realms) {
signon_identity_info_set_realms(identityInfo.get(), realms);
}
#endif
StoreIdentityData data;
signon_identity_store_credentials_with_info(identity, identityInfo.get(),
StoreIdentityCB, &data);
GRunWhile(boost::lambda::var(data.m_running));
if (!data.m_id || data.m_gerror) {
SE_THROW(StringPrintf("failed to create signon identity: %s",
data.m_gerror ? data.m_gerror->message : "???"));
}
// Now store in account.
static const char CREDENTIALS_ID[] = "CredentialsId";
ag_account_set_variant(account, CREDENTIALS_ID, g_variant_new_uint32(data.m_id));
#define ag_account_store_async_finish ag_account_store_finish
gboolean res;
SYNCEVO_GLIB_CALL_SYNC(res, gerror, ag_account_store_async,
account, NULL);
if (!res) {
gerror.throwError(SE_HERE, "failed to store account");
}
authData = AgAuthDataCXX::steal(ag_account_service_get_auth_data(accountService));
signonID = ag_auth_data_get_credentials_id(authData);
if (!signonID) {
SE_THROW("still no signonID?!");
}
method = ag_auth_data_get_method(authData);
mechanism = ag_auth_data_get_mechanism(authData);
}
SignonIdentityCXX identity(signon_identity_new_from_db(signonID), TRANSFER_REF);
SE_LOG_DEBUG(NULL, "using signond identity %d", signonID);
SignonAuthSessionCXX authSession(signon_identity_create_session(identity, method, gerror), TRANSFER_REF);
// TODO (?): retrieve start URL from account system
provider.reset(new SignonAuthProvider(authSession, sessionData, mechanism));
return provider;
}
#else // USE_ACCOUNTS
boost::shared_ptr<AuthProvider> createSignonAuthProvider(const InitStateString &username,
const InitStateString &password)
{
@ -340,9 +171,6 @@ boost::shared_ptr<AuthProvider> createSignonAuthProvider(const InitStateString &
return provider;
}
#endif // USE_ACCOUNTS
#endif // USE_SIGNON
SE_END_CXX