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-services
20  * @include: libedataserver/libedataserver.h
21  * @short_description: An extensible object holding all known OAuth2 services
22  *
23  * The extensible object, which holds all known OAuth2 services. Each
24  * #EOAuth2Service extends this object and adds itself to it with
25  * e_oauth2_services_add(). The services can be later searched for
26  * with e_oauth2_services_find(), which returns the service suitable
27  * for the given protocol and/or host name.
28  **/
29 
30 #include "evolution-data-server-config.h"
31 
32 #include <stdio.h>
33 
34 #include "e-extensible.h"
35 #include "e-oauth2-service.h"
36 
37 /* Known built-in implementations */
38 #include "e-oauth2-service-google.h"
39 #include "e-oauth2-service-outlook.h"
40 #include "e-oauth2-service-yahoo.h"
41 
42 #include "e-oauth2-services.h"
43 
44 struct _EOAuth2ServicesPrivate {
45 	GMutex property_lock;
46 	GSList *services; /* EOAuth2Service * */
47 };
48 
49 G_DEFINE_TYPE_WITH_CODE (EOAuth2Services, e_oauth2_services, G_TYPE_OBJECT,
50 	G_ADD_PRIVATE (EOAuth2Services)
51 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
52 
53 static GObject *services_singleton = NULL;
54 G_LOCK_DEFINE_STATIC (services_singleton);
55 
56 static void
services_singleton_weak_ref_cb(gpointer user_data,GObject * object)57 services_singleton_weak_ref_cb (gpointer user_data,
58 				GObject *object)
59 {
60 	G_LOCK (services_singleton);
61 
62 	g_warn_if_fail (object == services_singleton);
63 	services_singleton = NULL;
64 
65 	G_UNLOCK (services_singleton);
66 }
67 
68 static GObject *
oauth2_services_constructor(GType type,guint n_construct_params,GObjectConstructParam * construct_params)69 oauth2_services_constructor (GType type,
70 			     guint n_construct_params,
71 			     GObjectConstructParam *construct_params)
72 {
73 	GObject *object;
74 
75 	G_LOCK (services_singleton);
76 
77 	if (services_singleton) {
78 		object = g_object_ref (services_singleton);
79 	} else {
80 		object = G_OBJECT_CLASS (e_oauth2_services_parent_class)->constructor (type, n_construct_params, construct_params);
81 
82 		if (object)
83 			g_object_weak_ref (object, services_singleton_weak_ref_cb, NULL);
84 
85 		services_singleton = object;
86 	}
87 
88 	G_UNLOCK (services_singleton);
89 
90 	return object;
91 }
92 
93 static void
oauth2_services_dispose(GObject * object)94 oauth2_services_dispose (GObject *object)
95 {
96 	EOAuth2Services *services = E_OAUTH2_SERVICES (object);
97 
98 	g_mutex_lock (&services->priv->property_lock);
99 	g_slist_free_full (services->priv->services, g_object_unref);
100 	g_mutex_unlock (&services->priv->property_lock);
101 
102 	/* Chain up to parent's method. */
103 	G_OBJECT_CLASS (e_oauth2_services_parent_class)->dispose (object);
104 }
105 
106 static void
oauth2_services_finalize(GObject * object)107 oauth2_services_finalize (GObject *object)
108 {
109 	EOAuth2Services *services = E_OAUTH2_SERVICES (object);
110 
111 	g_mutex_clear (&services->priv->property_lock);
112 
113 	/* Chain up to parent's method. */
114 	G_OBJECT_CLASS (e_oauth2_services_parent_class)->finalize (object);
115 }
116 
117 static void
oauth2_services_constructed(GObject * object)118 oauth2_services_constructed (GObject *object)
119 {
120 	/* Chain up to parent's method. */
121 	G_OBJECT_CLASS (e_oauth2_services_parent_class)->constructed (object);
122 
123 	e_extensible_load_extensions (E_EXTENSIBLE (object));
124 }
125 
126 static void
e_oauth2_services_class_init(EOAuth2ServicesClass * klass)127 e_oauth2_services_class_init (EOAuth2ServicesClass *klass)
128 {
129 	GObjectClass *object_class;
130 
131 	object_class = G_OBJECT_CLASS (klass);
132 	object_class->dispose = oauth2_services_dispose;
133 	object_class->finalize = oauth2_services_finalize;
134 	object_class->constructed = oauth2_services_constructed;
135 	object_class->constructor = oauth2_services_constructor;
136 
137 	/* Ensure built-in service types are registered */
138 	g_type_ensure (E_TYPE_OAUTH2_SERVICE_GOOGLE);
139 	g_type_ensure (E_TYPE_OAUTH2_SERVICE_OUTLOOK);
140 	g_type_ensure (E_TYPE_OAUTH2_SERVICE_YAHOO);
141 }
142 
143 static void
e_oauth2_services_init(EOAuth2Services * oauth2_services)144 e_oauth2_services_init (EOAuth2Services *oauth2_services)
145 {
146 	oauth2_services->priv = e_oauth2_services_get_instance_private (oauth2_services);
147 
148 	g_mutex_init (&oauth2_services->priv->property_lock);
149 }
150 
151 /**
152  * e_oauth2_services_is_supported:
153  *
154  * Returns: %TRUE, when evolution-data-server had been compiled
155  *    with OAuth2 authentication enabled, %FALSE otherwise.
156  *
157  * Since: 3.28
158  **/
159 gboolean
e_oauth2_services_is_supported(void)160 e_oauth2_services_is_supported (void)
161 {
162 #ifdef ENABLE_OAUTH2
163 	return TRUE;
164 #else
165 	return FALSE;
166 #endif
167 }
168 
169 /**
170  * e_oauth2_services_new:
171  *
172  * Creates a new #EOAuth2Services instance.
173  *
174  * Returns: (transfer full): an #EOAuth2Services
175  *
176  * Since: 3.28
177  **/
178 EOAuth2Services *
e_oauth2_services_new(void)179 e_oauth2_services_new (void)
180 {
181 	return g_object_new (E_TYPE_OAUTH2_SERVICES, NULL);
182 }
183 
184 /**
185  * e_oauth2_services_add:
186  * @services: an #EOAuth2Services
187  * @service: an #EOAuth2Service to add
188  *
189  * Adds the @service to the list of known OAuth2 services into @services.
190  * It also adds a reference to @service.
191  *
192  * Since: 3.28
193  **/
194 void
e_oauth2_services_add(EOAuth2Services * services,EOAuth2Service * service)195 e_oauth2_services_add (EOAuth2Services *services,
196 		       EOAuth2Service *service)
197 {
198 	GSList *link;
199 
200 	g_return_if_fail (E_IS_OAUTH2_SERVICES (services));
201 	g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
202 
203 	g_mutex_lock (&services->priv->property_lock);
204 
205 	for (link = services->priv->services; link; link = g_slist_next (link)) {
206 		if (link->data == service)
207 			break;
208 	}
209 
210 	if (!link)
211 		services->priv->services = g_slist_prepend (services->priv->services, g_object_ref (service));
212 
213 	g_mutex_unlock (&services->priv->property_lock);
214 }
215 
216 /**
217  * e_oauth2_services_remove:
218  * @services: an #EOAuth2Services
219  * @service: an #EOAuth2Service to remove
220  *
221  * Removes the @service from the list of known services in @services.
222  * The function does nothing, if the @service had not been added.
223  *
224  * Since: 3.28
225  **/
226 void
e_oauth2_services_remove(EOAuth2Services * services,EOAuth2Service * service)227 e_oauth2_services_remove (EOAuth2Services *services,
228 			  EOAuth2Service *service)
229 {
230 	GSList *link;
231 
232 	g_return_if_fail (E_IS_OAUTH2_SERVICES (services));
233 	g_return_if_fail (E_IS_OAUTH2_SERVICE (service));
234 
235 	g_mutex_lock (&services->priv->property_lock);
236 
237 	for (link = services->priv->services; link; link = g_slist_next (link)) {
238 		if (link->data == service) {
239 			g_object_unref (service);
240 			services->priv->services = g_slist_remove (services->priv->services, service);
241 			break;
242 		}
243 	}
244 
245 	g_mutex_unlock (&services->priv->property_lock);
246 }
247 
248 /**
249  * e_oauth2_services_list:
250  * @services: an #EOAuth2Services
251  *
252  * Lists all currently known services, which had been added
253  * with e_oauth2_services_add(). Free the returned #GSList with
254  * g_slist_remove_full (known_services, g_object_unref);
255  * when no longer needed.
256  *
257  * Returns: (transfer full) (element-type EOAuth2Service): a newly allocated #GSList
258  *    with all currently known #EOAuth2Service referenced instances
259  *
260  * Since: 3.28
261  **/
262 GSList *
e_oauth2_services_list(EOAuth2Services * services)263 e_oauth2_services_list (EOAuth2Services *services)
264 {
265 	GSList *result;
266 
267 	g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL);
268 
269 	g_mutex_lock (&services->priv->property_lock);
270 
271 	result = g_slist_copy_deep (services->priv->services, (GCopyFunc) g_object_ref, NULL);
272 
273 	g_mutex_unlock (&services->priv->property_lock);
274 
275 	return result;
276 }
277 
278 /**
279  * e_oauth2_services_find:
280  * @services: an #EOAuth2Services
281  * @source: an #ESource
282  *
283  * Searches the list of currently known OAuth2 services for the one which
284  * can be used with the given @source.
285  *
286  * The returned #EOAuth2Service is referenced for thread safety, if found.
287  *
288  * Returns: (transfer full) (nullable): a referenced #EOAuth2Service, which can be used
289  *    with given @source, or %NULL, when none was found.
290  *
291  * Since: 3.28
292  **/
293 EOAuth2Service *
e_oauth2_services_find(EOAuth2Services * services,ESource * source)294 e_oauth2_services_find (EOAuth2Services *services,
295 			ESource *source)
296 {
297 	GSList *link;
298 	EOAuth2Service *result = NULL;
299 
300 	g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL);
301 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
302 
303 	g_mutex_lock (&services->priv->property_lock);
304 
305 	for (link = services->priv->services; link; link = g_slist_next (link)) {
306 		EOAuth2Service *service = link->data;
307 
308 		if (e_oauth2_service_can_process (service, source)) {
309 			result = g_object_ref (service);
310 			break;
311 		}
312 	}
313 
314 	g_mutex_unlock (&services->priv->property_lock);
315 
316 	return result;
317 }
318 
319 /**
320  * e_oauth2_services_guess:
321  * @services: an #EOAuth2Services
322  * @protocol: (nullable): a protocol to search the service for, like "imap", or %NULL
323  * @hostname: (nullable): a host name to search the service for, like "server.example.com", or %NULL
324  *
325  * Searches the list of currently known OAuth2 services for the one which
326  * can be used with the given @protocol and/or @hostname.
327  * Any of @protocol and @hostname can be %NULL, but not both.
328  * It's up to each #EOAuth2Service to decide, which of the arguments
329  * are important and whether all or only any of them can be required.
330  *
331  * The returned #EOAuth2Service is referenced for thread safety, if found.
332  *
333  * Returns: (transfer full) (nullable): a referenced #EOAuth2Service, which can be used
334  *    with given constraints, or %NULL, when none was found.
335  *
336  * Since: 3.28
337  **/
338 EOAuth2Service *
e_oauth2_services_guess(EOAuth2Services * services,const gchar * protocol,const gchar * hostname)339 e_oauth2_services_guess (EOAuth2Services *services,
340 			 const gchar *protocol,
341 			 const gchar *hostname)
342 {
343 	GSList *link;
344 	EOAuth2Service *result = NULL;
345 
346 	g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL);
347 	g_return_val_if_fail (protocol || hostname, NULL);
348 
349 	g_mutex_lock (&services->priv->property_lock);
350 
351 	for (link = services->priv->services; link; link = g_slist_next (link)) {
352 		EOAuth2Service *service = link->data;
353 
354 		if (e_oauth2_service_guess_can_process (service, protocol, hostname)) {
355 			result = g_object_ref (service);
356 			break;
357 		}
358 	}
359 
360 	g_mutex_unlock (&services->priv->property_lock);
361 
362 	return result;
363 }
364 
365 static gboolean
e_oauth2_services_can_check_auth_method(const gchar * auth_method)366 e_oauth2_services_can_check_auth_method (const gchar *auth_method)
367 {
368 	return auth_method && *auth_method &&
369 	       e_oauth2_services_is_supported () &&
370 	       g_strcmp0 (auth_method, "none") != 0 &&
371 	       g_strcmp0 (auth_method, "plain/password") != 0;
372 }
373 
374 /**
375  * e_oauth2_services_is_oauth2_alias:
376  * @services: an #EOAuth2Services
377  * @auth_method: (nullable): an authentication method, or %NULL
378  *
379  * Returns: whether exists any #EOAuth2Service, with the same name as @auth_method.
380  *
381  * See: e_oauth2_services_is_oauth2_alias_static()
382  *
383  * Since: 3.28
384  **/
385 gboolean
e_oauth2_services_is_oauth2_alias(EOAuth2Services * services,const gchar * auth_method)386 e_oauth2_services_is_oauth2_alias (EOAuth2Services *services,
387 				   const gchar *auth_method)
388 {
389 	GSList *link;
390 
391 	g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), FALSE);
392 
393 	if (!e_oauth2_services_can_check_auth_method (auth_method))
394 		return FALSE;
395 
396 	g_mutex_lock (&services->priv->property_lock);
397 
398 	for (link = services->priv->services; link; link = g_slist_next (link)) {
399 		EOAuth2Service *service = link->data;
400 		const gchar *name;
401 
402 		name = e_oauth2_service_get_name (service);
403 
404 		if (name && g_ascii_strcasecmp (name, auth_method) == 0)
405 			break;
406 	}
407 
408 	g_mutex_unlock (&services->priv->property_lock);
409 
410 	return link != NULL;
411 }
412 
413 /**
414  * e_oauth2_services_is_oauth2_alias_static:
415  * @auth_method: (nullable): an authentication method, or %NULL
416  *
417  * This is the same as e_oauth2_services_is_oauth2_alias(), except
418  * it creates its own #EOAuth2Services instance and frees it at the end.
419  * The #EOAuth2Services is implemented as a singleton, thus it won't be
420  * much trouble, as long as there is something else having created one
421  * instance.
422  *
423  * Returns: whether exists any #EOAuth2Service, with the same name as @auth_method.
424  *
425  * Since: 3.28
426  **/
427 gboolean
e_oauth2_services_is_oauth2_alias_static(const gchar * auth_method)428 e_oauth2_services_is_oauth2_alias_static (const gchar *auth_method)
429 {
430 	EOAuth2Services *services;
431 	gboolean is_alias;
432 
433 	if (!e_oauth2_services_can_check_auth_method (auth_method))
434 		return FALSE;
435 
436 	services = e_oauth2_services_new ();
437 	is_alias = e_oauth2_services_is_oauth2_alias (services, auth_method);
438 	g_clear_object (&services);
439 
440 	return is_alias;
441 }
442