uoa: Remove #include <libaccounts-glib/ag-auth-data.h>
[platform/upstream/evolution-data-server.git] / modules / ubuntu-online-accounts / uoa-utils.c
1 /*
2  * uoa-utils.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 #include "uoa-utils.h"
20
21 #include <config.h>
22 #include <glib/gi18n-lib.h>
23 #include <rest/rest-proxy.h>
24 #include <json-glib/json-glib.h>
25 #include <libsignon-glib/signon-glib.h>
26
27 #define GOOGLE_USERINFO_URI \
28         "https://www.googleapis.com/oauth2/v2/userinfo"
29
30 typedef struct _AsyncContext AsyncContext;
31
32 struct _AsyncContext {
33         GCancellable *cancellable;
34         gchar *user_identity;
35         gchar *email_address;
36 };
37
38 static void
39 async_context_free (AsyncContext *async_context)
40 {
41         if (async_context->cancellable != NULL)
42                 g_object_unref (async_context->cancellable);
43
44         g_free (async_context->user_identity);
45         g_free (async_context->email_address);
46
47         g_slice_free (AsyncContext, async_context);
48 }
49
50 /****************************** Google Provider ******************************/
51
52 static void
53 e_ag_account_google_got_userinfo_cb (RestProxyCall *call,
54                                      const GError *error,
55                                      GObject *weak_object,
56                                      gpointer user_data)
57 {
58         GSimpleAsyncResult *simple;
59         AsyncContext *async_context;
60         JsonParser *json_parser;
61         JsonObject *json_object;
62         JsonNode *json_node;
63         const gchar *email;
64         GError *local_error = NULL;
65
66         simple = G_SIMPLE_ASYNC_RESULT (user_data);
67         async_context = g_simple_async_result_get_op_res_gpointer (simple);
68
69         if (error != NULL) {
70                 g_simple_async_result_set_from_error (simple, error);
71                 goto exit;
72         }
73
74         /* This is shamelessly stolen from goagoogleprovider.c */
75
76         if (rest_proxy_call_get_status_code (call) != 200) {
77                 g_simple_async_result_set_error (
78                         simple, G_IO_ERROR, G_IO_ERROR_FAILED,
79                         _("Expected status 200 when requesting guid, "
80                         "instead got status %d (%s)"),
81                         rest_proxy_call_get_status_code (call),
82                         rest_proxy_call_get_status_message (call));
83                 goto exit;
84         }
85
86         json_parser = json_parser_new ();
87         json_parser_load_from_data (
88                 json_parser,
89                 rest_proxy_call_get_payload (call),
90                 rest_proxy_call_get_payload_length (call),
91                 &local_error);
92
93         if (local_error != NULL) {
94                 g_prefix_error (
95                         &local_error,
96                         _("Error parsing response as JSON: "));
97                 g_simple_async_result_take_error (simple, local_error);
98                 g_object_unref (json_parser);
99                 goto exit;
100         }
101
102         json_node = json_parser_get_root (json_parser);
103         json_object = json_node_get_object (json_node);
104         email = json_object_get_string_member (json_object, "email");
105
106         if (email != NULL) {
107                 async_context->user_identity = g_strdup (email);
108                 async_context->email_address = g_strdup (email);
109         } else {
110                 g_simple_async_result_set_error (
111                         simple, G_IO_ERROR, G_IO_ERROR_FAILED,
112                         _("Didn't find email member in JSON data"));
113         }
114
115         g_object_unref (json_parser);
116
117 exit:
118         g_simple_async_result_complete (simple);
119
120         g_object_unref (simple);
121 }
122
123 static void
124 e_ag_account_google_session_process_cb (GObject *source_object,
125                                         GAsyncResult *result,
126                                         gpointer user_data)
127 {
128         GSimpleAsyncResult *simple;
129         GVariant *session_data;
130         GError *error = NULL;
131
132         simple = G_SIMPLE_ASYNC_RESULT (user_data);
133
134         session_data = signon_auth_session_process_finish (
135                 SIGNON_AUTH_SESSION (source_object), result, &error);
136
137         /* Sanity check. */
138         g_return_if_fail (
139                 ((session_data != NULL) && (error == NULL)) ||
140                 ((session_data == NULL) && (error != NULL)));
141
142         /* Use the access token to obtain the user's email address. */
143
144         if (session_data != NULL) {
145                 RestProxy *proxy;
146                 RestProxyCall *call;
147                 gchar *access_token = NULL;
148
149                 g_variant_lookup (
150                         session_data, "AccessToken", "s", &access_token);
151
152                 g_variant_unref (session_data);
153
154                 proxy = rest_proxy_new (GOOGLE_USERINFO_URI, FALSE);
155                 call = rest_proxy_new_call (proxy);
156                 rest_proxy_call_set_method (call, "GET");
157
158                 /* XXX This should never be NULL, but if it is just let
159                  *     the call fail and pick up the resulting GError. */
160                 if (access_token != NULL) {
161                         rest_proxy_call_add_param (
162                                 call, "access_token", access_token);
163                         g_free (access_token);
164                 }
165
166                 /* XXX The 3rd argument is supposed to be a GObject
167                  *     that RestProxyCall weakly references such that
168                  *     its disposal cancels the call.  This obviously
169                  *     predates GCancellable.  Too bizarre to bother. */
170                 rest_proxy_call_async (
171                         call, e_ag_account_google_got_userinfo_cb,
172                         NULL, g_object_ref (simple), &error);
173
174                 if (error != NULL) {
175                         /* Undo the reference added to the async call. */
176                         g_object_unref (simple);
177                 }
178
179                 g_object_unref (proxy);
180                 g_object_unref (call);
181         }
182
183         if (error != NULL) {
184                 g_simple_async_result_take_error (simple, error);
185                 g_simple_async_result_complete (simple);
186         }
187
188         g_object_unref (simple);
189 }
190
191 static void
192 e_ag_account_collect_google_userinfo (GSimpleAsyncResult *simple,
193                                       AgAccount *ag_account,
194                                       GCancellable *cancellable)
195 {
196         AgAccountService *ag_account_service = NULL;
197         SignonAuthSession *session;
198         AgAuthData *ag_auth_data;
199         GList *list;
200         GError *error = NULL;
201
202         /* First obtain an OAuth 2.0 access token. */
203
204         list = ag_account_list_services_by_type (ag_account, "mail");
205         if (list != NULL) {
206                 ag_account_service = ag_account_service_new (
207                         ag_account, (AgService *) list->data);
208                 ag_service_list_free (list);
209         }
210
211         g_return_if_fail (ag_account_service != NULL);
212
213         ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
214
215         session = signon_auth_session_new (
216                 ag_auth_data_get_credentials_id (ag_auth_data),
217                 ag_auth_data_get_method (ag_auth_data), &error);
218
219         /* Sanity check. */
220         g_return_if_fail (
221                 ((session != NULL) && (error == NULL)) ||
222                 ((session == NULL) && (error != NULL)));
223
224         if (session != NULL) {
225                 signon_auth_session_process_async (
226                         session,
227                         ag_auth_data_get_login_parameters (ag_auth_data, NULL),
228                         ag_auth_data_get_mechanism (ag_auth_data),
229                         cancellable,
230                         e_ag_account_google_session_process_cb,
231                         g_object_ref (simple));
232         } else {
233                 g_simple_async_result_take_error (simple, error);
234                 g_simple_async_result_complete_in_idle (simple);
235         }
236
237         ag_auth_data_unref (ag_auth_data);
238
239         g_object_unref (ag_account_service);
240         g_object_unref (simple);
241 }
242
243 /****************************** Yahoo! Provider ******************************/
244
245 static void
246 e_ag_account_collect_yahoo_userinfo (GSimpleAsyncResult *simple,
247                                      AgAccount *ag_account,
248                                      GCancellable *cancellable)
249 {
250         AsyncContext *async_context;
251         GString *email_address;
252         const gchar *display_name;
253
254         /* XXX This is a bit of a hack just to get *something* working
255          *     for Yahoo! accounts.  The proper way to obtain userinfo
256          *     for Yahoo! is through OAuth 1.0 and OpenID APIs, which
257          *     does not look trivial.  This will do for now. */
258
259         /* XXX AgAccount's display name also sort of doubles as a user
260          *     name, which may or may not be an email address.  If the
261          *     display name has no domain part, assume "@yahoo.com". */
262
263         display_name = ag_account_get_display_name (ag_account);
264         g_return_if_fail (display_name != NULL);
265
266         email_address = g_string_new (display_name);
267         if (strchr (email_address->str, '@') == NULL)
268                 g_string_append (email_address, "@yahoo.com");
269
270         async_context = g_simple_async_result_get_op_res_gpointer (simple);
271
272         async_context->user_identity = g_strdup (email_address->str);
273         async_context->email_address = g_strdup (email_address->str);
274
275         g_simple_async_result_complete_in_idle (simple);
276
277         g_object_unref (simple);
278 }
279
280 /************************ End Provider-Specific Code *************************/
281
282 void
283 e_ag_account_collect_userinfo (AgAccount *ag_account,
284                                GCancellable *cancellable,
285                                GAsyncReadyCallback callback,
286                                gpointer user_data)
287 {
288         GSimpleAsyncResult *simple;
289         AsyncContext *async_context;
290         const gchar *provider_name;
291
292         g_return_if_fail (AG_IS_ACCOUNT (ag_account));
293
294         provider_name = ag_account_get_provider_name (ag_account);
295         g_return_if_fail (provider_name != NULL);
296
297         async_context = g_slice_new0 (AsyncContext);
298
299         if (G_IS_CANCELLABLE (cancellable))
300                 async_context->cancellable = g_object_ref (cancellable);
301
302         simple = g_simple_async_result_new (
303                 G_OBJECT (ag_account), callback,
304                 user_data, e_ag_account_collect_userinfo);
305
306         g_simple_async_result_set_check_cancellable (simple, cancellable);
307
308         g_simple_async_result_set_op_res_gpointer (
309                 simple, async_context, (GDestroyNotify) async_context_free);
310
311         /* XXX This has to be done differently for each provider. */
312
313         if (g_str_equal (provider_name, "google")) {
314                 e_ag_account_collect_google_userinfo (
315                         g_object_ref (simple), ag_account, cancellable);
316         } else if (g_str_equal (provider_name, "yahoo")) {
317                 e_ag_account_collect_yahoo_userinfo (
318                         g_object_ref (simple), ag_account, cancellable);
319         } else {
320                 g_warn_if_reached ();
321                 g_simple_async_result_complete_in_idle (simple);
322         }
323
324         g_object_unref (simple);
325 }
326
327 gboolean
328 e_ag_account_collect_userinfo_finish (AgAccount *ag_account,
329                                       GAsyncResult *result,
330                                       gchar **out_user_identity,
331                                       gchar **out_email_address,
332                                       GError **error)
333 {
334         GSimpleAsyncResult *simple;
335         AsyncContext *async_context;
336
337         g_return_val_if_fail (
338                 g_simple_async_result_is_valid (
339                 result, G_OBJECT (ag_account),
340                 e_ag_account_collect_userinfo), FALSE);
341
342         simple = G_SIMPLE_ASYNC_RESULT (result);
343         async_context = g_simple_async_result_get_op_res_gpointer (simple);
344
345         if (g_simple_async_result_propagate_error (simple, error))
346                 return FALSE;
347
348         /* The result strings may be NULL without an error. */
349
350         if (out_user_identity != NULL) {
351                 *out_user_identity = async_context->user_identity;
352                 async_context->user_identity = NULL;
353         }
354
355         if (out_email_address != NULL) {
356                 *out_email_address = async_context->email_address;
357                 async_context->email_address = NULL;
358         }
359
360         return TRUE;
361 }
362