|
Packit |
79f644 |
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
Packit |
79f644 |
/*
|
|
Packit |
79f644 |
* Copyright © 2011 – 2017 Red Hat, Inc.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This library is free software; you can redistribute it and/or
|
|
Packit |
79f644 |
* modify it under the terms of the GNU Lesser General Public
|
|
Packit |
79f644 |
* License as published by the Free Software Foundation; either
|
|
Packit |
79f644 |
* version 2 of the License, or (at your option) any later version.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This library is distributed in the hope that it will be useful,
|
|
Packit |
79f644 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
79f644 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
79f644 |
* Lesser General Public License for more details.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* You should have received a copy of the GNU Lesser General
|
|
Packit |
79f644 |
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
#include "config.h"
|
|
Packit |
79f644 |
#include <glib/gi18n-lib.h>
|
|
Packit |
79f644 |
#include <stdlib.h>
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
#include <rest/oauth2-proxy.h>
|
|
Packit |
79f644 |
#include <libsoup/soup.h>
|
|
Packit |
79f644 |
#include <json-glib/json-glib.h>
|
|
Packit |
79f644 |
#include <webkit2/webkit2.h>
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
#include "goaprovider.h"
|
|
Packit |
79f644 |
#include "goautils.h"
|
|
Packit |
79f644 |
#include "goawebview.h"
|
|
Packit |
79f644 |
#include "goaoauth2provider.h"
|
|
Packit |
79f644 |
#include "goaoauth2provider-priv.h"
|
|
Packit |
79f644 |
#include "goaoauth2provider-web-extension.h"
|
|
Packit |
79f644 |
#include "goaoauth2provider-web-view.h"
|
|
Packit |
79f644 |
#include "goarestproxy.h"
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* SECTION:goaoauth2provider
|
|
Packit |
79f644 |
* @title: GoaOAuth2Provider
|
|
Packit |
79f644 |
* @short_description: Abstract base class for OAuth 2.0 providers
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* #GoaOAuth2Provider is an abstract base class for
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15">OAuth
|
|
Packit |
79f644 |
* 2.0</ulink> based providers.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Subclasses must implement
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_authorization_uri,
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_token_uri,
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_redirect_uri,
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_scope,
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_client_id,
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_client_secret and
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_identity_sync methods.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Additionally, the
|
|
Packit |
79f644 |
* #GoaProviderClass.get_provider_type,
|
|
Packit |
79f644 |
* #GoaProviderClass.get_provider_name,
|
|
Packit |
79f644 |
* #GoaProviderClass.build_object (this should chain up to its
|
|
Packit |
79f644 |
* parent class) methods must be implemented.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Note that the #GoaProviderClass.add_account,
|
|
Packit |
79f644 |
* #GoaProviderClass.refresh_account and
|
|
Packit |
79f644 |
* #GoaProviderClass.ensure_credentials_sync methods do not
|
|
Packit |
79f644 |
* need to be implemented - this type implements these methods..
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
struct _GoaOAuth2ProviderPrivate
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GtkDialog *dialog;
|
|
Packit |
79f644 |
GError *error;
|
|
Packit |
79f644 |
GMainLoop *loop;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
const gchar *existing_identity;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gchar *account_object_path;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gchar *authorization_code;
|
|
Packit |
79f644 |
gchar *access_token;
|
|
Packit |
79f644 |
gint access_token_expires_in;
|
|
Packit |
79f644 |
gchar *refresh_token;
|
|
Packit |
79f644 |
gchar *identity;
|
|
Packit |
79f644 |
gchar *presentation_identity;
|
|
Packit |
79f644 |
gchar *password;
|
|
Packit |
79f644 |
};
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
G_LOCK_DEFINE_STATIC (provider_lock);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GoaOAuth2Provider, goa_oauth2_provider, GOA_TYPE_PROVIDER);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
is_authorization_error (GError *error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
gboolean ret;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_return_val_if_fail (error != NULL, FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret = FALSE;
|
|
Packit |
79f644 |
if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (SOUP_STATUS_IS_CLIENT_ERROR (error->code))
|
|
Packit |
79f644 |
ret = TRUE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_get_use_mobile_browser_default (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return FALSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_use_mobile_browser:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns whether there is a need for the embedded browser to identify
|
|
Packit |
79f644 |
* itself as running on a mobile phone in order to get a more compact
|
|
Packit |
79f644 |
* version of the approval page.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %FALSE.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if the embedded browser should identify itself as
|
|
Packit |
79f644 |
* running on a mobile platform, %FALSE otherwise.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_get_use_mobile_browser (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_use_mobile_browser (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_is_deny_node_default (GoaOAuth2Provider *self, WebKitDOMNode *node)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return FALSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_is_deny_node:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @node: A WebKitDOMNode.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Checks whether @node is the HTML UI element that the user can use
|
|
Packit |
79f644 |
* to deny permission to access his account. Usually they are either a
|
|
Packit |
79f644 |
* WebKitDOMHTMLButtonElement or a WebKitDOMHTMLInputElement.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Please note that providers may have multiple such elements in their
|
|
Packit |
79f644 |
* UI and this method should catch all of them.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %FALSE.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if the @node can be used to deny permission.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_is_deny_node (GoaOAuth2Provider *self, WebKitDOMNode *node)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_deny_node (self, node);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_is_password_node_default (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return FALSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_is_password_node:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @element: A WebKitDOMHTMLInputElement
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Checks whether @element is the HTML UI element that the user can
|
|
Packit |
79f644 |
* use to enter her password. This can be used to offer a
|
|
Packit |
79f644 |
* #GoaPasswordBased interface by saving the user's
|
|
Packit |
79f644 |
* password. Providers usually frown upon doing this, so this is not
|
|
Packit |
79f644 |
* recommended.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %FALSE.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if @element can be used to enter the password.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_is_password_node (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT (element), FALSE);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_password_node (self, element);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
goa_oauth2_provider_add_account_key_values_default (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
GVariantBuilder *builder)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* do nothing */
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_add_account_key_values:
|
|
Packit |
79f644 |
* @self: A #GoaProvider.
|
|
Packit |
79f644 |
* @builder: A #GVariantBuilder for a <literal>a{ss}</literal> variant.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Hook for implementations to add key/value pairs to the key-file
|
|
Packit |
79f644 |
* when creating an account.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation does nothing.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
void
|
|
Packit |
79f644 |
goa_oauth2_provider_add_account_key_values (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
GVariantBuilder *builder)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_if_fail (GOA_IS_OAUTH2_PROVIDER (self));
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->add_account_key_values (self, builder);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_build_authorization_uri_default (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
const gchar *authorization_uri,
|
|
Packit |
79f644 |
const gchar *escaped_redirect_uri,
|
|
Packit |
79f644 |
const gchar *escaped_client_id,
|
|
Packit |
79f644 |
const gchar *escaped_scope)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return g_strdup_printf ("%s"
|
|
Packit |
79f644 |
"?response_type=code"
|
|
Packit |
79f644 |
"&redirect_uri=%s"
|
|
Packit |
79f644 |
"&client_id=%s"
|
|
Packit |
79f644 |
"&scope=%s",
|
|
Packit |
79f644 |
authorization_uri,
|
|
Packit |
79f644 |
escaped_redirect_uri,
|
|
Packit |
79f644 |
escaped_client_id,
|
|
Packit |
79f644 |
escaped_scope);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_build_authorization_uri:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @authorization_uri: An authorization URI.
|
|
Packit |
79f644 |
* @escaped_redirect_uri: An escaped redirect URI
|
|
Packit |
79f644 |
* @escaped_client_id: An escaped client id
|
|
Packit |
79f644 |
* @escaped_scope: (allow-none): The escaped scope or %NULL
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Builds the URI that can be opened in a web browser (or embedded web
|
|
Packit |
79f644 |
* browser widget) to start authenticating an user.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* The default implementation just returns the expected URI
|
|
Packit |
79f644 |
* (e.g. <literal>http://example.com/dialog/oauth2?response_type=code&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&client_id=foo&scope=email%20stuff</literal>)
|
|
Packit |
79f644 |
* - override (and chain up) if you e.g. need to to pass additional
|
|
Packit |
79f644 |
* parameters.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* The @authorization_uri, @escaped_redirect_uri, @escaped_client_id
|
|
Packit |
79f644 |
* and @escaped_scope parameters originate from the result of the
|
|
Packit |
79f644 |
* the goa_oauth2_provider_get_authorization_uri(), goa_oauth2_provider_get_redirect_uri(), goa_oauth2_provider_get_client_id()
|
|
Packit |
79f644 |
* and goa_oauth2_provider_get_scope() methods with the latter
|
|
Packit |
79f644 |
* three escaped using g_uri_escape_string().
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer full): An authorization URI that must be freed with g_free().
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_build_authorization_uri (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
const gchar *authorization_uri,
|
|
Packit |
79f644 |
const gchar *escaped_redirect_uri,
|
|
Packit |
79f644 |
const gchar *escaped_client_id,
|
|
Packit |
79f644 |
const gchar *escaped_scope)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (authorization_uri != NULL, NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (escaped_redirect_uri != NULL, NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (escaped_client_id != NULL, NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->build_authorization_uri (self,
|
|
Packit |
79f644 |
authorization_uri,
|
|
Packit |
79f644 |
escaped_redirect_uri,
|
|
Packit |
79f644 |
escaped_client_id,
|
|
Packit |
79f644 |
escaped_scope);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_decide_navigation_policy_default (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
WebKitWebView *web_view,
|
|
Packit |
79f644 |
WebKitNavigationPolicyDecision *decision)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return FALSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/*
|
|
Packit |
79f644 |
* goa_oauth2_provider_decide_navigation_policy_default:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @decision: A #WebKitNavigationPolicyDecision
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Certain OAuth2-like, but not exactly
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15">OAuth2</ulink>,
|
|
Packit |
79f644 |
* providers may not send us to the redirect URI, as expected. They
|
|
Packit |
79f644 |
* might need some special handling for that. This is a provider
|
|
Packit |
79f644 |
* specific hook to accommodate them.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %FALSE.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if @provider decided what to do with @decision,
|
|
Packit |
79f644 |
* %FALSE otherwise.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_decide_navigation_policy (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
WebKitWebView *web_view,
|
|
Packit |
79f644 |
WebKitNavigationPolicyDecision *decision)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (WEBKIT_IS_WEB_VIEW (web_view), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (WEBKIT_IS_NAVIGATION_POLICY_DECISION (decision), FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->decide_navigation_policy (self, web_view, decision);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_process_redirect_url:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @redirect_url: A redirect URI from the web browser
|
|
Packit |
79f644 |
* @authorization_code: (out): Return location for access token
|
|
Packit |
79f644 |
* @error: Return location for error or %NULL
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Certain OAuth2-like, but not exactly
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15">OAuth2</ulink>,
|
|
Packit |
79f644 |
* providers do not follow the standard mechanisms for extracting the
|
|
Packit |
79f644 |
* access token or auth code from the redirect URI. They use some
|
|
Packit |
79f644 |
* non-standard technique to do so. This is a provider specific hook
|
|
Packit |
79f644 |
* to accommodate them and will only be used if implemented.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation if needed.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if @provider could process @redirect_url, %FALSE
|
|
Packit |
79f644 |
* otherwise.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_process_redirect_url (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
const gchar *redirect_url,
|
|
Packit |
79f644 |
gchar **authorization_code,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (redirect_url != NULL, FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (authorization_code != NULL, FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->process_redirect_url (self, redirect_url, authorization_code, error);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_authorization_uri:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-2.1">authorization
|
|
Packit |
79f644 |
* endpoint</ulink> used for authenticating the user and obtaining
|
|
Packit |
79f644 |
* authorization.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* You should not include any parameters in the returned URI. If you
|
|
Packit |
79f644 |
* need to include additional parameters than the standard ones,
|
|
Packit |
79f644 |
* override #GoaOAuth2ProviderClass.build_authorization_uri -
|
|
Packit |
79f644 |
* see goa_oauth2_provider_build_authorization_uri() for more
|
|
Packit |
79f644 |
* details.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_authorization_uri (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_authorization_uri (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_token_uri_default (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_token_uri:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-2.2">token
|
|
Packit |
79f644 |
* endpoint</ulink> used for obtaining an access token.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* A token URI is only needed when the OAuth2 provider does not support
|
|
Packit |
79f644 |
* a separate client-side flow. In such cases, override
|
|
Packit |
79f644 |
* #GoaOAuth2ProviderClass.get_token_uri. You should not include any
|
|
Packit |
79f644 |
* parameters in the returned URI.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %NULL.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_token_uri (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_token_uri (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_redirect_uri:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-2.1.1">redirect_uri</ulink>
|
|
Packit |
79f644 |
* used when requesting authorization.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_redirect_uri (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_redirect_uri (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_scope_default (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
return NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_scope:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-2.1.1">scope</ulink>
|
|
Packit |
79f644 |
* used when requesting authorization.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a virtual method where the default implementation returns
|
|
Packit |
79f644 |
* %NULL.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_scope (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_scope (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_client_id:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-3">client_id</ulink>
|
|
Packit |
79f644 |
* identifying the client.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_client_id (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_client_id (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_client_secret:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Gets the
|
|
Packit |
79f644 |
* url="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-3">client_secret</ulink>
|
|
Packit |
79f644 |
* associated with the client.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: (transfer none): A string owned by @self - do not free.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
const gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_client_secret (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_client_secret (self);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_identity_sync:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @access_token: A valid OAuth 2.0 access token.
|
|
Packit |
79f644 |
* @out_presentation_identity: (out): Return location for presentation identity or %NULL.
|
|
Packit |
79f644 |
* @cancellable: (allow-none): A #GCancellable or %NULL.
|
|
Packit |
79f644 |
* @error: Return location for @error or %NULL.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Method that returns the identity corresponding to
|
|
Packit |
79f644 |
* @access_token.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* The identity is needed because all authentication happens out of
|
|
Packit |
79f644 |
* band. In addition to the identity, an implementation also returns a
|
|
Packit |
79f644 |
* <emphasis>presentation identity</emphasis> that is more suitable
|
|
Packit |
79f644 |
* for presentation (the identity could be a GUID for example).
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* The calling thread is blocked while the identity is obtained.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: The identity or %NULL if error is set. The returned string
|
|
Packit |
79f644 |
* must be freed with g_free().
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_identity_sync (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
const gchar *access_token,
|
|
Packit |
79f644 |
gchar **out_presentation_identity,
|
|
Packit |
79f644 |
GCancellable *cancellable,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (access_token != NULL, NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->get_identity_sync (self,
|
|
Packit |
79f644 |
access_token,
|
|
Packit |
79f644 |
out_presentation_identity,
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_is_identity_node:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @element: A WebKitDOMHTMLInputElement.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Checks whether @element is the HTML UI element that the user can
|
|
Packit |
79f644 |
* use to identify herself at the provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This is a pure virtual method - a subclass must provide an
|
|
Packit |
79f644 |
* implementation.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: %TRUE if the @element can be used to deny permission.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_is_identity_node (GoaOAuth2Provider *self, WebKitDOMHTMLInputElement *element)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
return GOA_OAUTH2_PROVIDER_GET_CLASS (self)->is_identity_node (self, element);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gchar *
|
|
Packit |
79f644 |
get_tokens_sync (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
const gchar *authorization_code,
|
|
Packit |
79f644 |
const gchar *refresh_token,
|
|
Packit |
79f644 |
gchar **out_refresh_token,
|
|
Packit |
79f644 |
gint *out_access_token_expires_in,
|
|
Packit |
79f644 |
GCancellable *cancellable,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GError *tokens_error = NULL;
|
|
Packit |
79f644 |
RestProxy *proxy;
|
|
Packit |
79f644 |
RestProxyCall *call;
|
|
Packit |
79f644 |
gchar *ret = NULL;
|
|
Packit |
79f644 |
guint status_code;
|
|
Packit |
79f644 |
gchar *ret_access_token = NULL;
|
|
Packit |
79f644 |
gint ret_access_token_expires_in = 0;
|
|
Packit |
79f644 |
gchar *ret_refresh_token = NULL;
|
|
Packit |
79f644 |
const gchar *payload;
|
|
Packit |
79f644 |
gsize payload_length;
|
|
Packit |
79f644 |
const gchar *client_secret;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
proxy = goa_rest_proxy_new (goa_oauth2_provider_get_token_uri (self), FALSE);
|
|
Packit |
79f644 |
call = rest_proxy_new_call (proxy);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
rest_proxy_call_set_method (call, "POST");
|
|
Packit |
79f644 |
rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "client_id", goa_oauth2_provider_get_client_id (self));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
client_secret = goa_oauth2_provider_get_client_secret (self);
|
|
Packit |
79f644 |
if (client_secret != NULL)
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "client_secret", client_secret);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (refresh_token != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* Swell, we have a refresh code - just use that */
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "grant_type", "refresh_token");
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "refresh_token", refresh_token);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* No refresh code.. request an access token using the authorization code instead */
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "grant_type", "authorization_code");
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "redirect_uri", goa_oauth2_provider_get_redirect_uri (self));
|
|
Packit |
79f644 |
rest_proxy_call_add_param (call, "code", authorization_code);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: cancellable support? */
|
|
Packit |
79f644 |
if (!rest_proxy_call_sync (call, error))
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
status_code = rest_proxy_call_get_status_code (call);
|
|
Packit |
79f644 |
if (status_code != 200)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_FAILED,
|
|
Packit |
79f644 |
_("Expected status 200 when requesting access token, instead got status %d (%s)"),
|
|
Packit |
79f644 |
status_code,
|
|
Packit |
79f644 |
rest_proxy_call_get_status_message (call));
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
payload = rest_proxy_call_get_payload (call);
|
|
Packit |
79f644 |
payload_length = rest_proxy_call_get_payload_length (call);
|
|
Packit |
79f644 |
/* some older OAuth2 implementations does not return json - handle that too */
|
|
Packit |
79f644 |
if (g_str_has_prefix (payload, "access_token="))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GHashTable *hash;
|
|
Packit |
79f644 |
const gchar *expires_in_str;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_debug ("Response is not JSON - possibly old OAuth2 implementation");
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
hash = soup_form_decode (payload);
|
|
Packit |
79f644 |
ret_access_token = g_strdup (g_hash_table_lookup (hash, "access_token"));
|
|
Packit |
79f644 |
if (ret_access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_warning ("Did not find access_token in non-JSON data");
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_FAILED,
|
|
Packit |
79f644 |
_("Could not parse response"));
|
|
Packit |
79f644 |
g_hash_table_unref (hash);
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
/* refresh_token is optional */
|
|
Packit |
79f644 |
ret_refresh_token = g_hash_table_lookup (hash, "refresh_token");
|
|
Packit |
79f644 |
/* expires_in is optional */
|
|
Packit |
79f644 |
expires_in_str = g_hash_table_lookup (hash, "expires_in");
|
|
Packit |
79f644 |
/* sometimes "expires_in" appears as "expires" */
|
|
Packit |
79f644 |
if (expires_in_str == NULL)
|
|
Packit |
79f644 |
expires_in_str = g_hash_table_lookup (hash, "expires");
|
|
Packit |
79f644 |
if (expires_in_str != NULL)
|
|
Packit |
79f644 |
ret_access_token_expires_in = atoi (expires_in_str);
|
|
Packit |
79f644 |
g_hash_table_unref (hash);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
JsonParser *parser;
|
|
Packit |
79f644 |
JsonObject *object;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
parser = json_parser_new ();
|
|
Packit |
79f644 |
if (!json_parser_load_from_data (parser, payload, payload_length, &tokens_error))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_warning ("json_parser_load_from_data() failed: %s (%s, %d)",
|
|
Packit |
79f644 |
tokens_error->message,
|
|
Packit |
79f644 |
g_quark_to_string (tokens_error->domain),
|
|
Packit |
79f644 |
tokens_error->code);
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_FAILED,
|
|
Packit |
79f644 |
_("Could not parse response"));
|
|
Packit |
79f644 |
g_object_unref (parser);
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
object = json_node_get_object (json_parser_get_root (parser));
|
|
Packit |
79f644 |
if (!json_object_has_member (object, "access_token"))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_warning ("Did not find access_token in JSON data");
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_FAILED,
|
|
Packit |
79f644 |
_("Could not parse response"));
|
|
Packit |
79f644 |
g_object_unref (parser);
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret_access_token = g_strdup (json_object_get_string_member (object, "access_token"));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* refresh_token is optional... */
|
|
Packit |
79f644 |
if (json_object_has_member (object, "refresh_token"))
|
|
Packit |
79f644 |
ret_refresh_token = g_strdup (json_object_get_string_member (object, "refresh_token"));
|
|
Packit |
79f644 |
if (json_object_has_member (object, "expires_in"))
|
|
Packit |
79f644 |
ret_access_token_expires_in = json_object_get_int_member (object, "expires_in");
|
|
Packit |
79f644 |
g_object_unref (parser);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret = ret_access_token;
|
|
Packit |
79f644 |
ret_access_token = NULL;
|
|
Packit |
79f644 |
if (out_access_token_expires_in != NULL)
|
|
Packit |
79f644 |
*out_access_token_expires_in = ret_access_token_expires_in;
|
|
Packit |
79f644 |
if (out_refresh_token != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
*out_refresh_token = ret_refresh_token;
|
|
Packit |
79f644 |
ret_refresh_token = NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
g_clear_error (&tokens_error);
|
|
Packit |
79f644 |
g_free (ret_access_token);
|
|
Packit |
79f644 |
g_free (ret_refresh_token);
|
|
Packit |
79f644 |
g_clear_object (&call);
|
|
Packit |
79f644 |
g_clear_object (&proxy);
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
on_web_view_deny_click (GoaWebView *web_view, gpointer user_data)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
gtk_dialog_response (priv->dialog, GTK_RESPONSE_CANCEL);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
on_web_view_password_submit (GoaWebView *web_view, const gchar *password, gpointer user_data)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_free (priv->password);
|
|
Packit |
79f644 |
priv->password = g_strdup (password);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
on_web_view_decide_policy (WebKitWebView *web_view,
|
|
Packit |
79f644 |
WebKitPolicyDecision *decision,
|
|
Packit |
79f644 |
WebKitPolicyDecisionType decision_type,
|
|
Packit |
79f644 |
gpointer user_data)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
GHashTable *key_value_pairs;
|
|
Packit |
79f644 |
WebKitNavigationAction *action;
|
|
Packit |
79f644 |
WebKitURIRequest *request;
|
|
Packit |
79f644 |
SoupURI *uri;
|
|
Packit |
79f644 |
const gchar *fragment;
|
|
Packit |
79f644 |
const gchar *oauth2_error;
|
|
Packit |
79f644 |
const gchar *query;
|
|
Packit |
79f644 |
const gchar *redirect_uri;
|
|
Packit |
79f644 |
const gchar *requested_uri;
|
|
Packit |
79f644 |
gint response_id = GTK_RESPONSE_NONE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
|
|
Packit |
79f644 |
goto default_behaviour;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (goa_oauth2_provider_decide_navigation_policy (self,
|
|
Packit |
79f644 |
web_view,
|
|
Packit |
79f644 |
WEBKIT_NAVIGATION_POLICY_DECISION (decision)))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
response_id = 0;
|
|
Packit |
79f644 |
goto ignore_request;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: use oauth2_proxy_extract_access_token() */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
action = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (decision));
|
|
Packit |
79f644 |
request = webkit_navigation_action_get_request (action);
|
|
Packit |
79f644 |
requested_uri = webkit_uri_request_get_uri (request);
|
|
Packit |
79f644 |
redirect_uri = goa_oauth2_provider_get_redirect_uri (self);
|
|
Packit |
79f644 |
if (!g_str_has_prefix (requested_uri, redirect_uri))
|
|
Packit |
79f644 |
goto default_behaviour;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
uri = soup_uri_new (requested_uri);
|
|
Packit |
79f644 |
fragment = soup_uri_get_fragment (uri);
|
|
Packit |
79f644 |
query = soup_uri_get_query (uri);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* Three cases:
|
|
Packit |
79f644 |
* 1) we can either have the backend handle the URI for us, or
|
|
Packit |
79f644 |
* 2) we can either have the access_token and other information
|
|
Packit |
79f644 |
* directly in the fragment part of the URI, or
|
|
Packit |
79f644 |
* 3) the auth code can be in the query part of the URI, with which
|
|
Packit |
79f644 |
* we'll obtain the token later.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (GOA_OAUTH2_PROVIDER_GET_CLASS (self)->process_redirect_url)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
gchar *url;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
url = soup_uri_to_string (uri, FALSE);
|
|
Packit |
79f644 |
if (!goa_oauth2_provider_process_redirect_url (self, url, &priv->access_token, &priv->error))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_prefix_error (&priv->error, _("Authorization response: "));
|
|
Packit |
79f644 |
priv->error->domain = GOA_ERROR;
|
|
Packit |
79f644 |
priv->error->code = GOA_ERROR_NOT_AUTHORIZED;
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_CLOSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_OK;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_free (url);
|
|
Packit |
79f644 |
goto ignore_request;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (fragment != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* fragment is encoded into a key/value pairs for the token and
|
|
Packit |
79f644 |
* expiration values, using the same syntax as a URL query */
|
|
Packit |
79f644 |
key_value_pairs = soup_form_decode (fragment);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* We might use oauth2_proxy_extract_access_token() here but
|
|
Packit |
79f644 |
* we can also extract other information.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
priv->access_token = g_strdup (g_hash_table_lookup (key_value_pairs, "access_token"));
|
|
Packit |
79f644 |
if (priv->access_token != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
gchar *expires_in_str = NULL;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
expires_in_str = g_hash_table_lookup (key_value_pairs, "expires_in");
|
|
Packit |
79f644 |
/* sometimes "expires_in" appears as "expires" */
|
|
Packit |
79f644 |
if (expires_in_str == NULL)
|
|
Packit |
79f644 |
expires_in_str = g_hash_table_lookup (key_value_pairs, "expires");
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (expires_in_str != NULL)
|
|
Packit |
79f644 |
priv->access_token_expires_in = atoi (expires_in_str);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv->refresh_token = g_strdup (g_hash_table_lookup (key_value_pairs, "refresh_token"));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_OK;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
g_hash_table_unref (key_value_pairs);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (priv->access_token != NULL)
|
|
Packit |
79f644 |
goto ignore_request;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (query != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
key_value_pairs = soup_form_decode (query);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
|
|
Packit |
79f644 |
if (priv->authorization_code != NULL)
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_OK;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_hash_table_unref (key_value_pairs);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (priv->authorization_code != NULL)
|
|
Packit |
79f644 |
goto ignore_request;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* In case we don't find the access_token or auth code, then look
|
|
Packit |
79f644 |
* for the error in the query part of the URI.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
key_value_pairs = soup_form_decode (query);
|
|
Packit |
79f644 |
oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
|
|
Packit |
79f644 |
if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_CANCEL;
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_set_error (&priv->error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_NOT_AUTHORIZED,
|
|
Packit |
79f644 |
_("Authorization response: %s"),
|
|
Packit |
79f644 |
oauth2_error);
|
|
Packit |
79f644 |
response_id = GTK_RESPONSE_CLOSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
g_hash_table_unref (key_value_pairs);
|
|
Packit |
79f644 |
goto ignore_request;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ignore_request:
|
|
Packit |
79f644 |
g_assert (response_id != GTK_RESPONSE_NONE);
|
|
Packit |
79f644 |
if (response_id < 0)
|
|
Packit |
79f644 |
gtk_dialog_response (priv->dialog, response_id);
|
|
Packit |
79f644 |
webkit_policy_decision_ignore (decision);
|
|
Packit |
79f644 |
return TRUE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
default_behaviour:
|
|
Packit |
79f644 |
return FALSE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
get_tokens_and_identity (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
gboolean add_account,
|
|
Packit |
79f644 |
const gchar *existing_identity,
|
|
Packit |
79f644 |
GtkDialog *dialog,
|
|
Packit |
79f644 |
GtkBox *vbox)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
gboolean ret = FALSE;
|
|
Packit |
79f644 |
gchar *url;
|
|
Packit |
79f644 |
GtkWidget *embed;
|
|
Packit |
79f644 |
GtkWidget *grid;
|
|
Packit |
79f644 |
GtkWidget *web_view;
|
|
Packit |
79f644 |
const gchar *scope;
|
|
Packit |
79f644 |
gchar *escaped_redirect_uri = NULL;
|
|
Packit |
79f644 |
gchar *escaped_client_id = NULL;
|
|
Packit |
79f644 |
gchar *escaped_scope = NULL;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail ((!add_account && existing_identity != NULL && existing_identity[0] != '\0')
|
|
Packit |
79f644 |
|| (add_account && existing_identity == NULL), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (GTK_IS_BOX (vbox), FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
g_return_val_if_fail (priv->error == NULL, FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: check with NM whether we're online, if not - return error */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv->dialog = dialog;
|
|
Packit |
79f644 |
priv->existing_identity = existing_identity;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_clear_pointer (&priv->password, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->identity, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->presentation_identity, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->authorization_code, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->access_token, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->refresh_token, g_free);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: use oauth2_proxy_build_login_url_full() */
|
|
Packit |
79f644 |
escaped_redirect_uri = g_uri_escape_string (goa_oauth2_provider_get_redirect_uri (self), NULL, TRUE);
|
|
Packit |
79f644 |
escaped_client_id = g_uri_escape_string (goa_oauth2_provider_get_client_id (self), NULL, TRUE);
|
|
Packit |
79f644 |
scope = goa_oauth2_provider_get_scope (self);
|
|
Packit |
79f644 |
if (scope != NULL)
|
|
Packit |
79f644 |
escaped_scope = g_uri_escape_string (goa_oauth2_provider_get_scope (self), NULL, TRUE);
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
escaped_scope = NULL;
|
|
Packit |
79f644 |
url = goa_oauth2_provider_build_authorization_uri (self,
|
|
Packit |
79f644 |
goa_oauth2_provider_get_authorization_uri (self),
|
|
Packit |
79f644 |
escaped_redirect_uri,
|
|
Packit |
79f644 |
escaped_client_id,
|
|
Packit |
79f644 |
escaped_scope);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
goa_utils_set_dialog_title (GOA_PROVIDER (self), dialog, add_account);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
grid = gtk_grid_new ();
|
|
Packit |
79f644 |
gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
|
|
Packit |
79f644 |
gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
|
|
Packit |
79f644 |
gtk_container_add (GTK_CONTAINER (vbox), grid);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
web_view = goa_web_view_new (GOA_PROVIDER (self), existing_identity);
|
|
Packit |
79f644 |
gtk_widget_set_hexpand (web_view, TRUE);
|
|
Packit |
79f644 |
gtk_widget_set_vexpand (web_view, TRUE);
|
|
Packit |
79f644 |
embed = goa_web_view_get_view (GOA_WEB_VIEW (web_view));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (goa_oauth2_provider_get_use_mobile_browser (self))
|
|
Packit |
79f644 |
goa_web_view_fake_mobile (GOA_WEB_VIEW (web_view));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (embed), url);
|
|
Packit |
79f644 |
g_signal_connect (embed,
|
|
Packit |
79f644 |
"decide-policy",
|
|
Packit |
79f644 |
G_CALLBACK (on_web_view_decide_policy),
|
|
Packit |
79f644 |
self);
|
|
Packit |
79f644 |
g_signal_connect (web_view, "deny-click", G_CALLBACK (on_web_view_deny_click), self);
|
|
Packit |
79f644 |
g_signal_connect (web_view, "password-submit", G_CALLBACK (on_web_view_password_submit), self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gtk_container_add (GTK_CONTAINER (grid), web_view);
|
|
Packit |
79f644 |
gtk_window_set_default_size (GTK_WINDOW (dialog), -1, -1);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gtk_widget_show_all (GTK_WIDGET (vbox));
|
|
Packit |
79f644 |
gtk_dialog_run (GTK_DIALOG (dialog));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* We can have either the auth code, with which we'll obtain the token, or
|
|
Packit |
79f644 |
* the token directly if we are using a client side flow, since we don't
|
|
Packit |
79f644 |
* need to pass the code to the remote application.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (priv->authorization_code == NULL && priv->access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (priv->error == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_set_error (&priv->error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_DIALOG_DISMISSED,
|
|
Packit |
79f644 |
_("Dialog was dismissed"));
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
g_assert (priv->error == NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gtk_widget_hide (GTK_WIDGET (dialog));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (priv->authorization_code != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* OK, we now have the authorization code... now we need to get the
|
|
Packit |
79f644 |
* email address (to e.g. check if the account already exists on
|
|
Packit |
79f644 |
* @client).. for that we need to get a (short-lived) access token
|
|
Packit |
79f644 |
* and a refresh_token
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: run in worker thread */
|
|
Packit |
79f644 |
priv->access_token = get_tokens_sync (self,
|
|
Packit |
79f644 |
priv->authorization_code,
|
|
Packit |
79f644 |
NULL, /* refresh_token */
|
|
Packit |
79f644 |
&priv->refresh_token,
|
|
Packit |
79f644 |
&priv->access_token_expires_in,
|
|
Packit |
79f644 |
NULL, /* GCancellable */
|
|
Packit |
79f644 |
&priv->error);
|
|
Packit |
79f644 |
if (priv->access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_prefix_error (&priv->error, _("Error getting an Access Token: "));
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_assert (priv->access_token != NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: run in worker thread */
|
|
Packit |
79f644 |
priv->identity = goa_oauth2_provider_get_identity_sync (self,
|
|
Packit |
79f644 |
priv->access_token,
|
|
Packit |
79f644 |
&priv->presentation_identity,
|
|
Packit |
79f644 |
NULL, /* TODO: GCancellable */
|
|
Packit |
79f644 |
&priv->error);
|
|
Packit |
79f644 |
if (priv->identity == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_prefix_error (&priv->error, _("Error getting identity: "));
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret = TRUE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
g_free (url);
|
|
Packit |
79f644 |
g_free (escaped_redirect_uri);
|
|
Packit |
79f644 |
g_free (escaped_client_id);
|
|
Packit |
79f644 |
g_free (escaped_scope);
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
add_account_cb (GoaManager *manager,
|
|
Packit |
79f644 |
GAsyncResult *res,
|
|
Packit |
79f644 |
gpointer user_data)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (user_data);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
goa_manager_call_add_account_finish (manager,
|
|
Packit |
79f644 |
&priv->account_object_path,
|
|
Packit |
79f644 |
res,
|
|
Packit |
79f644 |
&priv->error);
|
|
Packit |
79f644 |
g_main_loop_quit (priv->loop);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gint64
|
|
Packit |
79f644 |
duration_to_abs_usec (gint duration_sec)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
gint64 ret;
|
|
Packit |
79f644 |
GTimeVal now;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_get_current_time (&now;;
|
|
Packit |
79f644 |
ret = ((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec);
|
|
Packit |
79f644 |
ret += ((gint64) duration_sec) * 1000L * 1000L;
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gint
|
|
Packit |
79f644 |
abs_usec_to_duration (gint64 abs_usec)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
gint64 ret;
|
|
Packit |
79f644 |
GTimeVal now;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_get_current_time (&now;;
|
|
Packit |
79f644 |
ret = abs_usec - (((gint64) now.tv_sec) * 1000L * 1000L + ((gint64) now.tv_usec));
|
|
Packit |
79f644 |
ret /= 1000L * 1000L;
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
add_credentials_key_values (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
GVariantBuilder *credentials)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (priv->authorization_code != NULL)
|
|
Packit |
79f644 |
g_variant_builder_add (credentials, "{sv}", "authorization_code",
|
|
Packit |
79f644 |
g_variant_new_string (priv->authorization_code));
|
|
Packit |
79f644 |
g_variant_builder_add (credentials, "{sv}", "access_token", g_variant_new_string (priv->access_token));
|
|
Packit |
79f644 |
if (priv->access_token_expires_in > 0)
|
|
Packit |
79f644 |
g_variant_builder_add (credentials, "{sv}", "access_token_expires_at",
|
|
Packit |
79f644 |
g_variant_new_int64 (duration_to_abs_usec (priv->access_token_expires_in)));
|
|
Packit |
79f644 |
if (priv->refresh_token != NULL)
|
|
Packit |
79f644 |
g_variant_builder_add (credentials, "{sv}", "refresh_token", g_variant_new_string (priv->refresh_token));
|
|
Packit |
79f644 |
if (priv->password != NULL)
|
|
Packit |
79f644 |
g_variant_builder_add (credentials, "{sv}", "password", g_variant_new_string (priv->password));
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static GoaObject *
|
|
Packit |
79f644 |
goa_oauth2_provider_add_account (GoaProvider *provider,
|
|
Packit |
79f644 |
GoaClient *client,
|
|
Packit |
79f644 |
GtkDialog *dialog,
|
|
Packit |
79f644 |
GtkBox *vbox,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (provider);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
GoaObject *ret = NULL;
|
|
Packit |
79f644 |
GVariantBuilder credentials;
|
|
Packit |
79f644 |
GVariantBuilder details;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_CLIENT (client), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (GTK_IS_BOX (vbox), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (!get_tokens_and_identity (self, TRUE, NULL, dialog, vbox))
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* OK, got the identity... see if there's already an account
|
|
Packit |
79f644 |
* of this type with the given identity
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (!goa_utils_check_duplicate (client,
|
|
Packit |
79f644 |
priv->identity,
|
|
Packit |
79f644 |
priv->presentation_identity,
|
|
Packit |
79f644 |
goa_provider_get_provider_type (GOA_PROVIDER (self)),
|
|
Packit |
79f644 |
(GoaPeekInterfaceFunc) goa_object_peek_oauth2_based,
|
|
Packit |
79f644 |
&priv->error))
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
|
|
Packit |
79f644 |
add_credentials_key_values (self, &credentials);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
|
|
Packit |
79f644 |
goa_oauth2_provider_add_account_key_values (self, &details);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* we want the GoaClient to update before this method returns (so it
|
|
Packit |
79f644 |
* can create a proxy for the new object) so run the mainloop while
|
|
Packit |
79f644 |
* waiting for this to complete
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
goa_manager_call_add_account (goa_client_get_manager (client),
|
|
Packit |
79f644 |
goa_provider_get_provider_type (GOA_PROVIDER (self)),
|
|
Packit |
79f644 |
priv->identity,
|
|
Packit |
79f644 |
priv->presentation_identity,
|
|
Packit |
79f644 |
g_variant_builder_end (&credentials),
|
|
Packit |
79f644 |
g_variant_builder_end (&details),
|
|
Packit |
79f644 |
NULL, /* GCancellable* */
|
|
Packit |
79f644 |
(GAsyncReadyCallback) add_account_cb,
|
|
Packit |
79f644 |
self);
|
|
Packit |
79f644 |
priv->loop = g_main_loop_new (NULL, FALSE);
|
|
Packit |
79f644 |
g_main_loop_run (priv->loop);
|
|
Packit |
79f644 |
if (priv->error != NULL)
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
|
|
Packit |
79f644 |
priv->account_object_path));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
/* We might have an object even when priv->error is set.
|
|
Packit |
79f644 |
* eg., if we failed to store the credentials in the keyring.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (priv->error != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_propagate_error (error, priv->error);
|
|
Packit |
79f644 |
priv->error = NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
g_assert (ret != NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_clear_pointer (&priv->account_object_path, g_free);
|
|
Packit |
79f644 |
g_clear_pointer (&priv->loop, g_main_loop_unref);
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_refresh_account (GoaProvider *provider,
|
|
Packit |
79f644 |
GoaClient *client,
|
|
Packit |
79f644 |
GoaObject *object,
|
|
Packit |
79f644 |
GtkWindow *parent,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (provider);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
GoaAccount *account;
|
|
Packit |
79f644 |
GtkWidget *dialog;
|
|
Packit |
79f644 |
const gchar *existing_identity;
|
|
Packit |
79f644 |
const gchar *existing_presentation_identity;
|
|
Packit |
79f644 |
GVariantBuilder builder;
|
|
Packit |
79f644 |
gboolean ret = FALSE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
|
|
Packit |
79f644 |
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
dialog = gtk_dialog_new_with_buttons (NULL,
|
|
Packit |
79f644 |
parent,
|
|
Packit |
79f644 |
GTK_DIALOG_MODAL
|
|
Packit |
79f644 |
| GTK_DIALOG_DESTROY_WITH_PARENT
|
|
Packit |
79f644 |
| GTK_DIALOG_USE_HEADER_BAR,
|
|
Packit |
79f644 |
NULL,
|
|
Packit |
79f644 |
NULL);
|
|
Packit |
79f644 |
gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
|
|
Packit |
79f644 |
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
|
|
Packit |
79f644 |
gtk_widget_show_all (dialog);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
account = goa_object_peek_account (object);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* We abuse presentation identity here because for some providers
|
|
Packit |
79f644 |
* identity can be a machine readable ID, which can not be used to
|
|
Packit |
79f644 |
* log in via the provider's web interface.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
existing_presentation_identity = goa_account_get_presentation_identity (account);
|
|
Packit |
79f644 |
if (!get_tokens_and_identity (self,
|
|
Packit |
79f644 |
FALSE,
|
|
Packit |
79f644 |
existing_presentation_identity,
|
|
Packit |
79f644 |
GTK_DIALOG (dialog),
|
|
Packit |
79f644 |
GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog)))))
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* Changes made to the web interface by the providers can break our
|
|
Packit |
79f644 |
* DOM parsing. So we should still query and check the identity
|
|
Packit |
79f644 |
* afterwards.
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
existing_identity = goa_account_get_identity (account);
|
|
Packit |
79f644 |
if (g_strcmp0 (priv->identity, existing_identity) != 0)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_FAILED,
|
|
Packit |
79f644 |
_("Was asked to log in as %s, but logged in as %s"),
|
|
Packit |
79f644 |
existing_identity,
|
|
Packit |
79f644 |
priv->identity);
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
|
|
Packit |
79f644 |
add_credentials_key_values (self, &builder);
|
|
Packit |
79f644 |
if (!goa_utils_store_credentials_for_object_sync (GOA_PROVIDER (self),
|
|
Packit |
79f644 |
object,
|
|
Packit |
79f644 |
g_variant_builder_end (&builder),
|
|
Packit |
79f644 |
NULL, /* GCancellable */
|
|
Packit |
79f644 |
error))
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
goa_account_call_ensure_credentials (goa_object_peek_account (object),
|
|
Packit |
79f644 |
NULL, /* GCancellable */
|
|
Packit |
79f644 |
NULL, NULL); /* callback, user_data */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
ret = TRUE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
if (priv->error != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_propagate_error (error, priv->error);
|
|
Packit |
79f644 |
priv->error = NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
gtk_widget_destroy (dialog);
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
free_mutex (GMutex *mutex)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_mutex_clear (mutex);
|
|
Packit |
79f644 |
g_slice_free (GMutex, mutex);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/**
|
|
Packit |
79f644 |
* goa_oauth2_provider_get_access_token_sync:
|
|
Packit |
79f644 |
* @self: A #GoaOAuth2Provider.
|
|
Packit |
79f644 |
* @object: A #GoaObject.
|
|
Packit |
79f644 |
* @force_refresh: If set to %TRUE, forces a refresh of the access token, if possible.
|
|
Packit |
79f644 |
* @out_access_token_expires_in: (out): Return location for how many seconds the returned token is valid for (0 if unknown) or %NULL.
|
|
Packit |
79f644 |
* @cancellable: (allow-none): A #GCancellable or %NULL.
|
|
Packit |
79f644 |
* @error: Return location for error or %NULL.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Synchronously gets an access token for @object. The calling thread
|
|
Packit |
79f644 |
* is blocked while the operation is pending.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* The resulting token is typically read from the local cache so most
|
|
Packit |
79f644 |
* of the time only a local roundtrip to the storage for the token
|
|
Packit |
79f644 |
* cache (e.g. <command>gnome-keyring-daemon</command>) is
|
|
Packit |
79f644 |
* needed. However, the operation may involve refreshing the token
|
|
Packit |
79f644 |
* with the service provider so a full network round-trip may be
|
|
Packit |
79f644 |
* needed.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Note that multiple calls are serialized to avoid multiple
|
|
Packit |
79f644 |
* outstanding requests to the service provider.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* This operation may fail if e.g. unable to refresh the credentials
|
|
Packit |
79f644 |
* or if network connectivity is not available. Note that even if a
|
|
Packit |
79f644 |
* token is returned, the returned token isn't guaranteed to work -
|
|
Packit |
79f644 |
* use goa_provider_ensure_credentials_sync() if you need
|
|
Packit |
79f644 |
* stronger guarantees.
|
|
Packit |
79f644 |
*
|
|
Packit |
79f644 |
* Returns: The access token or %NULL if error is set. The returned
|
|
Packit |
79f644 |
* string must be freed with g_free().
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
gchar *
|
|
Packit |
79f644 |
goa_oauth2_provider_get_access_token_sync (GoaOAuth2Provider *self,
|
|
Packit |
79f644 |
GoaObject *object,
|
|
Packit |
79f644 |
gboolean force_refresh,
|
|
Packit |
79f644 |
gint *out_access_token_expires_in,
|
|
Packit |
79f644 |
GCancellable *cancellable,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GVariant *credentials = NULL;
|
|
Packit |
79f644 |
GVariantIter iter;
|
|
Packit |
79f644 |
const gchar *key;
|
|
Packit |
79f644 |
GVariant *value;
|
|
Packit |
79f644 |
gchar *authorization_code = NULL;
|
|
Packit |
79f644 |
gchar *access_token = NULL;
|
|
Packit |
79f644 |
gint access_token_expires_in = 0;
|
|
Packit |
79f644 |
gchar *refresh_token = NULL;
|
|
Packit |
79f644 |
gchar *old_refresh_token = NULL;
|
|
Packit |
79f644 |
gchar *password = NULL;
|
|
Packit |
79f644 |
gboolean success = FALSE;
|
|
Packit |
79f644 |
GVariantBuilder builder;
|
|
Packit |
79f644 |
gchar *ret = NULL;
|
|
Packit |
79f644 |
GMutex *lock;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OAUTH2_PROVIDER (self), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (GOA_IS_OBJECT (object), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
|
Packit |
79f644 |
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* provider_lock is too coarse, use a per-object lock instead */
|
|
Packit |
79f644 |
G_LOCK (provider_lock);
|
|
Packit |
79f644 |
lock = g_object_get_data (G_OBJECT (object), "-goa-oauth2-provider-get-access-token-lock");
|
|
Packit |
79f644 |
if (lock == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
lock = g_slice_new0 (GMutex);
|
|
Packit |
79f644 |
g_mutex_init (lock);
|
|
Packit |
79f644 |
g_object_set_data_full (G_OBJECT (object),
|
|
Packit |
79f644 |
"-goa-oauth2-provider-get-access-token-lock",
|
|
Packit |
79f644 |
lock,
|
|
Packit |
79f644 |
(GDestroyNotify) free_mutex);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
G_UNLOCK (provider_lock);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_mutex_lock (lock);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* First, get the credentials from the keyring */
|
|
Packit |
79f644 |
credentials = goa_utils_lookup_credentials_sync (GOA_PROVIDER (self),
|
|
Packit |
79f644 |
object,
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error);
|
|
Packit |
79f644 |
if (credentials == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (error != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
(*error)->domain = GOA_ERROR;
|
|
Packit |
79f644 |
(*error)->code = GOA_ERROR_NOT_AUTHORIZED;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_variant_iter_init (&iter, credentials);
|
|
Packit |
79f644 |
while (g_variant_iter_next (&iter, "{&sv}", &key, &value))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (g_strcmp0 (key, "access_token") == 0)
|
|
Packit |
79f644 |
access_token = g_variant_dup_string (value, NULL);
|
|
Packit |
79f644 |
else if (g_strcmp0 (key, "access_token_expires_at") == 0)
|
|
Packit |
79f644 |
access_token_expires_in = abs_usec_to_duration (g_variant_get_int64 (value));
|
|
Packit |
79f644 |
else if (g_strcmp0 (key, "refresh_token") == 0)
|
|
Packit |
79f644 |
refresh_token = g_variant_dup_string (value, NULL);
|
|
Packit |
79f644 |
else if (g_strcmp0 (key, "authorization_code") == 0)
|
|
Packit |
79f644 |
authorization_code = g_variant_dup_string (value, NULL);
|
|
Packit |
79f644 |
else if (g_strcmp0 (key, "password") == 0)
|
|
Packit |
79f644 |
password = g_variant_dup_string (value, NULL);
|
|
Packit |
79f644 |
g_variant_unref (value);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_set_error (error,
|
|
Packit |
79f644 |
GOA_ERROR,
|
|
Packit |
79f644 |
GOA_ERROR_NOT_AUTHORIZED,
|
|
Packit |
79f644 |
_("Credentials do not contain access_token"));
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* if we can't refresh the token, just return it no matter what */
|
|
Packit |
79f644 |
if (refresh_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_debug ("Returning locally cached credentials that cannot be refreshed");
|
|
Packit |
79f644 |
success = TRUE;
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* If access_token is still "fresh enough" (e.g. more than ten
|
|
Packit |
79f644 |
* minutes of life left in it), just return it unless we've been
|
|
Packit |
79f644 |
* asked to forcibly refresh it
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (!force_refresh && access_token_expires_in > 10*60)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_debug ("Returning locally cached credentials (expires in %d seconds)", access_token_expires_in);
|
|
Packit |
79f644 |
success = TRUE;
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_debug ("Refreshing locally cached credentials (expires in %d seconds, force_refresh=%d)", access_token_expires_in, force_refresh);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* Otherwise, refresh it */
|
|
Packit |
79f644 |
old_refresh_token = refresh_token; refresh_token = NULL;
|
|
Packit |
79f644 |
g_free (access_token); access_token = NULL;
|
|
Packit |
79f644 |
access_token = get_tokens_sync (self,
|
|
Packit |
79f644 |
authorization_code,
|
|
Packit |
79f644 |
old_refresh_token,
|
|
Packit |
79f644 |
&refresh_token,
|
|
Packit |
79f644 |
&access_token_expires_in,
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error);
|
|
Packit |
79f644 |
if (access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (error != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_prefix_error (error, _("Failed to refresh access token (%s, %d): "),
|
|
Packit |
79f644 |
g_quark_to_string ((*error)->domain), (*error)->code);
|
|
Packit |
79f644 |
(*error)->code = is_authorization_error (*error) ? GOA_ERROR_NOT_AUTHORIZED : GOA_ERROR_FAILED;
|
|
Packit |
79f644 |
(*error)->domain = GOA_ERROR;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* It's not a sure thing we get a new refresh_token, so use our old
|
|
Packit |
79f644 |
* old if we didn't get a new one
|
|
Packit |
79f644 |
*/
|
|
Packit |
79f644 |
if (refresh_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
refresh_token = old_refresh_token;
|
|
Packit |
79f644 |
old_refresh_token = NULL;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* Good. Now update the keyring with the refreshed credentials */
|
|
Packit |
79f644 |
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
|
|
Packit |
79f644 |
g_variant_builder_add (&builder, "{sv}", "authorization_code", g_variant_new_string (authorization_code));
|
|
Packit |
79f644 |
g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (access_token));
|
|
Packit |
79f644 |
if (access_token_expires_in > 0)
|
|
Packit |
79f644 |
g_variant_builder_add (&builder, "{sv}", "access_token_expires_at",
|
|
Packit |
79f644 |
g_variant_new_int64 (duration_to_abs_usec (access_token_expires_in)));
|
|
Packit |
79f644 |
if (refresh_token != NULL)
|
|
Packit |
79f644 |
g_variant_builder_add (&builder, "{sv}", "refresh_token", g_variant_new_string (refresh_token));
|
|
Packit |
79f644 |
if (password != NULL)
|
|
Packit |
79f644 |
g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
if (!goa_utils_store_credentials_for_object_sync (GOA_PROVIDER (self),
|
|
Packit |
79f644 |
object,
|
|
Packit |
79f644 |
g_variant_builder_end (&builder),
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error))
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
if (error != NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
(*error)->domain = GOA_ERROR;
|
|
Packit |
79f644 |
(*error)->code = GOA_ERROR_NOT_AUTHORIZED;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
success = TRUE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
if (success)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
ret = access_token; access_token = NULL;
|
|
Packit |
79f644 |
g_assert (ret != NULL);
|
|
Packit |
79f644 |
if (out_access_token_expires_in != NULL)
|
|
Packit |
79f644 |
*out_access_token_expires_in = access_token_expires_in;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
g_free (authorization_code);
|
|
Packit |
79f644 |
g_free (access_token);
|
|
Packit |
79f644 |
g_free (refresh_token);
|
|
Packit |
79f644 |
g_free (old_refresh_token);
|
|
Packit |
79f644 |
g_free (password);
|
|
Packit |
79f644 |
g_clear_pointer (&credentials, (GDestroyNotify) g_variant_unref);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_mutex_unlock (lock);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean on_handle_get_access_token (GoaOAuth2Based *object,
|
|
Packit |
79f644 |
GDBusMethodInvocation *invocation,
|
|
Packit |
79f644 |
gpointer user_data);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_build_object (GoaProvider *provider,
|
|
Packit |
79f644 |
GoaObjectSkeleton *object,
|
|
Packit |
79f644 |
GKeyFile *key_file,
|
|
Packit |
79f644 |
const gchar *group,
|
|
Packit |
79f644 |
GDBusConnection *connection,
|
|
Packit |
79f644 |
gboolean just_added,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Based *oauth2_based;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
oauth2_based = goa_object_get_oauth2_based (GOA_OBJECT (object));
|
|
Packit |
79f644 |
if (oauth2_based != NULL)
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
oauth2_based = goa_oauth2_based_skeleton_new ();
|
|
Packit |
79f644 |
goa_oauth2_based_set_client_id (oauth2_based,
|
|
Packit |
79f644 |
goa_oauth2_provider_get_client_id (GOA_OAUTH2_PROVIDER (provider)));
|
|
Packit |
79f644 |
goa_oauth2_based_set_client_secret (oauth2_based,
|
|
Packit |
79f644 |
goa_oauth2_provider_get_client_secret (GOA_OAUTH2_PROVIDER (provider)));
|
|
Packit |
79f644 |
/* Ensure D-Bus method invocations run in their own thread */
|
|
Packit |
79f644 |
g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (oauth2_based),
|
|
Packit |
79f644 |
G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
|
|
Packit |
79f644 |
goa_object_skeleton_set_oauth2_based (object, oauth2_based);
|
|
Packit |
79f644 |
g_signal_connect (oauth2_based,
|
|
Packit |
79f644 |
"handle-get-access-token",
|
|
Packit |
79f644 |
G_CALLBACK (on_handle_get_access_token),
|
|
Packit |
79f644 |
NULL);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
g_object_unref (oauth2_based);
|
|
Packit |
79f644 |
return TRUE;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
goa_oauth2_provider_ensure_credentials_sync (GoaProvider *provider,
|
|
Packit |
79f644 |
GoaObject *object,
|
|
Packit |
79f644 |
gint *out_expires_in,
|
|
Packit |
79f644 |
GCancellable *cancellable,
|
|
Packit |
79f644 |
GError **error)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (provider);
|
|
Packit |
79f644 |
gboolean ret = FALSE;
|
|
Packit |
79f644 |
gchar *access_token = NULL;
|
|
Packit |
79f644 |
gint access_token_expires_in;
|
|
Packit |
79f644 |
gchar *identity = NULL;
|
|
Packit |
79f644 |
gboolean force_refresh = FALSE;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
again:
|
|
Packit |
79f644 |
access_token = goa_oauth2_provider_get_access_token_sync (self,
|
|
Packit |
79f644 |
object,
|
|
Packit |
79f644 |
force_refresh,
|
|
Packit |
79f644 |
&access_token_expires_in,
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error);
|
|
Packit |
79f644 |
if (access_token == NULL)
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
identity = goa_oauth2_provider_get_identity_sync (self,
|
|
Packit |
79f644 |
access_token,
|
|
Packit |
79f644 |
NULL, /* out_presentation_identity */
|
|
Packit |
79f644 |
cancellable,
|
|
Packit |
79f644 |
error);
|
|
Packit |
79f644 |
if (identity == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
/* OK, try again, with forcing the locally cached credentials to be refreshed */
|
|
Packit |
79f644 |
if (!force_refresh)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
force_refresh = TRUE;
|
|
Packit |
79f644 |
g_free (access_token); access_token = NULL;
|
|
Packit |
79f644 |
g_clear_error (error);
|
|
Packit |
79f644 |
goto again;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
goto out;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: maybe check with the identity we have */
|
|
Packit |
79f644 |
ret = TRUE;
|
|
Packit |
79f644 |
if (out_expires_in != NULL)
|
|
Packit |
79f644 |
*out_expires_in = access_token_expires_in;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
out:
|
|
Packit |
79f644 |
g_free (identity);
|
|
Packit |
79f644 |
g_free (access_token);
|
|
Packit |
79f644 |
return ret;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
goa_oauth2_provider_finalize (GObject *object)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaOAuth2Provider *self = GOA_OAUTH2_PROVIDER (object);
|
|
Packit |
79f644 |
GoaOAuth2ProviderPrivate *priv;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
priv = goa_oauth2_provider_get_instance_private (self);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_clear_pointer (&priv->loop, g_main_loop_unref);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
g_free (priv->account_object_path);
|
|
Packit |
79f644 |
g_free (priv->password);
|
|
Packit |
79f644 |
g_free (priv->identity);
|
|
Packit |
79f644 |
g_free (priv->presentation_identity);
|
|
Packit |
79f644 |
g_free (priv->authorization_code);
|
|
Packit |
79f644 |
g_free (priv->access_token);
|
|
Packit |
79f644 |
g_free (priv->refresh_token);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
G_OBJECT_CLASS (goa_oauth2_provider_parent_class)->finalize (object);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
goa_oauth2_provider_init (GoaOAuth2Provider *self)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
static void
|
|
Packit |
79f644 |
goa_oauth2_provider_class_init (GoaOAuth2ProviderClass *klass)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GObjectClass *object_class;
|
|
Packit |
79f644 |
GoaProviderClass *provider_class;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
object_class = G_OBJECT_CLASS (klass);
|
|
Packit |
79f644 |
object_class->finalize = goa_oauth2_provider_finalize;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
provider_class = GOA_PROVIDER_CLASS (klass);
|
|
Packit |
79f644 |
provider_class->add_account = goa_oauth2_provider_add_account;
|
|
Packit |
79f644 |
provider_class->refresh_account = goa_oauth2_provider_refresh_account;
|
|
Packit |
79f644 |
provider_class->build_object = goa_oauth2_provider_build_object;
|
|
Packit |
79f644 |
provider_class->ensure_credentials_sync = goa_oauth2_provider_ensure_credentials_sync;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
klass->build_authorization_uri = goa_oauth2_provider_build_authorization_uri_default;
|
|
Packit |
79f644 |
klass->decide_navigation_policy = goa_oauth2_provider_decide_navigation_policy_default;
|
|
Packit |
79f644 |
klass->get_token_uri = goa_oauth2_provider_get_token_uri_default;
|
|
Packit |
79f644 |
klass->get_scope = goa_oauth2_provider_get_scope_default;
|
|
Packit |
79f644 |
klass->get_use_mobile_browser = goa_oauth2_provider_get_use_mobile_browser_default;
|
|
Packit |
79f644 |
klass->is_deny_node = goa_oauth2_provider_is_deny_node_default;
|
|
Packit |
79f644 |
klass->is_password_node = goa_oauth2_provider_is_password_node_default;
|
|
Packit |
79f644 |
klass->add_account_key_values = goa_oauth2_provider_add_account_key_values_default;
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* ---------------------------------------------------------------------------------------------------- */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* runs in a thread dedicated to handling @invocation */
|
|
Packit |
79f644 |
static gboolean
|
|
Packit |
79f644 |
on_handle_get_access_token (GoaOAuth2Based *interface,
|
|
Packit |
79f644 |
GDBusMethodInvocation *invocation,
|
|
Packit |
79f644 |
gpointer user_data)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
GoaObject *object;
|
|
Packit |
79f644 |
GoaAccount *account;
|
|
Packit |
79f644 |
GoaProvider *provider;
|
|
Packit |
79f644 |
GError *error;
|
|
Packit |
79f644 |
const gchar *id;
|
|
Packit |
79f644 |
const gchar *method_name;
|
|
Packit |
79f644 |
const gchar *provider_type;
|
|
Packit |
79f644 |
gchar *access_token = NULL;
|
|
Packit |
79f644 |
gint access_token_expires_in;
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
/* TODO: maybe log what app is requesting access */
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
|
|
Packit |
79f644 |
account = goa_object_peek_account (object);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
id = goa_account_get_id (account);
|
|
Packit |
79f644 |
provider_type = goa_account_get_provider_type (account);
|
|
Packit |
79f644 |
method_name = g_dbus_method_invocation_get_method_name (invocation);
|
|
Packit |
79f644 |
g_debug ("Handling %s for account (%s, %s)", method_name, provider_type, id);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
provider = goa_provider_get_for_provider_type (provider_type);
|
|
Packit |
79f644 |
|
|
Packit |
79f644 |
error = NULL;
|
|
Packit |
79f644 |
access_token = goa_oauth2_provider_get_access_token_sync (GOA_OAUTH2_PROVIDER (provider),
|
|
Packit |
79f644 |
object,
|
|
Packit |
79f644 |
FALSE, /* force_refresh */
|
|
Packit |
79f644 |
&access_token_expires_in,
|
|
Packit |
79f644 |
NULL, /* GCancellable* */
|
|
Packit |
79f644 |
&error);
|
|
Packit |
79f644 |
if (access_token == NULL)
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
g_dbus_method_invocation_take_error (invocation, error);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
else
|
|
Packit |
79f644 |
{
|
|
Packit |
79f644 |
goa_oauth2_based_complete_get_access_token (interface,
|
|
Packit |
79f644 |
invocation,
|
|
Packit |
79f644 |
access_token,
|
|
Packit |
79f644 |
access_token_expires_in);
|
|
Packit |
79f644 |
}
|
|
Packit |
79f644 |
g_free (access_token);
|
|
Packit |
79f644 |
g_object_unref (provider);
|
|
Packit |
79f644 |
return TRUE; /* invocation was handled */
|
|
Packit |
79f644 |
}
|