updated changelog
[platform/upstream/evolution-data-server.git] / libedataserver / e-source-authenticator.c
1 /*
2  * e-source-authenticator.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 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 /**
19  * SECTION: e-source-authenticator
20  * @include: libedataserver/libedataserver.h
21  * @short_description: Interface for authentication attempts
22  *
23  * An object implementing the #ESourceAuthenticator interface gets passed
24  * to e_source_registry_authenticate().  The job of an #ESourceAuthenticator
25  * is to test whether a remote server will accept a given password, and then
26  * indicate the result by returning an #ESourceAuthenticationResult value.
27  *
28  * Typically only #EBackend subclasses need to implement this interface,
29  * as client applications are not involved in authentication.
30  *
31  * Note this interface is designed around "stateful authentication", where
32  * one connects to a server, provides credentials for authentication once,
33  * and then issues commands in an authenticated state for the remainder of
34  * the session.
35  *
36  * Backends requiring "stateless authentication" -- where credentials are
37  * included with each command -- will typically want to cache the password
38  * internally once it's verified as part of implementing this interface.
39  **/
40
41 #include "e-source-authenticator.h"
42
43 #include <config.h>
44 #include <glib/gi18n-lib.h>
45
46 /* These are for building an authentication prompt. */
47 #include <libedataserver/e-source-address-book.h>
48 #include <libedataserver/e-source-authentication.h>
49 #include <libedataserver/e-source-calendar.h>
50 #include <libedataserver/e-source-collection.h>
51 #include <libedataserver/e-source-mail-account.h>
52 #include <libedataserver/e-source-mail-identity.h>
53 #include <libedataserver/e-source-mail-transport.h>
54
55 typedef struct _AsyncContext AsyncContext;
56
57 struct _AsyncContext {
58         GString *password;
59         ESourceAuthenticationResult result;
60 };
61
62 G_DEFINE_INTERFACE (
63         ESourceAuthenticator,
64         e_source_authenticator,
65         G_TYPE_OBJECT)
66
67 static void
68 async_context_free (AsyncContext *async_context)
69 {
70         g_string_free (async_context->password, TRUE);
71
72         g_slice_free (AsyncContext, async_context);
73 }
74
75 static void
76 source_authenticator_get_prompt_strings (ESourceAuthenticator *auth,
77                                          ESource *source,
78                                          gchar **prompt_title,
79                                          gchar **prompt_message,
80                                          gchar **prompt_description)
81 {
82         ESourceAuthentication *extension;
83         GString *description;
84         const gchar *message;
85         const gchar *extension_name;
86         gchar *display_name;
87         gchar *host_name;
88         gchar *user_name;
89
90         /* Known types */
91         enum {
92                 TYPE_UNKNOWN,
93                 TYPE_AMBIGUOUS,
94                 TYPE_ADDRESS_BOOK,
95                 TYPE_CALENDAR,
96                 TYPE_MAIL_ACCOUNT,
97                 TYPE_MAIL_TRANSPORT,
98                 TYPE_MEMO_LIST,
99                 TYPE_TASK_LIST
100         } type = TYPE_UNKNOWN;
101
102         /* XXX This is kind of a hack but it should work for now.  Build a
103          *     suitable password prompt by checking for various extensions
104          *     in the ESource.  If no recognizable extensions are found, or
105          *     if the result is ambiguous, just refer to the data source as
106          *     an "account". */
107
108         display_name = e_source_dup_display_name (source);
109
110         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
111         extension = e_source_get_extension (source, extension_name);
112         host_name = e_source_authentication_dup_host (extension);
113         user_name = e_source_authentication_dup_user (extension);
114
115         extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
116         if (e_source_has_extension (source, extension_name)) {
117                 type = TYPE_ADDRESS_BOOK;
118         }
119
120         extension_name = E_SOURCE_EXTENSION_CALENDAR;
121         if (e_source_has_extension (source, extension_name)) {
122                 if (type == TYPE_UNKNOWN)
123                         type = TYPE_CALENDAR;
124                 else
125                         type = TYPE_AMBIGUOUS;
126         }
127
128         extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
129         if (e_source_has_extension (source, extension_name)) {
130                 if (type == TYPE_UNKNOWN)
131                         type = TYPE_MAIL_ACCOUNT;
132                 else
133                         type = TYPE_AMBIGUOUS;
134         }
135
136         extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
137         if (e_source_has_extension (source, extension_name)) {
138                 if (type == TYPE_UNKNOWN)
139                         type = TYPE_MAIL_TRANSPORT;
140                 else
141                         type = TYPE_AMBIGUOUS;
142         }
143
144         extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
145         if (e_source_has_extension (source, extension_name)) {
146                 if (type == TYPE_UNKNOWN)
147                         type = TYPE_MEMO_LIST;
148                 else
149                         type = TYPE_AMBIGUOUS;
150         }
151
152         extension_name = E_SOURCE_EXTENSION_TASK_LIST;
153         if (e_source_has_extension (source, extension_name)) {
154                 if (type == TYPE_UNKNOWN)
155                         type = TYPE_TASK_LIST;
156                 else
157                         type = TYPE_AMBIGUOUS;
158         }
159
160         switch (type) {
161                 case TYPE_ADDRESS_BOOK:
162                         message = _("Address book authentication request");
163                         break;
164                 case TYPE_CALENDAR:
165                 case TYPE_MEMO_LIST:
166                 case TYPE_TASK_LIST:
167                         message = _("Calendar authentication request");
168                         break;
169                 case TYPE_MAIL_ACCOUNT:
170                 case TYPE_MAIL_TRANSPORT:
171                         message = _("Mail authentication request");
172                         break;
173                 default:  /* generic account prompt */
174                         message = _("Authentication request");
175                         break;
176         }
177
178         description = g_string_sized_new (256);
179
180         switch (type) {
181                 case TYPE_ADDRESS_BOOK:
182                         g_string_append_printf (
183                                 description,
184                                 _("Please enter the password for "
185                                 "address book \"%s\"."), display_name);
186                         break;
187                 case TYPE_CALENDAR:
188                         g_string_append_printf (
189                                 description,
190                                 _("Please enter the password for "
191                                 "calendar \"%s\"."), display_name);
192                         break;
193                 case TYPE_MAIL_ACCOUNT:
194                         g_string_append_printf (
195                                 description,
196                                 _("Please enter the password for "
197                                 "mail account \"%s\"."), display_name);
198                         break;
199                 case TYPE_MAIL_TRANSPORT:
200                         g_string_append_printf (
201                                 description,
202                                 _("Please enter the password for "
203                                 "mail transport \"%s\"."), display_name);
204                         break;
205                 case TYPE_MEMO_LIST:
206                         g_string_append_printf (
207                                 description,
208                                 _("Please enter the password for "
209                                 "memo list \"%s\"."), display_name);
210                         break;
211                 case TYPE_TASK_LIST:
212                         g_string_append_printf (
213                                 description,
214                                 _("Please enter the password for "
215                                 "task list \"%s\"."), display_name);
216                         break;
217                 default:  /* generic account prompt */
218                         g_string_append_printf (
219                                 description,
220                                 _("Please enter the password for "
221                                 "account \"%s\"."), display_name);
222                         break;
223         }
224
225         if (host_name != NULL && user_name != NULL)
226                 g_string_append_printf (
227                         description, "\n(user: %s, host: %s)",
228                         user_name, host_name);
229         else if (host_name != NULL)
230                 g_string_append_printf (
231                         description, "\n(host: %s)", host_name);
232         else if (user_name != NULL)
233                 g_string_append_printf (
234                         description, "\n(user: %s)", user_name);
235
236         *prompt_title = g_strdup ("");
237         *prompt_message = g_strdup (message);
238         *prompt_description = g_string_free (description, FALSE);
239
240         g_free (display_name);
241         g_free (host_name);
242         g_free (user_name);
243 }
244
245 static gboolean
246 source_authenticator_get_without_password (ESourceAuthenticator *auth)
247 {
248         /* require password by default */
249         return FALSE;
250 }
251
252 /* Helper for source_authenticator_try_password() */
253 static void
254 source_authenticator_try_password_thread (GSimpleAsyncResult *simple,
255                                           GObject *object,
256                                           GCancellable *cancellable)
257 {
258         AsyncContext *async_context;
259         GError *error = NULL;
260
261         async_context = g_simple_async_result_get_op_res_gpointer (simple);
262
263         async_context->result =
264                 e_source_authenticator_try_password_sync (
265                         E_SOURCE_AUTHENTICATOR (object),
266                         async_context->password,
267                         cancellable, &error);
268
269         if (error != NULL)
270                 g_simple_async_result_take_error (simple, error);
271 }
272
273 static void
274 source_authenticator_try_password (ESourceAuthenticator *auth,
275                                    const GString *password,
276                                    GCancellable *cancellable,
277                                    GAsyncReadyCallback callback,
278                                    gpointer user_data)
279 {
280         GSimpleAsyncResult *simple;
281         AsyncContext *async_context;
282
283         async_context = g_slice_new0 (AsyncContext);
284         async_context->password = g_string_new (password->str);
285
286         simple = g_simple_async_result_new (
287                 G_OBJECT (auth), callback, user_data,
288                 source_authenticator_try_password);
289
290         g_simple_async_result_set_check_cancellable (simple, cancellable);
291
292         g_simple_async_result_set_op_res_gpointer (
293                 simple, async_context, (GDestroyNotify) async_context_free);
294
295         g_simple_async_result_run_in_thread (
296                 simple, source_authenticator_try_password_thread,
297                 G_PRIORITY_DEFAULT, cancellable);
298
299         g_object_unref (simple);
300 }
301
302 static ESourceAuthenticationResult
303 source_authenticator_try_password_finish (ESourceAuthenticator *auth,
304                                           GAsyncResult *result,
305                                           GError **error)
306 {
307         GSimpleAsyncResult *simple;
308         AsyncContext *async_context;
309
310         g_return_val_if_fail (
311                 g_simple_async_result_is_valid (
312                 result, G_OBJECT (auth),
313                 source_authenticator_try_password),
314                 E_SOURCE_AUTHENTICATION_REJECTED);
315
316         simple = G_SIMPLE_ASYNC_RESULT (result);
317         async_context = g_simple_async_result_get_op_res_gpointer (simple);
318
319         if (g_simple_async_result_propagate_error (simple, error))
320                 return E_SOURCE_AUTHENTICATION_ERROR;
321
322         return async_context->result;
323 }
324
325 static void
326 e_source_authenticator_default_init (ESourceAuthenticatorInterface *iface)
327 {
328         iface->get_prompt_strings = source_authenticator_get_prompt_strings;
329         iface->get_without_password = source_authenticator_get_without_password;
330         iface->try_password = source_authenticator_try_password;
331         iface->try_password_finish = source_authenticator_try_password_finish;
332 }
333
334 /**
335  * e_source_authenticator_get_prompt_strings:
336  * @auth: an #ESourceAuthenticator
337  * @source: an #ESource
338  * @prompt_title: (out): the title of the prompt
339  * @prompt_message: (out): the prompt message for the user
340  * @prompt_description: (out): the detailed description of the prompt
341  *
342  * Generates authentication prompt strings for @source.
343  *
344  * For registry service clients, #ESourceRegistry calls this function as
345  * part of e_source_registry_authenticate_sync().  In the registry service
346  * itself, #EAuthenticationSession calls this function during initialization.
347  * This function should rarely need to be called explicitly outside of those
348  * two cases.
349  *
350  * The #ESourceAuthenticatorInterface defines a default behavior for this
351  * method which should suffice in most cases.  But implementors can still
352  * override the method if needed for special circumstances.
353  *
354  * Free each of the returned prompt strings with g_free().
355  *
356  * Since: 3.6
357  **/
358 void
359 e_source_authenticator_get_prompt_strings (ESourceAuthenticator *auth,
360                                            ESource *source,
361                                            gchar **prompt_title,
362                                            gchar **prompt_message,
363                                            gchar **prompt_description)
364 {
365         ESourceAuthenticatorInterface *iface;
366
367         g_return_if_fail (E_IS_SOURCE_AUTHENTICATOR (auth));
368         g_return_if_fail (E_IS_SOURCE (source));
369         g_return_if_fail (prompt_title != NULL);
370         g_return_if_fail (prompt_message != NULL);
371         g_return_if_fail (prompt_description != NULL);
372
373         iface = E_SOURCE_AUTHENTICATOR_GET_INTERFACE (auth);
374         g_return_if_fail (iface->get_prompt_strings);
375
376         iface->get_prompt_strings (
377                 auth, source,
378                 prompt_title,
379                 prompt_message,
380                 prompt_description);
381 }
382
383 /**
384  * e_source_authenticator_get_without_password:
385  * @auth: an #ESourceAuthenticator
386  *
387  * Returns whether the used authentication method can be used without
388  * a password prompt. If so, then user is not asked for the password,
389  * only if the authentication fails. The default implementation returns
390  * %FALSE, which means always asks for the password (or read it from
391  * a keyring).
392  *
393  * Returns: whether to try to authenticate without asking for the password
394  *
395  * Since: 3.10
396  **/
397 gboolean
398 e_source_authenticator_get_without_password (ESourceAuthenticator *auth)
399 {
400         ESourceAuthenticatorInterface *iface;
401
402         g_return_val_if_fail (E_IS_SOURCE_AUTHENTICATOR (auth), FALSE);
403
404         iface = E_SOURCE_AUTHENTICATOR_GET_INTERFACE (auth);
405         g_return_val_if_fail (iface->get_without_password, FALSE);
406
407         return iface->get_without_password (auth);
408 }
409
410 /**
411  * e_source_authenticator_try_password_sync:
412  * @auth: an #ESourceAuthenticator
413  * @password: a user-provided password
414  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
415  * @error: return location for a #GError, or %NULL
416  *
417  * Attempts to authenticate using @password.
418  *
419  * The password is passed in a #GString container so its content is not
420  * accidentally revealed in a stack trace.
421  *
422  * If an error occurs, the function sets @error and returns
423  * #E_SOURCE_AUTHENTICATION_ERROR.
424  *
425  * Returns: the authentication result
426  *
427  * Since: 3.6
428  **/
429 ESourceAuthenticationResult
430 e_source_authenticator_try_password_sync (ESourceAuthenticator *auth,
431                                           const GString *password,
432                                           GCancellable *cancellable,
433                                           GError **error)
434 {
435         ESourceAuthenticatorInterface *iface;
436
437         g_return_val_if_fail (
438                 E_IS_SOURCE_AUTHENTICATOR (auth),
439                 E_SOURCE_AUTHENTICATION_REJECTED);
440         g_return_val_if_fail (
441                 password != NULL,
442                 E_SOURCE_AUTHENTICATION_REJECTED);
443
444         iface = E_SOURCE_AUTHENTICATOR_GET_INTERFACE (auth);
445         g_return_val_if_fail (
446                 iface->try_password_sync != NULL,
447                 E_SOURCE_AUTHENTICATION_REJECTED);
448
449         return iface->try_password_sync (
450                 auth, password, cancellable, error);
451 }
452
453 /**
454  * e_source_authenticator_try_password:
455  * @auth: an #ESourceAuthenticator
456  * @password: a user-provided password
457  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
458  * @callback: (scope async): a #GAsyncReadyCallback to call when the request
459  *            is satisfied
460  * @user_data: (closure): data to pass to the callback function
461  *
462  * Asyncrhonously attempts to authenticate using @password.
463  *
464  * The password is passed in a #GString container so its content is not
465  * accidentally revealed in a stack trace.
466  *
467  * When the operation is finished, @callback will be called.  You can then
468  * call e_source_authenticator_try_password_finish() to get the result of the
469  * operation.
470  *
471  * Since: 3.6
472  **/
473 void
474 e_source_authenticator_try_password (ESourceAuthenticator *auth,
475                                      const GString *password,
476                                      GCancellable *cancellable,
477                                      GAsyncReadyCallback callback,
478                                      gpointer user_data)
479 {
480         ESourceAuthenticatorInterface *iface;
481
482         g_return_if_fail (E_IS_SOURCE_AUTHENTICATOR (auth));
483         g_return_if_fail (password != NULL);
484
485         iface = E_SOURCE_AUTHENTICATOR_GET_INTERFACE (auth);
486         g_return_if_fail (iface->try_password != NULL);
487
488         iface->try_password (
489                 auth, password, cancellable, callback, user_data);
490 }
491
492 /**
493  * e_source_authenticator_try_password_finish:
494  * @auth: an #ESourceAuthenticator
495  * @result: a #GAsyncResult
496  * @error: return location for a #GError, or %NULL
497  *
498  * Finishes the operation started with e_source_authenticator_try_password().
499  *
500  * If an error occurred, the function sets @error and returns
501  * #E_SOURCE_AUTHENTICATION_ERROR.
502  *
503  * Returns: the authentication result
504  *
505  * Since: 3.6
506  **/
507 ESourceAuthenticationResult
508 e_source_authenticator_try_password_finish (ESourceAuthenticator *auth,
509                                             GAsyncResult *result,
510                                             GError **error)
511 {
512         ESourceAuthenticatorInterface *iface;
513
514         g_return_val_if_fail (
515                 E_IS_SOURCE_AUTHENTICATOR (auth),
516                 E_SOURCE_AUTHENTICATION_REJECTED);
517         g_return_val_if_fail (
518                 G_IS_ASYNC_RESULT (result),
519                 E_SOURCE_AUTHENTICATION_REJECTED);
520
521         iface = E_SOURCE_AUTHENTICATOR_GET_INTERFACE (auth);
522         g_return_val_if_fail (
523                 iface->try_password_finish != NULL,
524                 E_SOURCE_AUTHENTICATION_REJECTED);
525
526         return iface->try_password_finish (auth, result, error);
527 }
528