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