1 /*
2  * e-goa-password-based.c
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-data-server-config.h"
19 
20 /* XXX Yeah, yeah... */
21 #define GOA_API_IS_SUBJECT_TO_CHANGE
22 
23 #include <goa/goa.h>
24 #include <glib/gi18n-lib.h>
25 
26 #include "e-goa-password-based.h"
27 
28 struct _EGoaPasswordBasedPrivate {
29 	GoaClient *goa_client;
30 	GMutex lock;
31 };
32 
33 G_DEFINE_DYNAMIC_TYPE_EXTENDED (EGoaPasswordBased,
34 				e_goa_password_based,
35 				E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL,
36 				0,
37 				G_ADD_PRIVATE_DYNAMIC (EGoaPasswordBased))
38 
39 static GoaClient *
e_goa_password_based_ref_goa_client_sync(EGoaPasswordBased * goa_password_based,GCancellable * cancellable,GError ** error)40 e_goa_password_based_ref_goa_client_sync (EGoaPasswordBased *goa_password_based,
41 					  GCancellable *cancellable,
42 					  GError **error)
43 {
44 	GoaClient *goa_client;
45 
46 	g_return_val_if_fail (E_IS_GOA_PASSWORD_BASED (goa_password_based), NULL);
47 
48 	g_mutex_lock (&goa_password_based->priv->lock);
49 
50 	if (goa_password_based->priv->goa_client) {
51 		GDBusObjectManager *object_manager;
52 		gchar *owner_name = NULL;
53 
54 		object_manager = goa_client_get_object_manager (goa_password_based->priv->goa_client);
55 		if (object_manager)
56 			owner_name = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
57 
58 		if (!owner_name)
59 			g_clear_object (&goa_password_based->priv->goa_client);
60 
61 		g_free (owner_name);
62 	}
63 
64 	if (!goa_password_based->priv->goa_client)
65 		goa_password_based->priv->goa_client = goa_client_new_sync (cancellable, error);
66 
67 	if (goa_password_based->priv->goa_client)
68 		goa_client = g_object_ref (goa_password_based->priv->goa_client);
69 	else
70 		goa_client = NULL;
71 
72 	g_mutex_unlock (&goa_password_based->priv->lock);
73 
74 	return goa_client;
75 }
76 
77 static ESource *
e_goa_password_based_ref_credentials_source(ESourceCredentialsProvider * provider,ESource * source)78 e_goa_password_based_ref_credentials_source (ESourceCredentialsProvider *provider,
79 					     ESource *source)
80 {
81 	ESource *adept, *cred_source = NULL;
82 
83 	g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER (provider), NULL);
84 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
85 
86 	adept = g_object_ref (source);
87 
88 	while (adept && !e_source_has_extension (adept, E_SOURCE_EXTENSION_GOA)) {
89 		ESource *parent;
90 
91 		if (!e_source_get_parent (adept)) {
92 			break;
93 		}
94 
95 		parent = e_source_credentials_provider_ref_source (provider, e_source_get_parent (adept));
96 
97 		g_clear_object (&adept);
98 		adept = parent;
99 	}
100 
101 	if (adept && e_source_has_extension (adept, E_SOURCE_EXTENSION_GOA)) {
102 		cred_source = g_object_ref (adept);
103 	}
104 
105 	g_clear_object (&adept);
106 
107 	if (!cred_source)
108 		cred_source = e_source_credentials_provider_ref_credentials_source (provider, source);
109 
110 	return cred_source;
111 }
112 
113 static GoaObject *
e_goa_password_based_ref_account(ESourceCredentialsProvider * provider,ESource * source,GoaClient * goa_client)114 e_goa_password_based_ref_account (ESourceCredentialsProvider *provider,
115 				  ESource *source,
116                                   GoaClient *goa_client)
117 {
118 	ESource *cred_source = NULL;
119 	GoaObject *match = NULL;
120 	GList *list, *link;
121 	gchar *account_id = NULL;
122 	ESourceGoa *extension = NULL;
123 
124 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_GOA)) {
125 		extension = e_source_get_extension (source, E_SOURCE_EXTENSION_GOA);
126 	} else {
127 		cred_source = e_goa_password_based_ref_credentials_source (provider, source);
128 		if (cred_source && e_source_has_extension (cred_source, E_SOURCE_EXTENSION_GOA))
129 			extension = e_source_get_extension (cred_source, E_SOURCE_EXTENSION_GOA);
130 	}
131 
132 	if (!extension) {
133 		g_clear_object (&cred_source);
134 		return NULL;
135 	}
136 
137 	account_id = e_source_goa_dup_account_id (extension);
138 
139 	g_clear_object (&cred_source);
140 
141 	if (account_id == NULL)
142 		return NULL;
143 
144 	/* FIXME Use goa_client_lookup_by_id() once we require GOA 3.6. */
145 	list = goa_client_get_accounts (goa_client);
146 
147 	for (link = list; link != NULL; link = g_list_next (link)) {
148 		GoaObject *goa_object;
149 		GoaAccount *goa_account;
150 		const gchar *candidate_id;
151 
152 		goa_object = GOA_OBJECT (link->data);
153 		goa_account = goa_object_get_account (goa_object);
154 		candidate_id = goa_account_get_id (goa_account);
155 
156 		if (g_strcmp0 (account_id, candidate_id) == 0)
157 			match = g_object_ref (goa_object);
158 
159 		g_object_unref (goa_account);
160 
161 		if (match != NULL)
162 			break;
163 	}
164 
165 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
166 	g_free (account_id);
167 
168 	return match;
169 }
170 
171 static gboolean
e_goa_password_based_can_process(ESourceCredentialsProviderImpl * provider_impl,ESource * source)172 e_goa_password_based_can_process (ESourceCredentialsProviderImpl *provider_impl,
173 				  ESource *source)
174 {
175 	gboolean can_process;
176 
177 	g_return_val_if_fail (E_IS_GOA_PASSWORD_BASED (provider_impl), FALSE);
178 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
179 
180 	can_process = e_source_has_extension (source, E_SOURCE_EXTENSION_GOA);
181 	if (!can_process) {
182 		ESource *cred_source;
183 
184 		cred_source = e_goa_password_based_ref_credentials_source (
185 			e_source_credentials_provider_impl_get_provider (provider_impl),
186 			source);
187 
188 		if (cred_source) {
189 			can_process = e_source_has_extension (cred_source, E_SOURCE_EXTENSION_GOA);
190 			g_clear_object (&cred_source);
191 		}
192 	}
193 
194 	return can_process;
195 }
196 
197 static gboolean
e_goa_password_based_can_store(ESourceCredentialsProviderImpl * provider_impl)198 e_goa_password_based_can_store (ESourceCredentialsProviderImpl *provider_impl)
199 {
200 	g_return_val_if_fail (E_IS_GOA_PASSWORD_BASED (provider_impl), FALSE);
201 
202 	return FALSE;
203 }
204 
205 static gboolean
e_goa_password_based_can_prompt(ESourceCredentialsProviderImpl * provider_impl)206 e_goa_password_based_can_prompt (ESourceCredentialsProviderImpl *provider_impl)
207 {
208 	g_return_val_if_fail (E_IS_GOA_PASSWORD_BASED (provider_impl), FALSE);
209 
210 	return FALSE;
211 }
212 
213 static gboolean
e_goa_password_based_lookup_sync(ESourceCredentialsProviderImpl * provider_impl,ESource * source,GCancellable * cancellable,ENamedParameters ** out_credentials,GError ** error)214 e_goa_password_based_lookup_sync (ESourceCredentialsProviderImpl *provider_impl,
215 				  ESource *source,
216 				  GCancellable *cancellable,
217 				  ENamedParameters **out_credentials,
218 				  GError **error)
219 {
220 	GoaClient *goa_client = NULL;
221 	GoaObject *goa_object = NULL;
222 	GoaAccount *goa_account = NULL;
223 	GoaPasswordBased *goa_password_based = NULL;
224 	gchar *password = NULL;
225 	gboolean use_imap_password;
226 	gboolean use_smtp_password;
227 	gboolean success = FALSE;
228 	GError *local_error = NULL;
229 
230 	g_return_val_if_fail (E_IS_GOA_PASSWORD_BASED (provider_impl), FALSE);
231 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
232 	g_return_val_if_fail (out_credentials, FALSE);
233 
234 	goa_client = e_goa_password_based_ref_goa_client_sync (E_GOA_PASSWORD_BASED (provider_impl), cancellable, error);
235 	if (goa_client == NULL) {
236 		if (error && *error)
237 			g_dbus_error_strip_remote_error (*error);
238 		goto exit;
239 	}
240 
241 	goa_object = e_goa_password_based_ref_account (
242 		e_source_credentials_provider_impl_get_provider (provider_impl),
243 		source, goa_client);
244 
245 	if (goa_object == NULL) {
246 		g_set_error (
247 			error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
248 			_("Cannot find a corresponding account in "
249 			"the org.gnome.OnlineAccounts service from "
250 			"which to obtain a password for “%s”"),
251 			e_source_get_display_name (source));
252 		goto exit;
253 	}
254 
255 	goa_account = goa_object_get_account (goa_object);
256 	goa_password_based = goa_object_get_password_based (goa_object);
257 
258 	if (!goa_password_based) {
259 		/* Can be OAuth/2 based, thus return empty credentials. */
260 		*out_credentials = e_named_parameters_new ();
261 		success = TRUE;
262 		goto exit;
263 	}
264 
265 	success = goa_account_call_ensure_credentials_sync (goa_account, NULL, cancellable, &local_error);
266 	if (!success) {
267 		if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) {
268 			g_clear_error (&local_error);
269 		} else if (local_error) {
270 			g_dbus_error_strip_remote_error (local_error);
271 			g_propagate_error (error, local_error);
272 
273 			goto exit;
274 		}
275 	}
276 
277 	use_imap_password = e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
278 	use_smtp_password = e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_TRANSPORT);
279 
280 	/* Use a suitable password ID for the ESource. */
281 	if (use_imap_password) {
282 		goa_password_based_call_get_password_sync (
283 			goa_password_based, "imap-password",
284 			&password, cancellable, error);
285 	} else if (use_smtp_password) {
286 		goa_password_based_call_get_password_sync (
287 			goa_password_based, "smtp-password",
288 			&password, cancellable, error);
289 	} else {
290 		/* Generic fallback - password ID is not used. */
291 		goa_password_based_call_get_password_sync (
292 			goa_password_based, "",
293 			&password, cancellable, error);
294 	}
295 
296 	if (password == NULL) {
297 		success = FALSE;
298 		if (error && *error)
299 			g_dbus_error_strip_remote_error (*error);
300 		goto exit;
301 	}
302 
303 	*out_credentials = e_named_parameters_new ();
304 	e_named_parameters_set (*out_credentials, E_SOURCE_CREDENTIAL_PASSWORD, password);
305 
306  exit:
307 	g_clear_object (&goa_client);
308 	g_clear_object (&goa_object);
309 	g_clear_object (&goa_account);
310 	g_clear_object (&goa_password_based);
311 
312 	e_util_safe_free_string (password);
313 
314 	if (!success)
315 		g_prefix_error (error, "%s", _("Failed to get password from GOA: "));
316 
317 	return success;
318 }
319 
320 static void
e_goa_password_based_dispose(GObject * object)321 e_goa_password_based_dispose (GObject *object)
322 {
323 	EGoaPasswordBased *goa_password_based = E_GOA_PASSWORD_BASED (object);
324 
325 	g_mutex_lock (&goa_password_based->priv->lock);
326 
327 	g_clear_object (&goa_password_based->priv->goa_client);
328 
329 	g_mutex_unlock (&goa_password_based->priv->lock);
330 
331 	/* Chain up to parent's method. */
332 	G_OBJECT_CLASS (e_goa_password_based_parent_class)->dispose (object);
333 }
334 
335 static void
e_goa_password_based_finalize(GObject * object)336 e_goa_password_based_finalize (GObject *object)
337 {
338 	EGoaPasswordBased *goa_password_based = E_GOA_PASSWORD_BASED (object);
339 
340 	g_clear_object (&goa_password_based->priv->goa_client);
341 	g_mutex_clear (&goa_password_based->priv->lock);
342 
343 	/* Chain up to parent's method. */
344 	G_OBJECT_CLASS (e_goa_password_based_parent_class)->finalize (object);
345 }
346 
347 static void
e_goa_password_based_class_init(EGoaPasswordBasedClass * class)348 e_goa_password_based_class_init (EGoaPasswordBasedClass *class)
349 {
350 	ESourceCredentialsProviderImplClass *provider_impl_class;
351 	GObjectClass *object_class;
352 
353 	provider_impl_class = E_SOURCE_CREDENTIALS_PROVIDER_IMPL_CLASS (class);
354 	provider_impl_class->can_process = e_goa_password_based_can_process;
355 	provider_impl_class->can_store = e_goa_password_based_can_store;
356 	provider_impl_class->can_prompt = e_goa_password_based_can_prompt;
357 	provider_impl_class->lookup_sync = e_goa_password_based_lookup_sync;
358 
359 	object_class = G_OBJECT_CLASS (class);
360 	object_class->dispose = e_goa_password_based_dispose;
361 	object_class->finalize = e_goa_password_based_finalize;
362 }
363 
364 static void
e_goa_password_based_class_finalize(EGoaPasswordBasedClass * class)365 e_goa_password_based_class_finalize (EGoaPasswordBasedClass *class)
366 {
367 }
368 
369 static void
e_goa_password_based_init(EGoaPasswordBased * session)370 e_goa_password_based_init (EGoaPasswordBased *session)
371 {
372 	session->priv = e_goa_password_based_get_instance_private (session);
373 
374 	g_mutex_init (&session->priv->lock);
375 }
376 
377 void
e_goa_password_based_type_register(GTypeModule * type_module)378 e_goa_password_based_type_register (GTypeModule *type_module)
379 {
380 	/* XXX G_DEFINE_DYNAMIC_TYPE_EXTENDED declares a static type registration
381 	 *     function, so we have to wrap it with a public function in
382 	 *     order to register types from a separate compilation unit. */
383 	e_goa_password_based_register_type (type_module);
384 }
385 
386