/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
* Copyright (C) Philip Withnall 2011, 2014 <philip@tecnocode.co.uk>
*
* GData Client 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) any later version.
*
* GData Client 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 GData Client. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:gdata-authorizer
* @short_description: GData authorization interface
* @stability: Stable
* @include: gdata/gdata-authorizer.h
*
* The #GDataAuthorizer interface provides a uniform way to implement authentication and authorization processes for use by #GDataService<!-- -->s.
* Client code will construct a new #GDataAuthorizer instance of their choosing, such as #GDataClientLoginAuthorizer or #GDataOAuth2Authorizer, for
* the #GDataService<!-- -->s which will be used by the client, then authenticates and authorizes with the #GDataAuthorizer instead of the
* #GDataService. The #GDataService then uses the #GDataAuthorizer to authorize individual network requests using whatever authorization token was
* returned to the #GDataAuthorizer by the Google Accounts service.
*
* All #GDataAuthorizer implementations are expected to operate against a set of #GDataAuthorizationDomain<!-- -->s which are provided to the
* authorizer at construction time. These domains specify which data domains the client expects to access using the #GDataService<!-- -->s they
* have using the #GDataAuthorizer instance. Following the principle of least privilege, the set of domains should be the minimum such set of domains
* which still allows the client to operate normally. Note that implementations of #GDataAuthorizationDomain may display the list of requested
* authorization domains to the user for verification before authorization is granted.
*
* #GDataAuthorizer implementations are provided for some of the standard authorization processes supported by Google for installed applications, as
* listed in their <ulink type="http" url="http://code.google.com/apis/accounts/docs/GettingStarted.html">online documentation</ulink>:
* <itemizedlist>
* <listitem>#GDataClientLoginAuthorizer for
* <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html">ClientLogin</ulink> (deprecated)</listitem>
* <listitem>#GDataOAuth1Authorizer for
* <ulink type="http" url="http://code.google.com/apis/accounts/docs/OAuthForInstalledApps.html">OAuth 1.0</ulink> (deprecated)</listitem>
* <listitem>#GDataOAuth2Authorizer for
* <ulink type="http" url="https://developers.google.com/accounts/docs/OAuth2InstalledApp">OAuth 2.0</ulink> (preferred)</listitem>
* </itemizedlist>
*
* It is quite possible for clients to write their own #GDataAuthorizer implementation. For example, if a client already uses OAuth 2.0 and handles
* authentication itself, it may want to use its own #GDataAuthorizer implementation which simply exposes the client's existing access token to
* libgdata and does nothing more.
*
* It must be noted that all #GDataAuthorizer implementations must be thread safe, as methods such as gdata_authorizer_refresh_authorization() may be
* called from any thread (such as the thread performing an asynchronous query operation) at any time.
*
* Examples of code using #GDataAuthorizer can be found in the documentation for the various implementations of the #GDataAuthorizer interface.
*
* Since: 0.9.0
*/
#include <glib.h>
#include "gdata-authorizer.h"
G_DEFINE_INTERFACE (GDataAuthorizer, gdata_authorizer, G_TYPE_OBJECT)
static void
gdata_authorizer_default_init (GDataAuthorizerInterface *iface)
{
/* Nothing to see here */
}
/**
* gdata_authorizer_process_request:
* @self: a #GDataAuthorizer
* @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
* @message: the query to process
*
* Processes @message, adding all the necessary extra headers and parameters to ensure that it's correctly authenticated and authorized under the
* given @domain for the online service. Basically, if a query is not processed by calling this method on it, it will be sent to the online service as
* if it's a query from a non-logged-in user. Similarly, if the #GDataAuthorizer isn't authenticated or authorized (for @domain), no changes will
* be made to the @message.
*
* @domain may be %NULL if the request doesn't require authorization.
*
* This modifies @message in place.
*
* This method is thread safe.
*
* Since: 0.9.0
*/
void
gdata_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message)
{
GDataAuthorizerInterface *iface;
g_return_if_fail (GDATA_IS_AUTHORIZER (self));
g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
g_return_if_fail (SOUP_IS_MESSAGE (message));
iface = GDATA_AUTHORIZER_GET_IFACE (self);
g_assert (iface->process_request != NULL);
iface->process_request (self, domain, message);
}
/**
* gdata_authorizer_is_authorized_for_domain:
* @self: (allow-none): a #GDataAuthorizer, or %NULL
* @domain: the #GDataAuthorizationDomain to check against
*
* Returns whether the #GDataAuthorizer instance believes it's currently authorized to access the given @domain. Note that this will not perform any
* network requests, and will just look up the result in the #GDataAuthorizer's local cache of authorizations. This means that the result may be out
* of date, as the server may have since invalidated the authorization. If the #GDataAuthorizer class supports timeouts and TTLs on authorizations,
* they will not be taken into account; this method effectively returns whether the last successful authorization operation performed on the
* #GDataAuthorizer included @domain in the list of requested authorization domains.
*
* Note that %NULL may be passed as the #GDataAuthorizer, in which case %FALSE will always be returned, regardless of the @domain. This is for
* convenience of checking whether a domain is authorized by the #GDataAuthorizer returned by gdata_service_get_authorizer(), which may be %NULL.
* For example:
* |[
* if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (my_service), my_domain) == TRUE) {
* /<!-- -->* Code to execute only if we're authorized for the given domain *<!-- -->/
* }
* ]|
*
* This method is thread safe.
*
* Return value: %TRUE if the #GDataAuthorizer has been authorized to access @domain, %FALSE otherwise
*
* Since: 0.9.0
*/
gboolean
gdata_authorizer_is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain)
{
GDataAuthorizerInterface *iface;
g_return_val_if_fail (self == NULL || GDATA_IS_AUTHORIZER (self), FALSE);
g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE);
if (self == NULL) {
return FALSE;
}
iface = GDATA_AUTHORIZER_GET_IFACE (self);
g_assert (iface->is_authorized_for_domain != NULL);
return iface->is_authorized_for_domain (self, domain);
}
/**
* gdata_authorizer_refresh_authorization:
* @self: a #GDataAuthorizer
* @cancellable: (allow-none): optional #GCancellable object, or %NULL
* @error: a #GError, or %NULL
*
* Forces the #GDataAuthorizer to refresh any authorization tokens it holds with the online service. This should typically be called when a
* #GDataService query returns %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, and is already called transparently by methods such as
* gdata_service_query() and gdata_service_insert_entry() (see their documentation for more details).
*
* If re-authorization is successful, it's guaranteed that by the time this method returns, the properties containing the relevant authorization
* tokens on the #GDataAuthorizer instance will have been updated.
*
* If %FALSE is returned, @error will be set if (and only if) it's due to a refresh being attempted and failing. If a refresh is not attempted, %FALSE
* will be returned but @error will not be set.
*
* If the #GDataAuthorizer has not been previously authenticated or authorized (using the class' specific methods), no authorization will be
* attempted, %FALSE will be returned immediately and @error will not be set.
*
* Some #GDataAuthorizer implementations may not support refreshing authorization tokens at all; for example if doing so requires user interaction.
* %FALSE will be returned immediately in that case and @error will not be set.
*
* This method is thread safe.
*
* Return value: %TRUE if an authorization refresh was attempted and was successful, %FALSE if a refresh wasn't attempted or was unsuccessful
*
* Since: 0.9.0
*/
gboolean
gdata_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error)
{
GDataAuthorizerInterface *iface;
g_return_val_if_fail (GDATA_IS_AUTHORIZER (self), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iface = GDATA_AUTHORIZER_GET_IFACE (self);
/* Return FALSE with no error if the method isn't implemented */
if (iface->refresh_authorization == NULL) {
return FALSE;
}
return iface->refresh_authorization (self, cancellable, error);
}
static void
refresh_authorization_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
{
GDataAuthorizer *authorizer = GDATA_AUTHORIZER (source_object);
g_autoptr(GError) error = NULL;
/* Refresh the authorisation and return */
gdata_authorizer_refresh_authorization (authorizer, cancellable, &error);
if (error != NULL)
g_task_return_error (task, g_steal_pointer (&error));
else
g_task_return_boolean (task, TRUE);
}
/**
* gdata_authorizer_refresh_authorization_async:
* @self: a #GDataAuthorizer
* @cancellable: (allow-none): optional #GCancellable object, or %NULL
* @callback: (allow-none) (scope async): a #GAsyncReadyCallback to call when the authorization refresh operation is finished, or %NULL
* @user_data: (closure): data to pass to the @callback function
*
* Forces the #GDataAuthorizer to refresh any authorization tokens it holds with the online service. @self and @cancellable are reffed when this
* method is called, so can safely be freed after this method returns.
*
* For more details, see gdata_authorizer_refresh_authorization(), which is the synchronous version of this method. If the #GDataAuthorizer class
* doesn't implement #GDataAuthorizerInterface.refresh_authorization_async but does implement #GDataAuthorizerInterface.refresh_authorization, the
* latter will be called from a new thread to make it asynchronous.
*
* When the authorization refresh operation is finished, @callback will be called. You can then call gdata_authorizer_refresh_authorization_finish()
* to get the results of the operation.
*
* This method is thread safe.
*
* Since: 0.9.0
*/
void
gdata_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
GDataAuthorizerInterface *iface;
g_return_if_fail (GDATA_IS_AUTHORIZER (self));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
iface = GDATA_AUTHORIZER_GET_IFACE (self);
/* Either both _async() and _finish() must be defined, or they must both be undefined. */
g_assert ((iface->refresh_authorization_async == NULL && iface->refresh_authorization_finish == NULL) ||
(iface->refresh_authorization_async != NULL && iface->refresh_authorization_finish != NULL));
if (iface->refresh_authorization_async != NULL) {
/* Call the method */
iface->refresh_authorization_async (self, cancellable, callback, user_data);
} else {
g_autoptr(GTask) task = NULL;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, gdata_authorizer_refresh_authorization_async);
if (iface->refresh_authorization != NULL) {
/* If the _async() method isn't implemented, fall back to running the sync method in a thread */
g_task_run_in_thread (task, refresh_authorization_thread);
} else {
/* If neither are implemented, immediately return FALSE with no error in a callback */
g_task_return_boolean (task, FALSE);
}
return;
}
}
/**
* gdata_authorizer_refresh_authorization_finish:
* @self: a #GDataAuthorizer
* @async_result: a #GAsyncResult
* @error: a #GError, or %NULL
*
* Finishes an asynchronous authorization refresh operation for the #GDataAuthorizer, as started with gdata_authorizer_refresh_authorization_async().
*
* This method is thread safe.
*
* Return value: %TRUE if an authorization refresh was attempted and was successful, %FALSE if a refresh wasn't attempted or was unsuccessful
*
* Since: 0.9.0
*/
gboolean
gdata_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *async_result, GError **error)
{
GDataAuthorizerInterface *iface;
g_return_val_if_fail (GDATA_IS_AUTHORIZER (self), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iface = GDATA_AUTHORIZER_GET_IFACE (self);
/* Either both _async() and _finish() must be defined, or they must both be undefined. */
g_assert ((iface->refresh_authorization_async == NULL && iface->refresh_authorization_finish == NULL) ||
(iface->refresh_authorization_async != NULL && iface->refresh_authorization_finish != NULL));
if (iface->refresh_authorization_finish != NULL) {
/* Call the method */
return iface->refresh_authorization_finish (self, async_result, error);
} else if (iface->refresh_authorization != NULL) {
/* If the _async() method isn't implemented, fall back to finishing off running the sync method in a thread */
g_return_val_if_fail (g_task_is_valid (async_result, self), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_authorizer_refresh_authorization_async), FALSE);
return g_task_propagate_boolean (G_TASK (async_result), error);
}
/* Fall back to just returning FALSE if none of the methods are implemented */
return FALSE;
}