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