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