GOA: get OAuth2 tokens out of GNOME Online Accounts
"username = goa:..." selects an account in GOA and retrieves the OAuth2 token from that. The implementation uses the GOA D-Bus API directly, because our C++ D-Bus bindings are easier to use and this avoids an additional library dependency.
This commit is contained in:
parent
582025171d
commit
8f3f6130ab
51
src/backends/goa/GOARegister.cpp
Normal file
51
src/backends/goa/GOARegister.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 "goa.h"
|
||||
|
||||
#include <syncevo/IdentityProvider.h>
|
||||
|
||||
#include <syncevo/declarations.h>
|
||||
SE_BEGIN_CXX
|
||||
|
||||
static class GOAProvider : public IdentityProvider
|
||||
{
|
||||
public:
|
||||
GOAProvider() :
|
||||
IdentityProvider("goa",
|
||||
"goa:<GOA account presentation ID = email address>\n"
|
||||
" Authentication using GNOME Online Accounts,\n"
|
||||
" using an account created and managed with GNOME Control Center.")
|
||||
{}
|
||||
|
||||
virtual boost::shared_ptr<AuthProvider> create(const InitStateString &username,
|
||||
const InitStateString &password)
|
||||
{
|
||||
// Returning NULL if not enabled...
|
||||
boost::shared_ptr<AuthProvider> provider;
|
||||
#ifdef USE_GOA
|
||||
provider = createGOAAuthProvider(username, password);
|
||||
#endif
|
||||
return provider;
|
||||
}
|
||||
} gsso;
|
||||
|
||||
SE_END_CXX
|
112
src/backends/goa/README
Normal file
112
src/backends/goa/README
Normal file
|
@ -0,0 +1,112 @@
|
|||
Google CalDAV/CardDAV via OAuth2 with GNOME Online Accounts (GOA)
|
||||
=================================================================
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
SyncEvolution depends on a GNOME Online Accounts with CalDAV *and*
|
||||
CardDAV enabled for Google. This is hard-coded in the source code, so
|
||||
recompiling is the only (sane) way to change that. CalDAV has been
|
||||
enabled for a while, CardDAV is recent (>= 3.10).
|
||||
|
||||
It is possible to patch 3.8 without recompiling (see below). Versions
|
||||
older than 3.8 do not work because they lack OAuth2 support.
|
||||
|
||||
SyncEvolution needs an active account for Google in the GNOME Control
|
||||
Center, under "online accounts". Enable the different data categories
|
||||
if and only if you want to access the data with the core GNOME
|
||||
apps. SyncEvolution ignores these settings.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
OAuth2 authentication with GNOME Online Accounts is enabled by setting
|
||||
username or databaseUser to a string of the format
|
||||
goa:<GOA account presentation ID = email address | account ID>
|
||||
|
||||
Typically there is only one account using a Google email address, so
|
||||
that can be used to select the account. SyncEvolution checks if it is
|
||||
really unique and if not, provides a list of all accounts with their
|
||||
account ID. Then the unique account ID should be used instead.
|
||||
|
||||
The base URL for each service currently needs to be given via syncURL:
|
||||
|
||||
syncevolution --print-databases \
|
||||
backend=carddav \
|
||||
username=goa:john.doe@gmail.com \
|
||||
syncURL=https://www.googleapis.com/.well-known/carddav
|
||||
|
||||
src/syncevolution --print-databases \
|
||||
backend=caldav \
|
||||
username=goa:john.doe@gmail.com \
|
||||
syncURL=https://apidata.googleusercontent.com/caldav/v2
|
||||
|
||||
Once that works, follow the "CalDAV and CardDAV" instructions from the
|
||||
README with the different username and syncURL.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
Add --daemon=no to the command line to prevent shifting the actual
|
||||
command executing into syncevo-dbus-server and (from there)
|
||||
syncevo-dbus-helper.
|
||||
|
||||
Set SYNCEVOLUTION_DEBUG=1 to see all debug messages and increase the
|
||||
loglevel to see HTTP requests:
|
||||
|
||||
SYNCEVOLUTION_DEBUG=1 syncevolution --daemon=no \
|
||||
loglevel=4 \
|
||||
--print-databases \
|
||||
...
|
||||
|
||||
Known Problems
|
||||
--------------
|
||||
|
||||
When accessing CardDAV:
|
||||
|
||||
status-line] < HTTP/1.1 401 Unauthorized
|
||||
[hdr] WWW-Authenticate: AuthSub realm="https://www.google.com/accounts/AuthSubRequest" allowed-scopes="https://www.googleapis.com/auth/carddav"
|
||||
...
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<errors xmlns="http://schemas.google.com/g/2005">
|
||||
<error>
|
||||
<domain>GData</domain>
|
||||
<code>authError</code>
|
||||
<location type="header">Authorization</location>
|
||||
<internalReason>Invalid Credentials</internalReason>
|
||||
</error>
|
||||
</errors>
|
||||
...
|
||||
[INFO] operation temporarily (?) failed, going to retry in 5.0s before giving up in 295.8s: PROPFIND: Neon error code 3 = NE_AUTH, HTTP status 401: Could not authenticate to server: ignored AuthSub challenge
|
||||
...
|
||||
|
||||
This happens when using a GNOME Online Accounts which does (or did)
|
||||
not request CardDAV access when logging into Google. Install GNOME
|
||||
Online Accounts >= 3.10 or patch it (see below), "killall goa-daemon",
|
||||
then re-create the account in the GNOME Control Center.
|
||||
|
||||
Patching GOA 3.8
|
||||
----------------
|
||||
|
||||
It is possible to add CardDAV support to 3.8 without recompiling GNOME
|
||||
Online Accounts. However, the downside is that this approach has to
|
||||
disable access to some other kind of data and breaks when updating or
|
||||
reinstalling GOA.
|
||||
|
||||
1. Locate libgoa-backend-1.0.so.0.0.0: typically it is in /usr/lib or /usr/lib64.
|
||||
2. Open it in a text editor which can handle binary data (like emacs).
|
||||
3. Switch to "overwrite mode".
|
||||
4. Find the string starting with https://www.googleapis.com/auth/userinfo.email
|
||||
6. Overwrite the part which you don't need with https://www.googleapis.com/auth/carddav
|
||||
and spaces.
|
||||
|
||||
For example, if Google Docs access is not needed, replace
|
||||
"https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/ "
|
||||
with
|
||||
"https://www.googleapis.com/auth/carddav "
|
||||
|
||||
Here's a perl command which replaces Google Docs with CardDAV:
|
||||
|
||||
perl -pi -e 's;https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/ ;https://www.googleapis.com/auth/carddav ;' /usr/lib*/libgoa-backend-1.0.so.0.0.0
|
15
src/backends/goa/configure-sub.in
Normal file
15
src/backends/goa/configure-sub.in
Normal file
|
@ -0,0 +1,15 @@
|
|||
AC_ARG_ENABLE(goa,
|
||||
AS_HELP_STRING([--disable-goa],
|
||||
[enables or disables support for the GNOME Online Account single-sign-on system; default is on]),
|
||||
[enable_goa="$enableval"
|
||||
test "$enable_goa" = "yes" || test "$enable_goa" = "no" || AC_MSG_ERROR([invalid value for --enable-goa: $enable_goa])
|
||||
],
|
||||
enable_goa="yes")
|
||||
if test $enable_goa = "yes"; then
|
||||
AC_DEFINE(USE_GOA, 1, [use GNOME Online Accounts])
|
||||
# link into static executables, similar to a SyncSource
|
||||
SYNCSOURCES="$SYNCSOURCES src/backends/goa/providergoa.la"
|
||||
fi
|
||||
|
||||
# conditional compilation in make
|
||||
AM_CONDITIONAL([USE_GOA], [test "$use_goa" = "yes"])
|
26
src/backends/goa/goa.am
Normal file
26
src/backends/goa/goa.am
Normal file
|
@ -0,0 +1,26 @@
|
|||
dist_noinst_DATA += src/backends/goa/configure-sub.in \
|
||||
src/backends/goa/README \
|
||||
$(NONE)
|
||||
|
||||
src_backends_goa_lib = src/backends/goa/providergoa.la
|
||||
MOSTLYCLEANFILES += $(src_backends_goa_lib)
|
||||
|
||||
src_backends_goa_providergoa_la_SOURCES = \
|
||||
src/backends/goa/goa.h \
|
||||
src/backends/goa/goa.cpp \
|
||||
$(NONE)
|
||||
|
||||
if ENABLE_MODULES
|
||||
src_backends_goa_backenddir = $(BACKENDS_DIRECTORY)
|
||||
src_backends_goa_backend_LTLIBRARIES = $(src_backends_goa_lib)
|
||||
src_backends_goa_providergoa_la_SOURCES += \
|
||||
src/backends/goa/GOARegister.cpp
|
||||
else
|
||||
noinst_LTLIBRARIES += $(src_backends_goa_lib)
|
||||
endif
|
||||
|
||||
src_backends_goa_providergoa_la_LIBADD = $(SYNCEVOLUTION_LIBS) $(gdbus_build_dir)/libgdbussyncevo.la $(DBUS_LIBS)
|
||||
src_backends_goa_providergoa_la_LDFLAGS = -module -avoid-version
|
||||
src_backends_goa_providergoa_la_CXXFLAGS = $(SYNCEVOLUTION_CFLAGS) $(SYNCEVO_WFLAGS) $(DBUS_CFLAGS)
|
||||
src_backends_goa_providergoa_la_CPPFLAGS = -I$(gdbus_dir) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS)
|
||||
src_backends_goa_providergoa_la_DEPENDENCIES = src/syncevo/libsyncevolution.la
|
235
src/backends/goa/goa.cpp
Normal file
235
src/backends/goa/goa.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
#ifdef USE_GOA
|
||||
|
||||
#include "goa.h"
|
||||
#include <syncevo/IdentityProvider.h>
|
||||
#include <gdbus-cxx-bridge.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <syncevo/declarations.h>
|
||||
SE_BEGIN_CXX
|
||||
|
||||
/*
|
||||
* We call the GOA D-Bus API directly. This is easier than using
|
||||
* libgoa because our own D-Bus wrapper gives us data in C++ data
|
||||
* structures. It also avoids another library dependency.
|
||||
*/
|
||||
|
||||
static const char GOA_BUS_NAME[] = "org.gnome.OnlineAccounts";
|
||||
static const char GOA_PATH[] = "/org/gnome/OnlineAccounts";
|
||||
|
||||
static const char OBJECT_MANAGER_INTERFACE[] = "org.freedesktop.DBus.ObjectManager";
|
||||
static const char OBJECT_MANAGER_GET_MANAGED_OBJECTS[] = "GetManagedObjects";
|
||||
|
||||
static const char GOA_ACCOUNT_INTERFACE[] = "org.gnome.OnlineAccounts.Account";
|
||||
static const char GOA_ACCOUNT_ENSURE_CREDENTIALS[] = "EnsureCredentials";
|
||||
static const char GOA_ACCOUNT_PRESENTATION_IDENTITY[] = "PresentationIdentity";
|
||||
static const char GOA_ACCOUNT_ID[] = "Id";
|
||||
static const char GOA_ACCOUNT_PROVIDER_NAME[] = "ProviderName";
|
||||
|
||||
static const char GOA_OAUTH2_INTERFACE[] = "org.gnome.OnlineAccounts.OAuth2Based";
|
||||
static const char GOA_OAUTH2_GET_ACCESS_TOKEN[] = "GetAccessToken";
|
||||
|
||||
class GOAAccount;
|
||||
|
||||
class GOAManager : private GDBusCXX::DBusRemoteObject
|
||||
{
|
||||
typedef std::map<std::string, // property name
|
||||
boost::variant<std::string> // property value - we only care about strings
|
||||
> Properties;
|
||||
typedef std::map<std::string, // interface name
|
||||
Properties
|
||||
> Interfaces;
|
||||
typedef std::map<GDBusCXX::DBusObject_t, Interfaces> ManagedObjects;
|
||||
GDBusCXX::DBusClientCall1<ManagedObjects> m_getManagedObjects;
|
||||
|
||||
public:
|
||||
GOAManager(const GDBusCXX::DBusConnectionPtr &conn);
|
||||
|
||||
/**
|
||||
* Find a particular account, identified by its representation ID
|
||||
* (the unique user visible string). The account must support OAuth2,
|
||||
* otherwise an error is thrown.
|
||||
*/
|
||||
boost::shared_ptr<GOAAccount> lookupAccount(const std::string &representationID);
|
||||
};
|
||||
|
||||
class GOAAccount
|
||||
{
|
||||
GDBusCXX::DBusRemoteObject m_account;
|
||||
GDBusCXX::DBusRemoteObject m_oauth2;
|
||||
|
||||
|
||||
public:
|
||||
GOAAccount(const GDBusCXX::DBusConnectionPtr &conn,
|
||||
const std::string &path);
|
||||
|
||||
GDBusCXX::DBusClientCall1<int32_t> m_ensureCredentials;
|
||||
GDBusCXX::DBusClientCall1<std::string> m_getAccessToken;
|
||||
};
|
||||
|
||||
GOAManager::GOAManager(const GDBusCXX::DBusConnectionPtr &conn) :
|
||||
GDBusCXX::DBusRemoteObject(conn, GOA_PATH, OBJECT_MANAGER_INTERFACE, GOA_BUS_NAME),
|
||||
m_getManagedObjects(*this, OBJECT_MANAGER_GET_MANAGED_OBJECTS)
|
||||
{
|
||||
}
|
||||
|
||||
boost::shared_ptr<GOAAccount> GOAManager::lookupAccount(const std::string &username)
|
||||
{
|
||||
SE_LOG_DEBUG(NULL, "Looking up all accounts in GNOME Online Accounts, searching for '%s'.", username.c_str());
|
||||
ManagedObjects objects = m_getManagedObjects();
|
||||
|
||||
GDBusCXX::DBusObject_t accountPath;
|
||||
bool unique = true;
|
||||
bool hasOAuth2 = false;
|
||||
std::vector<std::string> accounts;
|
||||
BOOST_FOREACH (const ManagedObjects::value_type &object, objects) {
|
||||
const GDBusCXX::DBusObject_t &path = object.first;
|
||||
const Interfaces &interfaces = object.second;
|
||||
// boost::adaptors::keys() would be nicer, but is not available on Ubuntu Lucid.
|
||||
std::list<std::string> interfaceKeys;
|
||||
BOOST_FOREACH (const Interfaces::value_type &entry, interfaces) {
|
||||
interfaceKeys.push_back(entry.first);
|
||||
}
|
||||
SE_LOG_DEBUG(NULL, "GOA object %s implements %s", path.c_str(),
|
||||
boost::join(interfaceKeys, ", ").c_str());
|
||||
Interfaces::const_iterator it = interfaces.find(GOA_ACCOUNT_INTERFACE);
|
||||
if (it != interfaces.end()) {
|
||||
const Properties &properties = it->second;
|
||||
Properties::const_iterator id = properties.find(GOA_ACCOUNT_ID);
|
||||
Properties::const_iterator presentationID = properties.find(GOA_ACCOUNT_PRESENTATION_IDENTITY);
|
||||
if (id != properties.end() &&
|
||||
presentationID != properties.end()) {
|
||||
const std::string &idStr = boost::get<std::string>(id->second);
|
||||
const std::string &presentationIDStr = boost::get<std::string>(presentationID->second);
|
||||
Properties::const_iterator provider = properties.find(GOA_ACCOUNT_PROVIDER_NAME);
|
||||
std::string description = StringPrintf("%s, %s = %s",
|
||||
provider == properties.end() ? "???" : boost::get<std::string>(provider->second).c_str(),
|
||||
presentationIDStr.c_str(),
|
||||
idStr.c_str());
|
||||
SE_LOG_DEBUG(NULL, "GOA account %s", description.c_str());
|
||||
accounts.push_back(description);
|
||||
// The assumption here is that ID and presentation
|
||||
// identifier are so different that there can be
|
||||
// no overlap. Otherwise we would have to know
|
||||
// whether the user gave us an ID or presentation
|
||||
// identifier.
|
||||
if (idStr == username ||
|
||||
presentationIDStr == username) {
|
||||
if (accountPath.empty()) {
|
||||
accountPath = path;
|
||||
hasOAuth2 = interfaces.find(GOA_OAUTH2_INTERFACE) != interfaces.end();
|
||||
SE_LOG_DEBUG(NULL, "found matching GNOME Online Account for '%s': %s", username.c_str(), description.c_str());
|
||||
} else {
|
||||
unique = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SE_LOG_DEBUG(NULL, "ignoring %s, lacks expected properties",
|
||||
path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(accounts.begin(), accounts.end());
|
||||
if (accountPath.empty()) {
|
||||
if (accounts.empty()) {
|
||||
SE_THROW(StringPrintf("GNOME Online Account '%s' not found. You must set up the account in GNOME Control Center/Online Accounts first.", username.c_str()));
|
||||
} else {
|
||||
SE_THROW(StringPrintf("GNOME Online Account '%s' not found. Choose one of the following:\n%s",
|
||||
username.c_str(),
|
||||
boost::join(accounts, "\n").c_str()));
|
||||
}
|
||||
} else if (!unique) {
|
||||
SE_THROW(StringPrintf("GNOME Online Account '%s' is not unique. Choose one of the following, using the unique ID instead of the more ambiguous representation name:\n%s",
|
||||
username.c_str(),
|
||||
boost::join(accounts, "\n").c_str()));
|
||||
} else if (!hasOAuth2) {
|
||||
SE_THROW(StringPrintf("Found GNOME Online Account '%s', but it does not support OAuth2. Are you sure that you picked the right account and that you are using GNOME Online Accounts >= 3.8?",
|
||||
username.c_str()));
|
||||
}
|
||||
|
||||
boost::shared_ptr<GOAAccount> account(new GOAAccount(getConnection(), accountPath));
|
||||
return account;
|
||||
}
|
||||
|
||||
GOAAccount::GOAAccount(const GDBusCXX::DBusConnectionPtr &conn,
|
||||
const std::string &path) :
|
||||
m_account(conn, path, GOA_ACCOUNT_INTERFACE, GOA_BUS_NAME),
|
||||
m_oauth2(conn, path, GOA_OAUTH2_INTERFACE, GOA_BUS_NAME),
|
||||
m_ensureCredentials(m_account, GOA_ACCOUNT_ENSURE_CREDENTIALS),
|
||||
m_getAccessToken(m_oauth2, GOA_OAUTH2_GET_ACCESS_TOKEN)
|
||||
{
|
||||
}
|
||||
|
||||
class GOAAuthProvider : public AuthProvider
|
||||
{
|
||||
boost::shared_ptr<GOAAccount> m_account;
|
||||
|
||||
public:
|
||||
GOAAuthProvider(const boost::shared_ptr<GOAAccount> &account) :
|
||||
m_account(account)
|
||||
{}
|
||||
|
||||
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
|
||||
{
|
||||
m_account->m_ensureCredentials();
|
||||
std::string token = m_account->m_getAccessToken();
|
||||
return token;
|
||||
}
|
||||
|
||||
virtual std::string getUsername() const { return ""; }
|
||||
};
|
||||
|
||||
boost::shared_ptr<AuthProvider> createGOAAuthProvider(const InitStateString &username,
|
||||
const InitStateString &password)
|
||||
{
|
||||
// Because we share the connection, hopefully this won't be too expensive.
|
||||
GDBusCXX::DBusErrorCXX err;
|
||||
GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection("SESSION",
|
||||
"",
|
||||
false,
|
||||
&err);
|
||||
if (!conn) {
|
||||
err.throwFailure("connecting to session bus");
|
||||
}
|
||||
|
||||
GOAManager manager(conn);
|
||||
boost::shared_ptr<GOAAccount> account = manager.lookupAccount(username);
|
||||
boost::shared_ptr<AuthProvider> provider(new GOAAuthProvider(account));
|
||||
return provider;
|
||||
}
|
||||
|
||||
SE_END_CXX
|
||||
|
||||
#endif // USE_GOA
|
||||
|
||||
|
34
src/backends/goa/goa.h
Normal file
34
src/backends/goa/goa.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
#ifndef INCL_SYNC_EVOLUTION_GOA_AUTH_PROVIDER
|
||||
# define INCL_SYNC_EVOLUTION_GOA_AUTH_PROVIDER
|
||||
|
||||
#include <syncevo/util.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <syncevo/declarations.h>
|
||||
SE_BEGIN_CXX
|
||||
|
||||
class AuthProvider;
|
||||
boost::shared_ptr<AuthProvider> createGOAAuthProvider(const InitStateString &username,
|
||||
const InitStateString &password);
|
||||
|
||||
SE_END_CXX
|
||||
#endif
|
Loading…
Reference in a new issue