1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /**
19 * SECTION: e-oauth2-service
20 * @include: libedataserver/libedataserver.h
21 * @short_description: An interface for an OAuth2 service
22 *
23 * An interface for an OAuth2 service. Any descendant might be defined
24 * as an extension of #EOAuth2Services and it should add itself into it
25 * with e_oauth2_services_add(). To make it easier, an #EOAuth2ServiceBase
26 * is provided for convenience.
27 **/
28
29 #include "evolution-data-server-config.h"
30
31 #include <string.h>
32 #include <glib/gi18n-lib.h>
33
34 #ifdef ENABLE_OAUTH2
35 #include <json-glib/json-glib.h>
36 #endif
37
38 #include "e-secret-store.h"
39 #include "e-soup-ssl-trust.h"
40 #include "e-source-authentication.h"
41
42 #include "e-oauth2-service.h"
43
G_DEFINE_INTERFACE(EOAuth2Service,e_oauth2_service,G_TYPE_OBJECT)44 G_DEFINE_INTERFACE (EOAuth2Service, e_oauth2_service, G_TYPE_OBJECT)
45
46 static gboolean
47 eos_default_can_process (EOAuth2Service *service,
48 ESource *source)
49 {
50 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
51
52 if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
53 ESourceAuthentication *auth_extension;
54 gchar *method;
55
56 auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
57 if (e_source_authentication_get_is_external (auth_extension))
58 return FALSE;
59
60 method = e_source_authentication_dup_method (auth_extension);
61
62 if (g_strcmp0 (method, e_oauth2_service_get_name (service)) == 0) {
63 g_free (method);
64 return TRUE;
65 }
66
67 g_free (method);
68 }
69
70 return FALSE;
71 }
72
73 static gboolean
eos_default_guess_can_process(EOAuth2Service * service,const gchar * protocol,const gchar * hostname)74 eos_default_guess_can_process (EOAuth2Service *service,
75 const gchar *protocol,
76 const gchar *hostname)
77 {
78 gboolean can = FALSE;
79 GSettings *settings;
80 gchar **values;
81 gint ii, name_len, hostname_len;
82 const gchar *name;
83
84 if (!hostname || !*hostname)
85 return FALSE;
86
87 name = e_oauth2_service_get_name (service);
88 g_return_val_if_fail (name != NULL, FALSE);
89 name_len = strlen (name);
90 hostname_len = strlen (hostname);
91
92 settings = g_settings_new ("org.gnome.evolution-data-server");
93 values = g_settings_get_strv (settings, "oauth2-services-hint");
94 g_object_unref (settings);
95
96 for (ii = 0; !can && values && values[ii]; ii++) {
97 const gchar *line = values[ii];
98 gint len;
99
100 if (!g_str_has_prefix (line, name) ||
101 (line[name_len] != ':' && line[name_len] != '-'))
102 continue;
103
104 if (line[name_len] == '-') {
105 len = protocol ? strlen (protocol) : -1;
106
107 if (len <= 0 || g_ascii_strncasecmp (line + name_len + 1, protocol, len) != 0 ||
108 line[name_len + len + 1] != ':')
109 continue;
110
111 line += name_len + len + 2;
112 } else { /* line[name_len] == ':' */
113 line += name_len + 1;
114 }
115
116 while (line && *line) {
117 if (g_ascii_strncasecmp (line, hostname, hostname_len) == 0 &&
118 (line[hostname_len] == ',' || line[hostname_len] == '\0')) {
119 can = TRUE;
120 break;
121 }
122
123 line = strchr (line, ',');
124 if (line)
125 line++;
126 }
127 }
128
129 g_strfreev (values);
130
131 return can;
132 }
133
134 static guint32
eos_default_get_flags(EOAuth2Service * service)135 eos_default_get_flags (EOAuth2Service *service)
136 {
137 return E_OAUTH2_SERVICE_FLAG_NONE;
138 }
139
140 static const gchar *
eos_default_get_redirect_uri(EOAuth2Service * service,ESource * source)141 eos_default_get_redirect_uri (EOAuth2Service *service,
142 ESource *source)
143 {
144 return "urn:ietf:wg:oauth:2.0:oob";
145 }
146
147 static void
eos_default_prepare_authentication_uri_query(EOAuth2Service * service,ESource * source,GHashTable * uri_query)148 eos_default_prepare_authentication_uri_query (EOAuth2Service *service,
149 ESource *source,
150 GHashTable *uri_query)
151 {
152 e_oauth2_service_util_set_to_form (uri_query, "response_type", "code");
153 e_oauth2_service_util_set_to_form (uri_query, "client_id", e_oauth2_service_get_client_id (service, source));
154 e_oauth2_service_util_set_to_form (uri_query, "redirect_uri", e_oauth2_service_get_redirect_uri (service, source));
155
156 if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
157 ESourceAuthentication *auth_extension;
158 gchar *user;
159
160 auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
161 user = e_source_authentication_dup_user (auth_extension);
162
163 if (user && *user)
164 e_oauth2_service_util_take_to_form (uri_query, "login_hint", user);
165 else
166 g_free (user);
167 }
168 }
169
170 static EOAuth2ServiceNavigationPolicy
eos_default_get_authentication_policy(EOAuth2Service * service,ESource * source,const gchar * uri)171 eos_default_get_authentication_policy (EOAuth2Service *service,
172 ESource *source,
173 const gchar *uri)
174 {
175 return E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW;
176 }
177
178 static void
eos_default_prepare_get_token_form(EOAuth2Service * service,ESource * source,const gchar * authorization_code,GHashTable * form)179 eos_default_prepare_get_token_form (EOAuth2Service *service,
180 ESource *source,
181 const gchar *authorization_code,
182 GHashTable *form)
183 {
184 e_oauth2_service_util_set_to_form (form, "code", authorization_code);
185 e_oauth2_service_util_set_to_form (form, "client_id", e_oauth2_service_get_client_id (service, source));
186 e_oauth2_service_util_set_to_form (form, "client_secret", e_oauth2_service_get_client_secret (service, source));
187 e_oauth2_service_util_set_to_form (form, "redirect_uri", e_oauth2_service_get_redirect_uri (service, source));
188 e_oauth2_service_util_set_to_form (form, "grant_type", "authorization_code");
189 }
190
191 static void
eos_default_prepare_get_token_message(EOAuth2Service * service,ESource * source,SoupMessage * message)192 eos_default_prepare_get_token_message (EOAuth2Service *service,
193 ESource *source,
194 SoupMessage *message)
195 {
196 }
197
198 static void
eos_default_prepare_refresh_token_form(EOAuth2Service * service,ESource * source,const gchar * refresh_token,GHashTable * form)199 eos_default_prepare_refresh_token_form (EOAuth2Service *service,
200 ESource *source,
201 const gchar *refresh_token,
202 GHashTable *form)
203 {
204 e_oauth2_service_util_set_to_form (form, "refresh_token", refresh_token);
205 e_oauth2_service_util_set_to_form (form, "client_id", e_oauth2_service_get_client_id (service, source));
206 e_oauth2_service_util_set_to_form (form, "client_secret", e_oauth2_service_get_client_secret (service, source));
207 e_oauth2_service_util_set_to_form (form, "grant_type", "refresh_token");
208 }
209
210 static void
eos_default_prepare_refresh_token_message(EOAuth2Service * service,ESource * source,SoupMessage * message)211 eos_default_prepare_refresh_token_message (EOAuth2Service *service,
212 ESource *source,
213 SoupMessage *message)
214 {
215 }
216
217 static void
e_oauth2_service_default_init(EOAuth2ServiceInterface * iface)218 e_oauth2_service_default_init (EOAuth2ServiceInterface *iface)
219 {
220 iface->can_process = eos_default_can_process;
221 iface->guess_can_process = eos_default_guess_can_process;
222 iface->get_flags = eos_default_get_flags;
223 iface->get_redirect_uri = eos_default_get_redirect_uri;
224 iface->prepare_authentication_uri_query = eos_default_prepare_authentication_uri_query;
225 iface->get_authentication_policy = eos_default_get_authentication_policy;
226 iface->prepare_get_token_form = eos_default_prepare_get_token_form;
227 iface->prepare_get_token_message = eos_default_prepare_get_token_message;
228 iface->prepare_refresh_token_form = eos_default_prepare_refresh_token_form;
229 iface->prepare_refresh_token_message = eos_default_prepare_refresh_token_message;
230 }
231
232 /**
233 * e_oauth2_service_can_process:
234 * @service: an #EOAuth2Service
235 * @source: an #ESource
236 *
237 * Checks whether the @service can be used with the given @source.
238 *
239 * The default implementation checks whether the @source has an #ESourceAuthentication
240 * extension and when its method matches e_oauth2_service_get_name(), then it automatically
241 * returns %TRUE. Contrary, when the @source contains GNOME Online Accounts or Ubuntu
242 * Online Accounts extension, then it returns %FALSE.
243 *
244 * The default implementation is tried always as the first and when it fails, then
245 * the descendant's implementation is called.
246 *
247 * Returns: Whether the @service can be used for the given @source
248 *
249 * Since: 3.28
250 **/
251 gboolean
e_oauth2_service_can_process(EOAuth2Service * service,ESource * source)252 e_oauth2_service_can_process (EOAuth2Service *service,
253 ESource *source)
254 {
255 EOAuth2ServiceInterface *iface;
256
257 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
258 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
259
260 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
261 g_return_val_if_fail (iface != NULL, FALSE);
262 g_return_val_if_fail (iface->can_process != NULL, FALSE);
263
264 if (eos_default_can_process (service, source))
265 return TRUE;
266
267 return iface->can_process != eos_default_can_process &&
268 iface->can_process (service, source);
269 }
270
271 /**
272 * e_oauth2_service_guess_can_process:
273 * @service: an #EOAuth2Service
274 * @protocol: (nullable): a protocol to search the service for, like "imap", or %NULL
275 * @hostname: (nullable): a host name to search the service for, like "server.example.com", or %NULL
276 *
277 * Checks whether the @service can be used with the given @protocol and/or @hostname.
278 * Any of @protocol and @hostname can be %NULL, but not both. It's up to each implementer
279 * to decide, which of the arguments are important and whether all or only any of them
280 * can be required.
281 *
282 * The function is meant to check whether the @service can be offered
283 * for example when configuring a new account. The real usage is
284 * determined by e_oauth2_service_can_process().
285 *
286 * The default implementation consults org.gnome.evolution-data-server.oauth2-services-hint
287 * GSettings key against given hostname. See its description for more information.
288 *
289 * The default implementation is tried always as the first and when it fails, then
290 * the descendant's implementation is called.
291 *
292 * Returns: Whether the @service can be used for the given arguments
293 *
294 * Since: 3.28
295 **/
296 gboolean
e_oauth2_service_guess_can_process(EOAuth2Service * service,const gchar * protocol,const gchar * hostname)297 e_oauth2_service_guess_can_process (EOAuth2Service *service,
298 const gchar *protocol,
299 const gchar *hostname)
300 {
301 EOAuth2ServiceInterface *iface;
302
303 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
304 g_return_val_if_fail (protocol || hostname, FALSE);
305
306 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
307 g_return_val_if_fail (iface != NULL, FALSE);
308 g_return_val_if_fail (iface->guess_can_process != NULL, FALSE);
309
310 if (eos_default_guess_can_process (service, protocol, hostname))
311 return TRUE;
312
313 return iface->guess_can_process != eos_default_guess_can_process &&
314 iface->guess_can_process (service, protocol, hostname);
315 }
316
317 /**
318 * e_oauth2_service_get_flags:
319 * @service: an #EOAuth2Service
320 *
321 * Returns: bit-or of #EOAuth2ServiceFlags for the @service. The default
322 * implementation returns %E_OAUTH2_SERVICE_FLAG_NONE.
323 *
324 * Since: 3.28
325 **/
326 guint32
e_oauth2_service_get_flags(EOAuth2Service * service)327 e_oauth2_service_get_flags (EOAuth2Service *service)
328 {
329 EOAuth2ServiceInterface *iface;
330
331 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), E_OAUTH2_SERVICE_FLAG_NONE);
332
333 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
334 g_return_val_if_fail (iface != NULL, E_OAUTH2_SERVICE_FLAG_NONE);
335 g_return_val_if_fail (iface->get_flags != NULL, E_OAUTH2_SERVICE_FLAG_NONE);
336
337 return iface->get_flags (service);
338 }
339
340 /**
341 * e_oauth2_service_get_name:
342 * @service: an #EOAuth2Service
343 *
344 * Returns a unique name of the service. It can be named for example
345 * by the server or the company from which it receives the OAuth2
346 * token and where it refreshes it, like "Company" for login.company.com.
347 *
348 * Returns: the name of the @service
349 *
350 * Since: 3.28
351 **/
352 const gchar *
e_oauth2_service_get_name(EOAuth2Service * service)353 e_oauth2_service_get_name (EOAuth2Service *service)
354 {
355 EOAuth2ServiceInterface *iface;
356
357 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
358
359 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
360 g_return_val_if_fail (iface != NULL, NULL);
361 g_return_val_if_fail (iface->get_name != NULL, NULL);
362
363 return iface->get_name (service);
364 }
365
366 /**
367 * e_oauth2_service_get_display_name:
368 * @service: an #EOAuth2Service
369 *
370 * Returns a human readable name of the service. This is similar to
371 * e_oauth2_service_get_name(), except this string should be localized,
372 * because it will be used in user-visible strings.
373 *
374 * Returns: the display name of the @service
375 *
376 * Since: 3.28
377 **/
378 const gchar *
e_oauth2_service_get_display_name(EOAuth2Service * service)379 e_oauth2_service_get_display_name (EOAuth2Service *service)
380 {
381 EOAuth2ServiceInterface *iface;
382
383 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
384
385 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
386 g_return_val_if_fail (iface != NULL, NULL);
387 g_return_val_if_fail (iface->get_display_name != NULL, NULL);
388
389 return iface->get_display_name (service);
390 }
391
392 /**
393 * e_oauth2_service_get_client_id:
394 * @service: an #EOAuth2Service
395 * @source: an associated #ESource
396 *
397 * Returns: application client ID, as provided by the server
398 *
399 * Since: 3.28
400 **/
401 const gchar *
e_oauth2_service_get_client_id(EOAuth2Service * service,ESource * source)402 e_oauth2_service_get_client_id (EOAuth2Service *service,
403 ESource *source)
404 {
405 EOAuth2ServiceInterface *iface;
406
407 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
408 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
409
410 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
411 g_return_val_if_fail (iface != NULL, NULL);
412 g_return_val_if_fail (iface->get_client_id != NULL, NULL);
413
414 return iface->get_client_id (service, source);
415 }
416
417 /**
418 * e_oauth2_service_get_client_secret:
419 * @service: an #EOAuth2Service
420 * @source: an associated #ESource
421 *
422 * Returns: (nullable): application client secret, as provided by the server, or %NULL
423 *
424 * Since: 3.28
425 **/
426 const gchar *
e_oauth2_service_get_client_secret(EOAuth2Service * service,ESource * source)427 e_oauth2_service_get_client_secret (EOAuth2Service *service,
428 ESource *source)
429 {
430 EOAuth2ServiceInterface *iface;
431
432 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
433 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
434
435 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
436 g_return_val_if_fail (iface != NULL, NULL);
437 g_return_val_if_fail (iface->get_client_secret != NULL, NULL);
438
439 return iface->get_client_secret (service, source);
440 }
441
442 /**
443 * e_oauth2_service_get_authentication_uri:
444 * @service: an #EOAuth2Service
445 * @source: an associated #ESource
446 *
447 * Returns: an authentication URI, to be used to obtain
448 * the authentication code
449 *
450 * Since: 3.28
451 **/
452 const gchar *
e_oauth2_service_get_authentication_uri(EOAuth2Service * service,ESource * source)453 e_oauth2_service_get_authentication_uri (EOAuth2Service *service,
454 ESource *source)
455 {
456 EOAuth2ServiceInterface *iface;
457
458 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
459 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
460
461 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
462 g_return_val_if_fail (iface != NULL, NULL);
463 g_return_val_if_fail (iface->get_authentication_uri != NULL, NULL);
464
465 return iface->get_authentication_uri (service, source);
466 }
467
468 /**
469 * e_oauth2_service_get_refresh_uri:
470 * @service: an #EOAuth2Service
471 * @source: an associated #ESource
472 *
473 * Returns: a URI to be used to refresh the authentication token
474 *
475 * Since: 3.28
476 **/
477 const gchar *
e_oauth2_service_get_refresh_uri(EOAuth2Service * service,ESource * source)478 e_oauth2_service_get_refresh_uri (EOAuth2Service *service,
479 ESource *source)
480 {
481 EOAuth2ServiceInterface *iface;
482
483 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
484 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
485
486 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
487 g_return_val_if_fail (iface != NULL, NULL);
488 g_return_val_if_fail (iface->get_refresh_uri != NULL, NULL);
489
490 return iface->get_refresh_uri (service, source);
491 }
492
493 /**
494 * e_oauth2_service_get_redirect_uri:
495 * @service: an #EOAuth2Service
496 * @source: an associated #ESource
497 *
498 * Returns a value for the "redirect_uri" keys in the authenticate and get_token
499 * operations. The default implementation returns "urn:ietf:wg:oauth:2.0:oob".
500 *
501 * Returns: (nullable): The redirect_uri to use, or %NULL for none
502 *
503 * Since: 3.28
504 **/
505 const gchar *
e_oauth2_service_get_redirect_uri(EOAuth2Service * service,ESource * source)506 e_oauth2_service_get_redirect_uri (EOAuth2Service *service,
507 ESource *source)
508 {
509 EOAuth2ServiceInterface *iface;
510
511 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL);
512 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
513
514 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
515 g_return_val_if_fail (iface != NULL, NULL);
516 g_return_val_if_fail (iface->get_redirect_uri != NULL, NULL);
517
518 return iface->get_redirect_uri (service, source);
519 }
520
521 /**
522 * e_oauth2_service_prepare_authentication_uri_query:
523 * @service: an #EOAuth2Service
524 * @source: an associated #ESource
525 * @uri_query: (element-type utf8 utf8): query for the URI to use
526 *
527 * The @service can change what arguments are passed in the authentication URI
528 * in this method. The default implementation sets some values too, namely
529 * "response_type", "client_id", "redirect_uri" and "login_hint", if available
530 * in the @source. These parameters are always provided, even when the interface
531 * implementer overrides this method.
532 *
533 * The @uri_query hash table expects both key and value to be newly allocated
534 * strings, which will be freed together with the hash table or when the key
535 * is replaced.
536 *
537 * Since: 3.28
538 **/
539 void
e_oauth2_service_prepare_authentication_uri_query(EOAuth2Service * service,ESource * source,GHashTable * uri_query)540 e_oauth2_service_prepare_authentication_uri_query (EOAuth2Service *service,
541 ESource *source,
542 GHashTable *uri_query)
543 {
544 EOAuth2ServiceInterface *iface;
545
546 g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
547 g_return_if_fail (E_IS_SOURCE (source));
548 g_return_if_fail (uri_query != NULL);
549
550 eos_default_prepare_authentication_uri_query (service, source, uri_query);
551
552 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
553 g_return_if_fail (iface != NULL);
554 g_return_if_fail (iface->prepare_authentication_uri_query != NULL);
555
556 if (iface->prepare_authentication_uri_query != eos_default_prepare_authentication_uri_query)
557 iface->prepare_authentication_uri_query (service, source, uri_query);
558 }
559
560 /**
561 * e_oauth2_service_get_authentication_policy:
562 * @service: an #EOAuth2Service
563 * @source: an associated #ESource
564 * @uri: a URI of the navigation resource
565 *
566 * Used to decide what to do when the server redirects to the next page.
567 * The default implementation always returns %E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW.
568 *
569 * This method is called before e_oauth2_service_extract_authorization_code() and
570 * can be used to block certain resources or to abort the authentication when
571 * the server redirects to an unexpected page (like when user denies authorization
572 * in the page).
573 *
574 * Returns: one of #EOAuth2ServiceNavigationPolicy
575 *
576 * Since: 3.28
577 **/
578 EOAuth2ServiceNavigationPolicy
e_oauth2_service_get_authentication_policy(EOAuth2Service * service,ESource * source,const gchar * uri)579 e_oauth2_service_get_authentication_policy (EOAuth2Service *service,
580 ESource *source,
581 const gchar *uri)
582 {
583 EOAuth2ServiceInterface *iface;
584
585 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
586 g_return_val_if_fail (E_IS_SOURCE (source), E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
587 g_return_val_if_fail (uri != NULL, E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
588
589 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
590 g_return_val_if_fail (iface != NULL, E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
591 g_return_val_if_fail (iface->get_authentication_policy != NULL, E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
592
593 return iface->get_authentication_policy (service, source, uri);
594 }
595
596 /**
597 * e_oauth2_service_extract_authorization_code:
598 * @service: an #EOAuth2Service
599 * @source: an associated #ESource
600 * @page_title: a web page title
601 * @page_uri: a web page URI
602 * @page_content: (nullable): a web page content
603 * @out_authorization_code: (out) (transfer full): the extracted authorization code
604 *
605 * Tries to extract an authorization code from a web page provided by the server.
606 * The function can be called multiple times, whenever the page load is finished.
607 *
608 * There can happen three states: 1) either the @service cannot determine
609 * the authentication code from the page information, then the %FALSE is
610 * returned and the @out_authorization_code is left untouched; or 2) the server
611 * reported a failure, in which case the function returns %TRUE and lefts
612 * the @out_authorization_code untouched; or 3) the @service could extract
613 * the authentication code from the given arguments, then the function
614 * returns %TRUE and sets the received authorization code to @out_authorization_code.
615 *
616 * The @page_content is %NULL, unless flags returned by e_oauth2_service_get_flags()
617 * contain also %E_OAUTH2_SERVICE_FLAG_EXTRACT_REQUIRES_PAGE_CONTENT.
618 *
619 * This method is always called after e_oauth2_service_get_authentication_policy().
620 *
621 * Returns: whether could recognized successful or failed server response.
622 * The @out_authorization_code is populated on success too.
623 *
624 * Since: 3.28
625 **/
626 gboolean
e_oauth2_service_extract_authorization_code(EOAuth2Service * service,ESource * source,const gchar * page_title,const gchar * page_uri,const gchar * page_content,gchar ** out_authorization_code)627 e_oauth2_service_extract_authorization_code (EOAuth2Service *service,
628 ESource *source,
629 const gchar *page_title,
630 const gchar *page_uri,
631 const gchar *page_content,
632 gchar **out_authorization_code)
633 {
634 EOAuth2ServiceInterface *iface;
635
636 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
637 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
638
639 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
640 g_return_val_if_fail (iface != NULL, FALSE);
641 g_return_val_if_fail (iface->extract_authorization_code != NULL, FALSE);
642
643 return iface->extract_authorization_code (service, source, page_title, page_uri, page_content, out_authorization_code);
644 }
645
646 /**
647 * e_oauth2_service_prepare_get_token_form:
648 * @service: an #EOAuth2Service
649 * @source: an associated #ESource
650 * @authorization_code: authorization code, as returned from e_oauth2_service_extract_authorization_code()
651 * @form: (element-type utf8 utf8): form parameters to be used in the POST request
652 *
653 * Sets additional form parameters to be used in the POST request when requesting
654 * access token after successfully obtained authorization code.
655 * The default implementation sets some values too, namely
656 * "code", "client_id", "client_secret", "redirect_uri" and "grant_type".
657 * These parameters are always provided, even when the interface implementer overrides this method.
658 *
659 * The @form hash table expects both key and value to be newly allocated
660 * strings, which will be freed together with the hash table or when the key
661 * is replaced.
662 *
663 * Since: 3.28
664 **/
665 void
e_oauth2_service_prepare_get_token_form(EOAuth2Service * service,ESource * source,const gchar * authorization_code,GHashTable * form)666 e_oauth2_service_prepare_get_token_form (EOAuth2Service *service,
667 ESource *source,
668 const gchar *authorization_code,
669 GHashTable *form)
670 {
671 EOAuth2ServiceInterface *iface;
672
673 g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
674 g_return_if_fail (E_IS_SOURCE (source));
675 g_return_if_fail (authorization_code != NULL);
676 g_return_if_fail (form != NULL);
677
678 eos_default_prepare_get_token_form (service, source, authorization_code, form);
679
680 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
681 g_return_if_fail (iface != NULL);
682 g_return_if_fail (iface->prepare_get_token_form != NULL);
683
684 if (iface->prepare_get_token_form != eos_default_prepare_get_token_form)
685 iface->prepare_get_token_form (service, source, authorization_code, form);
686 }
687
688 /**
689 * e_oauth2_service_prepare_get_token_message:
690 * @service: an #EOAuth2Service
691 * @source: an associated #ESource
692 * @message: a #SoupMessage
693 *
694 * The @service can change the @message before it's sent to
695 * the e_oauth2_service_get_authentication_uri(), with POST data
696 * being provided by e_oauth2_service_prepare_get_token_form().
697 * The default implementation does nothing with the @message.
698 *
699 * Since: 3.28
700 **/
701 void
e_oauth2_service_prepare_get_token_message(EOAuth2Service * service,ESource * source,SoupMessage * message)702 e_oauth2_service_prepare_get_token_message (EOAuth2Service *service,
703 ESource *source,
704 SoupMessage *message)
705 {
706 EOAuth2ServiceInterface *iface;
707
708 g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
709 g_return_if_fail (E_IS_SOURCE (source));
710 g_return_if_fail (SOUP_IS_MESSAGE (message));
711
712 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
713 g_return_if_fail (iface != NULL);
714 g_return_if_fail (iface->prepare_get_token_message != NULL);
715
716 iface->prepare_get_token_message (service, source, message);
717 }
718
719 /**
720 * e_oauth2_service_prepare_refresh_token_form:
721 * @service: an #EOAuth2Service
722 * @source: an associated #ESource
723 * @refresh_token: a refresh token to be used
724 * @form: (element-type utf8 utf8): form parameters to be used in the POST request
725 *
726 * Sets additional form parameters to be used in the POST request when requesting
727 * to refresh an access token.
728 * The default implementation sets some values too, namely
729 * "refresh_token", "client_id", "client_secret" and "grant_type".
730 * These parameters are always provided, even when the interface implementer overrides this method.
731 *
732 * The @form hash table expects both key and value to be newly allocated
733 * strings, which will be freed together with the hash table or when the key
734 * is replaced.
735 *
736 * Since: 3.28
737 **/
738 void
e_oauth2_service_prepare_refresh_token_form(EOAuth2Service * service,ESource * source,const gchar * refresh_token,GHashTable * form)739 e_oauth2_service_prepare_refresh_token_form (EOAuth2Service *service,
740 ESource *source,
741 const gchar *refresh_token,
742 GHashTable *form)
743 {
744 EOAuth2ServiceInterface *iface;
745
746 g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
747 g_return_if_fail (E_IS_SOURCE (source));
748 g_return_if_fail (refresh_token != NULL);
749 g_return_if_fail (form != NULL);
750
751 eos_default_prepare_refresh_token_form (service, source, refresh_token, form);
752
753 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
754 g_return_if_fail (iface != NULL);
755 g_return_if_fail (iface->prepare_refresh_token_form != NULL);
756
757 if (iface->prepare_refresh_token_form != eos_default_prepare_refresh_token_form)
758 iface->prepare_refresh_token_form (service, source, refresh_token, form);
759 }
760
761 /**
762 * e_oauth2_service_prepare_refresh_token_message:
763 * @service: an #EOAuth2Service
764 * @source: an associated #ESource
765 * @message: a #SoupMessage
766 *
767 * The @service can change the @message before it's sent to
768 * the e_oauth2_service_get_refresh_uri(), with POST data
769 * being provided by e_oauth2_service_prepare_refresh_token_form().
770 * The default implementation does nothing with the @message.
771 *
772 * Since: 3.28
773 **/
774 void
e_oauth2_service_prepare_refresh_token_message(EOAuth2Service * service,ESource * source,SoupMessage * message)775 e_oauth2_service_prepare_refresh_token_message (EOAuth2Service *service,
776 ESource *source,
777 SoupMessage *message)
778 {
779 EOAuth2ServiceInterface *iface;
780
781 g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
782 g_return_if_fail (E_IS_SOURCE (source));
783 g_return_if_fail (SOUP_IS_MESSAGE (message));
784
785 iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
786 g_return_if_fail (iface != NULL);
787 g_return_if_fail (iface->prepare_refresh_token_message != NULL);
788
789 iface->prepare_refresh_token_message (service, source, message);
790 }
791
792 static SoupSession *
eos_create_soup_session(EOAuth2ServiceRefSourceFunc ref_source,gpointer ref_source_user_data,ESource * source)793 eos_create_soup_session (EOAuth2ServiceRefSourceFunc ref_source,
794 gpointer ref_source_user_data,
795 ESource *source)
796 {
797 static gint oauth2_debug = -1;
798 ESourceAuthentication *auth_extension;
799 ESource *proxy_source = NULL;
800 SoupSession *session;
801 gchar *uid;
802
803 if (oauth2_debug == -1)
804 oauth2_debug = g_strcmp0 (g_getenv ("OAUTH2_DEBUG"), "1") == 0 ? 1 : 0;
805
806 session = soup_session_new ();
807 g_object_set (
808 session,
809 SOUP_SESSION_TIMEOUT, 90,
810 SOUP_SESSION_SSL_STRICT, TRUE,
811 SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
812 SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
813 NULL);
814
815 if (oauth2_debug) {
816 SoupLogger *logger;
817
818 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
819 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
820 g_object_unref (logger);
821 }
822
823 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION))
824 return session;
825
826 auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
827 uid = e_source_authentication_dup_proxy_uid (auth_extension);
828 if (uid) {
829 proxy_source = ref_source (ref_source_user_data, uid);
830
831 g_free (uid);
832 }
833
834 if (proxy_source) {
835 GProxyResolver *proxy_resolver;
836
837 proxy_resolver = G_PROXY_RESOLVER (proxy_source);
838 if (g_proxy_resolver_is_supported (proxy_resolver))
839 g_object_set (session, SOUP_SESSION_PROXY_RESOLVER, proxy_resolver, NULL);
840
841 g_object_unref (proxy_source);
842 }
843
844 return session;
845 }
846
847 static SoupMessage *
eos_create_soup_message(ESource * source,const gchar * uri,GHashTable * post_form)848 eos_create_soup_message (ESource *source,
849 const gchar *uri,
850 GHashTable *post_form)
851 {
852 SoupMessage *message;
853 gchar *post_data;
854
855 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
856 g_return_val_if_fail (uri != NULL, NULL);
857 g_return_val_if_fail (post_form != NULL, NULL);
858
859 message = soup_message_new (SOUP_METHOD_POST, uri);
860 g_return_val_if_fail (message != NULL, NULL);
861
862 post_data = soup_form_encode_hash (post_form);
863 if (!post_data) {
864 g_warn_if_fail (post_data != NULL);
865 g_object_unref (message);
866
867 return NULL;
868 }
869
870 soup_message_set_request (message, "application/x-www-form-urlencoded",
871 SOUP_MEMORY_TAKE, post_data, strlen (post_data));
872
873 e_soup_ssl_trust_connect (message, source);
874
875 soup_message_headers_append (message->request_headers, "Connection", "close");
876
877 return message;
878 }
879
880 static void
eos_abort_session_cb(GCancellable * cancellable,SoupSession * session)881 eos_abort_session_cb (GCancellable *cancellable,
882 SoupSession *session)
883 {
884 soup_session_abort (session);
885 }
886
887 static gboolean
eos_send_message(SoupSession * session,SoupMessage * message,gchar ** out_response_body,GCancellable * cancellable,GError ** error)888 eos_send_message (SoupSession *session,
889 SoupMessage *message,
890 gchar **out_response_body,
891 GCancellable *cancellable,
892 GError **error)
893 {
894 guint status_code = SOUP_STATUS_CANCELLED;
895 gboolean success = FALSE;
896
897 g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
898 g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
899 g_return_val_if_fail (out_response_body != NULL, FALSE);
900
901 if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
902 gulong cancel_handler_id = 0;
903
904 if (cancellable)
905 cancel_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (eos_abort_session_cb), session, NULL);
906
907 status_code = soup_session_send_message (session, message);
908
909 if (cancel_handler_id)
910 g_cancellable_disconnect (cancellable, cancel_handler_id);
911 }
912
913 if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
914 if (message->response_body) {
915 *out_response_body = g_strndup (message->response_body->data, message->response_body->length);
916 success = TRUE;
917 } else {
918 status_code = SOUP_STATUS_MALFORMED;
919 g_set_error_literal (error, SOUP_HTTP_ERROR, status_code, _("Malformed, no message body set"));
920 }
921 } else if (status_code != SOUP_STATUS_CANCELLED) {
922 GString *error_msg;
923
924 error_msg = g_string_new (message->reason_phrase);
925 if (message->response_body && message->response_body->length) {
926 g_string_append (error_msg, " (");
927 g_string_append_len (error_msg, message->response_body->data, message->response_body->length);
928 g_string_append_c (error_msg, ')');
929 }
930
931 g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code, error_msg->str);
932
933 g_string_free (error_msg, TRUE);
934 }
935
936 return success;
937 }
938
939 static gboolean
eos_generate_secret_uid(EOAuth2Service * service,ESource * source,gchar ** out_uid)940 eos_generate_secret_uid (EOAuth2Service *service,
941 ESource *source,
942 gchar **out_uid)
943 {
944 ESourceAuthentication *authentication_extension;
945 gchar *user;
946
947 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
948 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
949
950 if (out_uid)
951 *out_uid = NULL;
952
953 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION))
954 return FALSE;
955
956 authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
957 user = e_source_authentication_dup_user (authentication_extension);
958 if (!user || !*user) {
959 g_free (user);
960 return FALSE;
961 }
962
963 if (out_uid)
964 *out_uid = g_strdup_printf ("OAuth2::%s[%s]", e_oauth2_service_get_name (service), user);
965
966 g_free (user);
967
968 return TRUE;
969 }
970
971 static gboolean
972 eos_encode_to_secret (gchar **out_secret,
973 const gchar *key1_name,
974 const gchar *value1,
975 ...) G_GNUC_NULL_TERMINATED;
976
977 static gboolean
eos_encode_to_secret(gchar ** out_secret,const gchar * key1_name,const gchar * value1,...)978 eos_encode_to_secret (gchar **out_secret,
979 const gchar *key1_name,
980 const gchar *value1,
981 ...)
982 {
983 #ifdef ENABLE_OAUTH2
984 JsonBuilder *builder;
985 JsonNode *node;
986 const gchar *key, *value;
987 va_list va;
988
989 g_return_val_if_fail (out_secret != NULL, FALSE);
990 g_return_val_if_fail (key1_name != NULL, FALSE);
991 g_return_val_if_fail (value1 != NULL, FALSE);
992
993 *out_secret = NULL;
994
995 builder = json_builder_new ();
996
997 va_start (va, value1);
998 key = key1_name;
999 value = value1;
1000
1001 json_builder_begin_object (builder);
1002
1003 while (key && value) {
1004 json_builder_set_member_name (builder, key);
1005 json_builder_add_string_value (builder, value);
1006
1007 key = va_arg (va, const gchar *);
1008 if (!key)
1009 break;
1010
1011 value = va_arg (va, const gchar *);
1012 g_warn_if_fail (value != NULL);
1013 }
1014
1015 va_end (va);
1016
1017 json_builder_end_object (builder);
1018 node = json_builder_get_root (builder);
1019
1020 g_object_unref (builder);
1021
1022 if (node) {
1023 JsonGenerator *generator;
1024
1025 generator = json_generator_new ();
1026 json_generator_set_root (generator, node);
1027
1028 *out_secret = json_generator_to_data (generator, NULL);
1029
1030 g_object_unref (generator);
1031 json_node_free (node);
1032 }
1033
1034 return *out_secret != NULL;
1035 #else
1036 return FALSE;
1037 #endif
1038 }
1039
1040 static gboolean
1041 eos_decode_from_secret (const gchar *secret,
1042 const gchar *key1_name,
1043 gchar **out_value1,
1044 ...) G_GNUC_NULL_TERMINATED;
1045
1046 static gboolean
eos_decode_from_secret(const gchar * secret,const gchar * key1_name,gchar ** out_value1,...)1047 eos_decode_from_secret (const gchar *secret,
1048 const gchar *key1_name,
1049 gchar **out_value1,
1050 ...)
1051 {
1052 #ifdef ENABLE_OAUTH2
1053 JsonParser *parser;
1054 JsonReader *reader;
1055 const gchar *key;
1056 gchar **out_value;
1057 va_list va;
1058 GError *error = NULL;
1059
1060 g_return_val_if_fail (key1_name != NULL, FALSE);
1061 g_return_val_if_fail (out_value1 != NULL, FALSE);
1062
1063 if (!secret || !*secret)
1064 return FALSE;
1065
1066 parser = json_parser_new ();
1067 if (!json_parser_load_from_data (parser, secret, -1, &error)) {
1068 g_object_unref (parser);
1069
1070 g_debug ("%s: Failed to parse secret '%s': %s", G_STRFUNC, secret, error ? error->message : "Unknown error");
1071 g_clear_error (&error);
1072
1073 return FALSE;
1074 }
1075
1076 reader = json_reader_new (json_parser_get_root (parser));
1077 key = key1_name;
1078 out_value = out_value1;
1079
1080 va_start (va, out_value1);
1081
1082 while (key && out_value) {
1083 *out_value = NULL;
1084
1085 if (json_reader_read_member (reader, key)) {
1086 *out_value = g_strdup (json_reader_get_string_value (reader));
1087 if (!*out_value) {
1088 const GError *reader_error = json_reader_get_error (reader);
1089
1090 if (g_error_matches (reader_error, JSON_READER_ERROR, JSON_READER_ERROR_INVALID_TYPE)) {
1091 gint64 iv64;
1092
1093 json_reader_end_member (reader);
1094
1095 iv64 = json_reader_get_int_value (reader);
1096
1097 if (!json_reader_get_error (reader))
1098 *out_value = g_strdup_printf ("%" G_GINT64_FORMAT, iv64);
1099 }
1100 }
1101
1102 if (*out_value && !**out_value) {
1103 g_free (*out_value);
1104 *out_value = NULL;
1105 }
1106 }
1107
1108 json_reader_end_member (reader);
1109
1110 key = va_arg (va, const gchar *);
1111 if (!key)
1112 break;
1113
1114 out_value = va_arg (va, gchar **);
1115 g_warn_if_fail (out_value != NULL);
1116 }
1117
1118 g_object_unref (reader);
1119 g_object_unref (parser);
1120 va_end (va);
1121
1122 return TRUE;
1123 #else
1124 return FALSE;
1125 #endif
1126 }
1127
1128 static gboolean
eos_store_token_sync(EOAuth2Service * service,ESource * source,const gchar * refresh_token,const gchar * access_token,const gchar * expires_in,GCancellable * cancellable,GError ** error)1129 eos_store_token_sync (EOAuth2Service *service,
1130 ESource *source,
1131 const gchar *refresh_token,
1132 const gchar *access_token,
1133 const gchar *expires_in,
1134 GCancellable *cancellable,
1135 GError **error)
1136 {
1137 gint64 expires_after_tm;
1138 gchar *expires_after, *secret = NULL, *uid = NULL;
1139 gboolean success = FALSE;
1140
1141 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1142
1143 if (!refresh_token || !access_token || !expires_in)
1144 return FALSE;
1145
1146 if (g_cancellable_set_error_if_cancelled (cancellable, error))
1147 return FALSE;
1148
1149 expires_after_tm = g_get_real_time () / G_USEC_PER_SEC;
1150 expires_after_tm += g_ascii_strtoll (expires_in, NULL, 10);
1151 expires_after = g_strdup_printf ("%" G_GINT64_FORMAT, expires_after_tm);
1152
1153 if (eos_encode_to_secret (&secret,
1154 E_OAUTH2_SECRET_REFRESH_TOKEN, refresh_token,
1155 E_OAUTH2_SECRET_ACCESS_TOKEN, access_token,
1156 E_OAUTH2_SECRET_EXPIRES_AFTER, expires_after, NULL) &&
1157 eos_generate_secret_uid (service, source, &uid)) {
1158 gchar *label;
1159
1160 label = g_strdup_printf ("Evolution Data Source - %s", strstr (uid, "::") + 2);
1161
1162 success = e_secret_store_store_sync (uid, secret, label, TRUE, cancellable, error);
1163
1164 g_free (label);
1165 }
1166
1167 g_free (uid);
1168 g_free (secret);
1169 g_free (expires_after);
1170
1171 return success;
1172 }
1173
1174 /* Can return success when the access token is already expired and refresh token is available */
1175 static gboolean
eos_lookup_token_sync(EOAuth2Service * service,ESource * source,gchar ** out_refresh_token,gchar ** out_access_token,gint * out_expires_in,GCancellable * cancellable,GError ** error)1176 eos_lookup_token_sync (EOAuth2Service *service,
1177 ESource *source,
1178 gchar **out_refresh_token,
1179 gchar **out_access_token,
1180 gint *out_expires_in,
1181 GCancellable *cancellable,
1182 GError **error)
1183 {
1184 gchar *secret = NULL, *uid = NULL, *expires_after = NULL;
1185 gboolean success = FALSE;
1186
1187 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1188 g_return_val_if_fail (out_refresh_token != NULL, FALSE);
1189 g_return_val_if_fail (out_access_token != NULL, FALSE);
1190 g_return_val_if_fail (out_expires_in != NULL, FALSE);
1191
1192 *out_refresh_token = NULL;
1193 *out_access_token = NULL;
1194 *out_expires_in = -1;
1195
1196 if (g_cancellable_set_error_if_cancelled (cancellable, error))
1197 return FALSE;
1198
1199 if (!eos_generate_secret_uid (service, source, &uid)) {
1200 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
1201 /* Translators: The first %s is a display name of the source, the second is its UID and
1202 the third is the name of the OAuth service. */
1203 _("Source “%s” (%s) is not valid for “%s” OAuth2 service"),
1204 e_source_get_display_name (source),
1205 e_source_get_uid (source),
1206 e_oauth2_service_get_name (service));
1207 return FALSE;
1208 }
1209
1210 if (!e_secret_store_lookup_sync (uid, &secret, cancellable, error)) {
1211 g_free (uid);
1212 return FALSE;
1213 }
1214
1215 g_free (uid);
1216
1217 if (!secret) {
1218 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("OAuth2 secret not found"));
1219 return FALSE;
1220 }
1221
1222 success = eos_decode_from_secret (secret,
1223 E_OAUTH2_SECRET_REFRESH_TOKEN, out_refresh_token,
1224 E_OAUTH2_SECRET_ACCESS_TOKEN, out_access_token,
1225 E_OAUTH2_SECRET_EXPIRES_AFTER, &expires_after,
1226 NULL);
1227
1228 if (success && expires_after) {
1229 gint64 num_expires_after, num_now;
1230
1231 num_expires_after = g_ascii_strtoll (expires_after, NULL, 10);
1232 num_now = g_get_real_time () / G_USEC_PER_SEC;
1233
1234 if (num_now < num_expires_after)
1235 *out_expires_in = num_expires_after - num_now - 1;
1236 }
1237
1238 success = success && *out_refresh_token != NULL;
1239
1240 if (!success) {
1241 g_clear_pointer (out_refresh_token, e_util_safe_free_string);
1242 g_clear_pointer (out_access_token, e_util_safe_free_string);
1243 }
1244
1245 e_util_safe_free_string (secret);
1246 g_free (expires_after);
1247
1248 return success;
1249 }
1250
1251 /**
1252 * e_oauth2_service_receive_and_store_token_sync:
1253 * @service: an #EOAuth2Service
1254 * @source: an #ESource
1255 * @authorization_code: authorization code provided by the server
1256 * @ref_source: (scope call): an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource
1257 * @ref_source_user_data: user data for @ref_source
1258 * @cancellable: optional #GCancellable object, or %NULL
1259 * @error: return location for a #GError, or %NULL
1260 *
1261 * Queries @service at e_oauth2_service_get_refresh_uri() with a request to obtain
1262 * a new access token, associated with the given @authorization_code and stores
1263 * it into the secret store on success.
1264 *
1265 * Returns: whether succeeded
1266 *
1267 * Since: 3.28
1268 **/
1269 gboolean
e_oauth2_service_receive_and_store_token_sync(EOAuth2Service * service,ESource * source,const gchar * authorization_code,EOAuth2ServiceRefSourceFunc ref_source,gpointer ref_source_user_data,GCancellable * cancellable,GError ** error)1270 e_oauth2_service_receive_and_store_token_sync (EOAuth2Service *service,
1271 ESource *source,
1272 const gchar *authorization_code,
1273 EOAuth2ServiceRefSourceFunc ref_source,
1274 gpointer ref_source_user_data,
1275 GCancellable *cancellable,
1276 GError **error)
1277 {
1278 SoupSession *session;
1279 SoupMessage *message;
1280 GHashTable *post_form;
1281 gchar *response_json = NULL;
1282 gboolean success;
1283
1284 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1285 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1286 g_return_val_if_fail (authorization_code != NULL, FALSE);
1287 g_return_val_if_fail (ref_source != NULL, FALSE);
1288
1289 session = eos_create_soup_session (ref_source, ref_source_user_data, source);
1290 if (!session)
1291 return FALSE;
1292
1293 post_form = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1294
1295 e_oauth2_service_prepare_get_token_form (service, source, authorization_code, post_form);
1296
1297 message = eos_create_soup_message (source, e_oauth2_service_get_refresh_uri (service, source), post_form);
1298
1299 g_hash_table_destroy (post_form);
1300
1301 if (!message) {
1302 g_object_unref (session);
1303 return FALSE;
1304 }
1305
1306 e_oauth2_service_prepare_get_token_message (service, source, message);
1307
1308 success = eos_send_message (session, message, &response_json, cancellable, error);
1309 if (success) {
1310 gchar *access_token = NULL, *refresh_token = NULL, *expires_in = NULL, *token_type = NULL;
1311
1312 if (eos_decode_from_secret (response_json,
1313 "access_token", &access_token,
1314 "refresh_token", &refresh_token,
1315 "expires_in", &expires_in,
1316 "token_type", &token_type,
1317 NULL) && access_token && refresh_token && expires_in && token_type) {
1318
1319 g_warn_if_fail (g_ascii_strcasecmp (token_type, "Bearer") == 0);
1320
1321 success = eos_store_token_sync (service, source,
1322 refresh_token, access_token, expires_in, cancellable, error);
1323 } else {
1324 success = FALSE;
1325 }
1326
1327 e_util_safe_free_string (access_token);
1328 e_util_safe_free_string (refresh_token);
1329 g_free (expires_in);
1330 g_free (token_type);
1331 }
1332
1333 g_object_unref (message);
1334 g_object_unref (session);
1335 e_util_safe_free_string (response_json);
1336
1337 return success;
1338 }
1339
1340 /**
1341 * e_oauth2_service_refresh_and_store_token_sync:
1342 * @service: an #EOAuth2Service
1343 * @source: an #ESource
1344 * @refresh_token: refresh token as provided by the server
1345 * @ref_source: (scope call): an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource
1346 * @ref_source_user_data: user data for @ref_source
1347 * @cancellable: optional #GCancellable object, or %NULL
1348 * @error: return location for a #GError, or %NULL
1349 *
1350 * Queries @service at e_oauth2_service_get_refresh_uri() with a request to refresh
1351 * existing access token with provided @refresh_token and stores it into the secret
1352 * store on success.
1353 *
1354 * Returns: whether succeeded
1355 *
1356 * Since: 3.28
1357 **/
1358 gboolean
e_oauth2_service_refresh_and_store_token_sync(EOAuth2Service * service,ESource * source,const gchar * refresh_token,EOAuth2ServiceRefSourceFunc ref_source,gpointer ref_source_user_data,GCancellable * cancellable,GError ** error)1359 e_oauth2_service_refresh_and_store_token_sync (EOAuth2Service *service,
1360 ESource *source,
1361 const gchar *refresh_token,
1362 EOAuth2ServiceRefSourceFunc ref_source,
1363 gpointer ref_source_user_data,
1364 GCancellable *cancellable,
1365 GError **error)
1366 {
1367 SoupSession *session;
1368 SoupMessage *message;
1369 GHashTable *post_form;
1370 gchar *response_json = NULL;
1371 gboolean success;
1372 GError *local_error = NULL;
1373
1374 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1375 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1376 g_return_val_if_fail (refresh_token != NULL, FALSE);
1377 g_return_val_if_fail (ref_source != NULL, FALSE);
1378
1379 session = eos_create_soup_session (ref_source, ref_source_user_data, source);
1380 if (!session)
1381 return FALSE;
1382
1383 post_form = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1384
1385 e_oauth2_service_prepare_refresh_token_form (service, source, refresh_token, post_form);
1386
1387 message = eos_create_soup_message (source, e_oauth2_service_get_refresh_uri (service, source), post_form);
1388
1389 g_hash_table_destroy (post_form);
1390
1391 if (!message) {
1392 g_object_unref (session);
1393 return FALSE;
1394 }
1395
1396 e_oauth2_service_prepare_refresh_token_message (service, source, message);
1397
1398 success = eos_send_message (session, message, &response_json, cancellable, &local_error);
1399 if (success) {
1400 gchar *access_token = NULL, *expires_in = NULL, *new_refresh_token = NULL;
1401
1402 if (eos_decode_from_secret (response_json,
1403 "access_token", &access_token,
1404 "expires_in", &expires_in,
1405 "refresh_token", &new_refresh_token,
1406 NULL) && access_token && expires_in) {
1407 success = eos_store_token_sync (service, source,
1408 (new_refresh_token && *new_refresh_token) ? new_refresh_token : refresh_token,
1409 access_token, expires_in, cancellable, error);
1410 } else {
1411 success = FALSE;
1412
1413 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Received incorrect response from server “%s”."),
1414 e_oauth2_service_get_refresh_uri (service, source));
1415 }
1416
1417 e_util_safe_free_string (access_token);
1418 g_free (new_refresh_token);
1419 g_free (expires_in);
1420 } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST)) {
1421 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
1422 _("Failed to refresh access token. Sign to the server again, please."));
1423 g_clear_error (&local_error);
1424 }
1425
1426 if (local_error)
1427 g_propagate_error (error, local_error);
1428
1429 g_object_unref (message);
1430 g_object_unref (session);
1431 e_util_safe_free_string (response_json);
1432
1433 return success;
1434 }
1435
1436 /**
1437 * e_oauth2_service_delete_token_sync:
1438 * @service: an #EOAuth2Service
1439 * @source: an #ESource
1440 * @cancellable: optional #GCancellable object, or %NULL
1441 * @error: return location for a #GError, or %NULL
1442 *
1443 * Deletes token information for the @service and @source from the secret store.
1444 *
1445 * Returns: whether succeeded
1446 *
1447 * Since: 3.28
1448 **/
1449 gboolean
e_oauth2_service_delete_token_sync(EOAuth2Service * service,ESource * source,GCancellable * cancellable,GError ** error)1450 e_oauth2_service_delete_token_sync (EOAuth2Service *service,
1451 ESource *source,
1452 GCancellable *cancellable,
1453 GError **error)
1454 {
1455 gchar *uid = NULL;
1456 gboolean success;
1457
1458 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1459 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1460
1461 if (!eos_generate_secret_uid (service, source, &uid)) {
1462 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
1463 /* Translators: The first %s is a display name of the source, the second is its UID. */
1464 _("Source “%s” (%s) is not a valid OAuth2 source"),
1465 e_source_get_display_name (source),
1466 e_source_get_uid (source));
1467 return FALSE;
1468 }
1469
1470 success = e_secret_store_delete_sync (uid, cancellable, error);
1471
1472 g_free (uid);
1473
1474 return success;
1475 }
1476
1477 /**
1478 * e_oauth2_service_get_access_token_sync:
1479 * @service: an #EOAuth2Service
1480 * @source: an #ESource
1481 * @ref_source: (scope call): an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource
1482 * @ref_source_user_data: user data for @ref_source
1483 * @out_access_token: (out) (transfer full): return location for the access token
1484 * @out_expires_in: (out): how many seconds the access token expires in
1485 * @cancellable: optional #GCancellable object, or %NULL
1486 * @error: return location for a #GError, or %NULL
1487 *
1488 * Reads access token information from the secret store for the @source and
1489 * in case it's expired it refreshes the token, if possible.
1490 *
1491 * Free the returned @out_access_token with g_free(), when no longer needed.
1492 *
1493 * Returns: %TRUE, when the returned access token has been set and it's not expired,
1494 * %FALSE otherwise.
1495 *
1496 * Since: 3.28
1497 **/
1498 gboolean
e_oauth2_service_get_access_token_sync(EOAuth2Service * service,ESource * source,EOAuth2ServiceRefSourceFunc ref_source,gpointer ref_source_user_data,gchar ** out_access_token,gint * out_expires_in,GCancellable * cancellable,GError ** error)1499 e_oauth2_service_get_access_token_sync (EOAuth2Service *service,
1500 ESource *source,
1501 EOAuth2ServiceRefSourceFunc ref_source,
1502 gpointer ref_source_user_data,
1503 gchar **out_access_token,
1504 gint *out_expires_in,
1505 GCancellable *cancellable,
1506 GError **error)
1507 {
1508 gchar *refresh_token = NULL;
1509 gboolean success = TRUE;
1510
1511 g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE);
1512 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1513 g_return_val_if_fail (ref_source != NULL, FALSE);
1514 g_return_val_if_fail (out_access_token != NULL, FALSE);
1515 g_return_val_if_fail (out_expires_in != NULL, FALSE);
1516
1517 if (!eos_lookup_token_sync (service, source, &refresh_token, out_access_token, out_expires_in, cancellable, error))
1518 return FALSE;
1519
1520 if (*out_expires_in <= 0 && refresh_token) {
1521 success = e_oauth2_service_refresh_and_store_token_sync (service, source, refresh_token,
1522 ref_source, ref_source_user_data, cancellable, error);
1523
1524 g_clear_pointer (&refresh_token, e_util_safe_free_string);
1525 g_clear_pointer (out_access_token, e_util_safe_free_string);
1526
1527 success = success && eos_lookup_token_sync (service, source, &refresh_token, out_access_token, out_expires_in, cancellable, error);
1528 }
1529
1530 e_util_safe_free_string (refresh_token);
1531
1532 if (success && *out_expires_in <= 0) {
1533 g_clear_pointer (out_access_token, e_util_safe_free_string);
1534
1535 success = FALSE;
1536
1537 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
1538 _("The access token is expired and it failed to refresh it. Sign to the server again, please."));
1539 }
1540
1541 return success;
1542 }
1543
1544 /**
1545 * e_oauth2_service_util_set_to_form:
1546 * @form: (element-type utf8 utf8): a #GHashTable
1547 * @name: a property name
1548 * @value: (nullable): a property value
1549 *
1550 * Sets @value for @name to @form. The @form should be
1551 * the one used in e_oauth2_service_prepare_authentication_uri_query(),
1552 * e_oauth2_service_prepare_get_token_form() or
1553 * e_oauth2_service_prepare_refresh_token_form().
1554 *
1555 * If the @value is %NULL, then the property named @name is removed
1556 * from the @form instead.
1557 *
1558 * Since: 3.28
1559 **/
1560 void
e_oauth2_service_util_set_to_form(GHashTable * form,const gchar * name,const gchar * value)1561 e_oauth2_service_util_set_to_form (GHashTable *form,
1562 const gchar *name,
1563 const gchar *value)
1564 {
1565 g_return_if_fail (form != NULL);
1566 g_return_if_fail (name != NULL);
1567
1568 if (value)
1569 g_hash_table_insert (form, g_strdup (name), g_strdup (value));
1570 else
1571 g_hash_table_remove (form, name);
1572 }
1573
1574 /**
1575 * e_oauth2_service_util_take_to_form:
1576 * @form: (element-type utf8 utf8): a #GHashTable
1577 * @name: a property name
1578 * @value: (transfer full) (nullable): a property value
1579 *
1580 * Takes ownership of @value and sets it for @name to @form. The @value
1581 * will be freed with g_free(), when no longer needed. The @form should be
1582 * the one used in e_oauth2_service_prepare_authentication_uri_query(),
1583 * e_oauth2_service_prepare_get_token_form() or
1584 * e_oauth2_service_prepare_refresh_token_form().
1585 *
1586 * If the @value is %NULL, then the property named @name is removed
1587 * from the @form instead.
1588 *
1589 * Since: 3.28
1590 **/
1591 void
e_oauth2_service_util_take_to_form(GHashTable * form,const gchar * name,gchar * value)1592 e_oauth2_service_util_take_to_form (GHashTable *form,
1593 const gchar *name,
1594 gchar *value)
1595 {
1596 g_return_if_fail (form != NULL);
1597 g_return_if_fail (name != NULL);
1598
1599 if (value)
1600 g_hash_table_insert (form, g_strdup (name), value);
1601 else
1602 g_hash_table_remove (form, name);
1603 }
1604