summaryrefslogtreecommitdiff
path: root/gdata/gdata-authorizer.c
blob: fbfff76442cdba2d59bbd6b9fe25a5d5d4240aae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/* -*- 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 #GDataServices.
 * Client code will construct a new #GDataAuthorizer instance of their choosing, such as #GDataClientLoginAuthorizer or #GDataOAuth2Authorizer, for
 * the #GDataServices 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 #GDataAuthorizationDomains which are provided to the
 * authorizer at construction time. These domains specify which data domains the client expects to access using the #GDataServices 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>#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;
}