215 lines
8.1 KiB
C++
215 lines
8.1 KiB
C++
/*
|
|
* Copyright (C) 2014 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>
|
|
#include <syncevo/GLibSupport.h>
|
|
#include <syncevo/GVariantSupport.h>
|
|
#include <syncevo/SoupTransportAgent.h>
|
|
#include <syncevo/CurlTransportAgent.h>
|
|
#include <json.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
class RefreshTokenAuthProvider : public AuthProvider
|
|
{
|
|
boost::shared_ptr<HTTPTransportAgent> m_agent;
|
|
std::string m_tokenHost;
|
|
std::string m_tokenPath;
|
|
std::string m_scope;
|
|
std::string m_clientID;
|
|
std::string m_clientSecret;
|
|
std::string m_refreshToken;
|
|
std::string m_accessToken;
|
|
|
|
public:
|
|
RefreshTokenAuthProvider(const char* tokenHost,
|
|
const char* tokenPath,
|
|
const char* scope,
|
|
const char* clientID,
|
|
const char* clientSecret,
|
|
const char* refreshToken) :
|
|
m_tokenHost(tokenHost),
|
|
m_tokenPath(tokenPath),
|
|
m_scope(scope),
|
|
m_clientID(clientID),
|
|
m_clientSecret(clientSecret),
|
|
m_refreshToken(refreshToken)
|
|
{
|
|
#ifdef ENABLE_LIBSOUP
|
|
m_agent = SoupTransportAgent::create(static_cast<GMainLoop *>(NULL));
|
|
#elif defined(ENABLE_LIBCURL)
|
|
boost::shared_ptr<CurlTransportAgent> agent(new CurlTransportAgent());
|
|
m_agent = agent;
|
|
#endif
|
|
}
|
|
|
|
virtual bool methodIsSupported(AuthMethod method) const { return method == AUTH_METHOD_OAUTH2; }
|
|
|
|
virtual Credentials getCredentials() { SE_THROW("only OAuth2 is supported"); }
|
|
|
|
virtual std::string getOAuth2Bearer(const PasswordUpdateCallback &passwordUpdateCallback)
|
|
{
|
|
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token");
|
|
|
|
if (m_accessToken.empty()) {
|
|
const char *reply;
|
|
size_t replyLen;
|
|
std::string contentType;
|
|
|
|
m_agent->setURL(m_tokenHost + m_tokenPath);
|
|
m_agent->setContentType("application/x-www-form-urlencoded");
|
|
|
|
std::ostringstream oss;
|
|
oss<<"grant_type=refresh_token&client_id="<<m_clientID;
|
|
oss<<"&client_secret="<<m_clientSecret<<"&scope="<<m_scope;
|
|
oss<<"&refresh_token="<<m_refreshToken;
|
|
|
|
std::string requestBody = oss.str();
|
|
m_agent->send(requestBody.c_str(), requestBody.length());
|
|
|
|
switch (m_agent->wait()) {
|
|
case TransportAgent::ACTIVE:
|
|
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token - agent active");
|
|
break;
|
|
case TransportAgent::GOT_REPLY:
|
|
{
|
|
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token - agent got reply");
|
|
m_agent->getReply(reply, replyLen, contentType);
|
|
|
|
json_object *jobj = json_tokener_parse(reply);
|
|
if (jobj) {
|
|
json_object_object_foreach(jobj, key, val) {
|
|
if (strcmp("access_token", key) == 0) {
|
|
m_accessToken = json_object_get_string(val);
|
|
}
|
|
if (strcmp("refresh_token", key) == 0) {
|
|
std::string newRefreshToken = json_object_get_string(val);
|
|
if (passwordUpdateCallback) {
|
|
try {
|
|
passwordUpdateCallback(newRefreshToken);
|
|
} catch (...) {
|
|
std::string explanation;
|
|
Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
|
|
SE_LOG_INFO(NULL, "The attempt to update the refresh token in the 'password' property failed, probably because there is no configuration for it: %s\nRemember to use the new token in the future: %s", explanation.c_str(), newRefreshToken.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
json_object_put(jobj);
|
|
}
|
|
else {
|
|
SE_THROW("OAuth2 misformatted response");
|
|
}
|
|
}
|
|
break;
|
|
case TransportAgent::TIME_OUT:
|
|
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token - agent time out");
|
|
SE_THROW("OAuth2 request timed out");
|
|
break;
|
|
case TransportAgent::INACTIVE:
|
|
case TransportAgent::CLOSED:
|
|
case TransportAgent::FAILED: {
|
|
std::string errorString;
|
|
m_agent->getReply(reply, replyLen, contentType);
|
|
|
|
json_object *jobj = json_tokener_parse(reply);
|
|
if (jobj) {
|
|
json_object_object_foreach(jobj, key, val) {
|
|
if (strcmp ("error", key) == 0) {
|
|
errorString = json_object_get_string(val);
|
|
}
|
|
}
|
|
json_object_put(jobj);
|
|
}
|
|
|
|
SE_THROW("OAuth2 request failed with error: " + errorString);
|
|
break;
|
|
}
|
|
case TransportAgent::CANCELED:
|
|
SE_LOG_DEBUG(NULL, "retrieving OAuth2 token - agent cancelled");
|
|
SE_THROW("OAuth2 request cancelled");
|
|
break;
|
|
}
|
|
}
|
|
return m_accessToken;
|
|
}
|
|
|
|
virtual void invalidateCachedSecrets() { m_accessToken.clear(); }
|
|
|
|
virtual std::string getUsername() const { return ""; }
|
|
};
|
|
|
|
boost::shared_ptr<AuthProvider> createOAuth2AuthProvider(const InitStateString &username,
|
|
const InitStateString &password)
|
|
{
|
|
// Expected content of parameter GVariant.
|
|
boost::shared_ptr<GVariantType> hashtype(g_variant_type_new("a{ss}"), g_variant_type_free);
|
|
|
|
// 'username' is the part after oauth2: which we can parse directly.
|
|
GErrorCXX gerror;
|
|
GVariantStealCXX parametersVar(g_variant_parse(hashtype.get(), username.c_str(), NULL, NULL, gerror));
|
|
if (!parametersVar) {
|
|
gerror.throwError(SE_HERE, "parsing 'oauth2:' username");
|
|
}
|
|
GHashTableCXX parameters(Variant2StrHashTable(parametersVar));
|
|
|
|
// Extract the values that we expect in the parameters hash.
|
|
const char *tokenHost;
|
|
const char *tokenPath;
|
|
const char *scope;
|
|
const char *clientID;
|
|
const char *clientSecret;
|
|
|
|
tokenHost = (const gchar *)g_hash_table_lookup(parameters, "TokenHost");
|
|
if (!tokenHost) {
|
|
SE_THROW("need 'TokenHost: <string>' in 'oauth2:' parameters");
|
|
}
|
|
|
|
tokenPath = (const gchar *)g_hash_table_lookup(parameters, "TokenPath");
|
|
if (!tokenPath) {
|
|
SE_THROW("need 'TokenPath: <string>' in 'oauth2:' parameters");
|
|
}
|
|
|
|
scope = (const gchar *)g_hash_table_lookup(parameters, "Scope");
|
|
if (!scope) {
|
|
SE_THROW("need 'Scope: <string>' in 'oauth2:' parameters");
|
|
}
|
|
|
|
clientID = (const gchar *)g_hash_table_lookup(parameters, "ClientID");
|
|
if (!clientID) {
|
|
SE_THROW("need 'ClientID: <string>' in 'oauth2:' parameters");
|
|
}
|
|
|
|
clientSecret = (const gchar *)g_hash_table_lookup(parameters, "ClientSecret");
|
|
if (!clientSecret) {
|
|
SE_THROW("need 'ClientSecret: <string>' in 'oauth2:' parameters");
|
|
}
|
|
|
|
if (password.empty()) {
|
|
SE_THROW("need refresh token provided as password");
|
|
}
|
|
boost::shared_ptr<AuthProvider> provider(new RefreshTokenAuthProvider(tokenHost, tokenPath, scope, clientID, clientSecret, password.c_str()));
|
|
return provider;
|
|
}
|
|
|
|
SE_END_CXX
|