fixed typo enable_sqllite -> enable_sqlite
[platform/upstream/libsoup.git] / libsoup / soup-auth-domain.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-auth-domain.c: HTTP Authentication Domain (server-side)
4  *
5  * Copyright (C) 2007 Novell, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13
14 #include "soup-auth-domain.h"
15 #include "soup.h"
16 #include "soup-path-map.h"
17
18 /**
19  * SECTION:soup-auth-domain
20  * @short_description: Server-side authentication
21  * @see_also: #SoupServer
22  *
23  * A #SoupAuthDomain manages authentication for all or part of a
24  * #SoupServer. To make a server require authentication, first create
25  * an appropriate subclass of #SoupAuthDomain, and then add it to the
26  * server with soup_server_add_auth_domain().
27  *
28  * In order for an auth domain to have any effect, you must add one or
29  * more paths to it (via soup_auth_domain_add_path() or the
30  * %SOUP_AUTH_DOMAIN_ADD_PATH property). To require authentication for
31  * all ordinary requests, add the path "/". (Note that this does not
32  * include the special "*" URI (eg, "OPTIONS *"), which must be added
33  * as a separate path if you want to cover it.)
34  *
35  * If you need greater control over which requests should and
36  * shouldn't be authenticated, add paths covering everything you
37  * <emphasis>might</emphasis> want authenticated, and then use a
38  * filter (soup_auth_domain_set_filter()) to bypass authentication for
39  * those requests that don't need it.
40  **/
41
42 enum {
43         PROP_0,
44
45         PROP_REALM,
46         PROP_PROXY,
47         PROP_ADD_PATH,
48         PROP_REMOVE_PATH,
49         PROP_FILTER,
50         PROP_FILTER_DATA,
51         PROP_GENERIC_AUTH_CALLBACK,
52         PROP_GENERIC_AUTH_DATA,
53
54         LAST_PROP
55 };
56
57 typedef struct {
58         char *realm;
59         gboolean proxy;
60         SoupPathMap *paths;
61
62         SoupAuthDomainFilter filter;
63         gpointer filter_data;
64         GDestroyNotify filter_dnotify;
65
66         SoupAuthDomainGenericAuthCallback auth_callback;
67         gpointer auth_data;
68         GDestroyNotify auth_dnotify;
69
70 } SoupAuthDomainPrivate;
71
72 #define SOUP_AUTH_DOMAIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainPrivate))
73
74 G_DEFINE_ABSTRACT_TYPE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT)
75
76 static void
77 soup_auth_domain_init (SoupAuthDomain *domain)
78 {
79         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
80
81         priv->paths = soup_path_map_new (NULL);
82 }
83
84 static void
85 soup_auth_domain_finalize (GObject *object)
86 {
87         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
88
89         g_free (priv->realm);
90         soup_path_map_free (priv->paths);
91
92         if (priv->filter_dnotify)
93                 priv->filter_dnotify (priv->filter_data);
94         if (priv->auth_dnotify)
95                 priv->auth_dnotify (priv->auth_data);
96
97         G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object);
98 }
99
100 static void
101 soup_auth_domain_set_property (GObject *object, guint prop_id,
102                                const GValue *value, GParamSpec *pspec)
103 {
104         SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object);
105         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
106
107         switch (prop_id) {
108         case PROP_REALM:
109                 g_free (priv->realm);
110                 priv->realm = g_value_dup_string (value);
111                 break;
112         case PROP_PROXY:
113                 priv->proxy = g_value_get_boolean (value);
114                 break;
115         case PROP_ADD_PATH:
116                 soup_auth_domain_add_path (auth_domain,
117                                            g_value_get_string (value));
118                 break;
119         case PROP_REMOVE_PATH:
120                 soup_auth_domain_remove_path (auth_domain,
121                                               g_value_get_string (value));
122                 break;
123         case PROP_FILTER:
124                 priv->filter = g_value_get_pointer (value);
125                 break;
126         case PROP_FILTER_DATA:
127                 if (priv->filter_dnotify) {
128                         priv->filter_dnotify (priv->filter_data);
129                         priv->filter_dnotify = NULL;
130                 }
131                 priv->filter_data = g_value_get_pointer (value);
132                 break;
133         case PROP_GENERIC_AUTH_CALLBACK:
134                 priv->auth_callback = g_value_get_pointer (value);
135                 break;
136         case PROP_GENERIC_AUTH_DATA:
137                 if (priv->auth_dnotify) {
138                         priv->auth_dnotify (priv->auth_data);
139                         priv->auth_dnotify = NULL;
140                 }
141                 priv->auth_data = g_value_get_pointer (value);
142                 break;
143         default:
144                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
145                 break;
146         }
147 }
148
149 static void
150 soup_auth_domain_get_property (GObject *object, guint prop_id,
151                                GValue *value, GParamSpec *pspec)
152 {
153         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
154
155         switch (prop_id) {
156         case PROP_REALM:
157                 g_value_set_string (value, priv->realm);
158                 break;
159         case PROP_PROXY:
160                 g_value_set_boolean (value, priv->proxy);
161                 break;
162         case PROP_FILTER:
163                 g_value_set_pointer (value, priv->filter);
164                 break;
165         case PROP_FILTER_DATA:
166                 g_value_set_pointer (value, priv->filter_data);
167                 break;
168         case PROP_GENERIC_AUTH_CALLBACK:
169                 g_value_set_pointer (value, priv->auth_callback);
170                 break;
171         case PROP_GENERIC_AUTH_DATA:
172                 g_value_set_pointer (value, priv->auth_data);
173                 break;
174         default:
175                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
176                 break;
177         }
178 }
179
180 static void
181 soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class)
182 {
183         GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class);
184
185         g_type_class_add_private (auth_domain_class, sizeof (SoupAuthDomainPrivate));
186
187         object_class->finalize = soup_auth_domain_finalize;
188         object_class->set_property = soup_auth_domain_set_property;
189         object_class->get_property = soup_auth_domain_get_property;
190
191         /**
192          * SOUP_AUTH_DOMAIN_REALM:
193          *
194          * Alias for the #SoupAuthDomain:realm property. (The realm of
195          * this auth domain.)
196          **/
197         g_object_class_install_property (
198                 object_class, PROP_REALM,
199                 g_param_spec_string (SOUP_AUTH_DOMAIN_REALM,
200                                      "Realm",
201                                      "The realm of this auth domain",
202                                      NULL,
203                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
204         /**
205          * SOUP_AUTH_DOMAIN_PROXY:
206          *
207          * Alias for the #SoupAuthDomain:proxy property. (Whether or
208          * not this is a proxy auth domain.)
209          **/
210         g_object_class_install_property (
211                 object_class, PROP_PROXY,
212                 g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY,
213                                       "Proxy",
214                                       "Whether or not this is a proxy auth domain",
215                                       FALSE,
216                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
217         /**
218          * SOUP_AUTH_DOMAIN_ADD_PATH:
219          *
220          * Alias for the #SoupAuthDomain:add-path property. (Shortcut
221          * for calling soup_auth_domain_add_path().)
222          **/
223         g_object_class_install_property (
224                 object_class, PROP_ADD_PATH,
225                 g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH,
226                                      "Add a path",
227                                      "Add a path covered by this auth domain",
228                                      NULL,
229                                      G_PARAM_WRITABLE));
230         /**
231          * SOUP_AUTH_DOMAIN_REMOVE_PATH:
232          *
233          * Alias for the #SoupAuthDomain:remove-path property.
234          * (Shortcut for calling soup_auth_domain_remove_path().)
235          **/
236         g_object_class_install_property (
237                 object_class, PROP_REMOVE_PATH,
238                 g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH,
239                                      "Remove a path",
240                                      "Remove a path covered by this auth domain",
241                                      NULL,
242                                      G_PARAM_WRITABLE));
243         /**
244          * SOUP_AUTH_DOMAIN_FILTER:
245          *
246          * Alias for the #SoupAuthDomain:filter property. (The
247          * #SoupAuthDomainFilter for the domain.)
248          **/
249         g_object_class_install_property (
250                 object_class, PROP_FILTER,
251                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER,
252                                       "Filter",
253                                       "A filter for deciding whether or not to require authentication",
254                                       G_PARAM_READWRITE));
255         /**
256          * SOUP_AUTH_DOMAIN_FILTER_DATA:
257          *
258          * Alias for the #SoupAuthDomain:filter-data property. (Data
259          * to pass to the #SoupAuthDomainFilter.)
260          **/
261         g_object_class_install_property (
262                 object_class, PROP_FILTER_DATA,
263                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA,
264                                       "Filter data",
265                                       "Data to pass to filter",
266                                       G_PARAM_READWRITE));
267         /**
268          * SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK:
269          *
270          * Alias for the #SoupAuthDomain:generic-auth-callback property.
271          * (The #SoupAuthDomainGenericAuthCallback.)
272          **/
273         g_object_class_install_property (
274                 object_class, PROP_GENERIC_AUTH_CALLBACK,
275                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK,
276                                       "Generic authentication callback",
277                                       "An authentication callback that can be used with any SoupAuthDomain subclass",
278                                       G_PARAM_READWRITE));
279         /**
280          * SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA:
281          *
282          * Alias for the #SoupAuthDomain:generic-auth-data property.
283          * (The data to pass to the #SoupAuthDomainGenericAuthCallback.)
284          **/
285         g_object_class_install_property (
286                 object_class, PROP_GENERIC_AUTH_DATA,
287                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA,
288                                       "Authentication callback data",
289                                       "Data to pass to auth callback",
290                                       G_PARAM_READWRITE));
291 }
292
293 /**
294  * soup_auth_domain_add_path:
295  * @domain: a #SoupAuthDomain
296  * @path: the path to add to @domain
297  *
298  * Adds @path to @domain, such that requests under @path on @domain's
299  * server will require authentication (unless overridden by
300  * soup_auth_domain_remove_path() or soup_auth_domain_set_filter()).
301  *
302  * You can also add paths by setting the %SOUP_AUTH_DOMAIN_ADD_PATH
303  * property, which can also be used to add one or more paths at
304  * construct time.
305  **/
306 void
307 soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path)
308 {
309         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
310
311         /* "" should not match "*" */
312         if (!*path)
313                 path = "/";
314
315         soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE));
316 }
317
318 /**
319  * soup_auth_domain_remove_path:
320  * @domain: a #SoupAuthDomain
321  * @path: the path to remove from @domain
322  *
323  * Removes @path from @domain, such that requests under @path on
324  * @domain's server will NOT require authentication.
325  *
326  * This is not simply an undo-er for soup_auth_domain_add_path(); it
327  * can be used to "carve out" a subtree that does not require
328  * authentication inside a hierarchy that does. Note also that unlike
329  * with soup_auth_domain_add_path(), this cannot be overridden by
330  * adding a filter, as filters can only bypass authentication that
331  * would otherwise be required, not require it where it would
332  * otherwise be unnecessary.
333  *
334  * You can also remove paths by setting the
335  * %SOUP_AUTH_DOMAIN_REMOVE_PATH property, which can also be used to
336  * remove one or more paths at construct time.
337  **/
338 void
339 soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path)
340 {
341         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
342
343         /* "" should not match "*" */
344         if (!*path)
345                 path = "/";
346
347         soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE));
348 }
349
350 /**
351  * SoupAuthDomainFilter:
352  * @domain: a #SoupAuthDomain
353  * @msg: a #SoupMessage
354  * @user_data: the data passed to soup_auth_domain_set_filter()
355  *
356  * The prototype for a #SoupAuthDomain filter; see
357  * soup_auth_domain_set_filter() for details.
358  *
359  * Return value: %TRUE if @msg requires authentication, %FALSE if not.
360  **/
361
362 /**
363  * soup_auth_domain_set_filter:
364  * @domain: a #SoupAuthDomain
365  * @filter: the auth filter for @domain
366  * @filter_data: data to pass to @filter
367  * @dnotify: destroy notifier to free @filter_data when @domain
368  * is destroyed
369  *
370  * Adds @filter as an authentication filter to @domain. The filter
371  * gets a chance to bypass authentication for certain requests that
372  * would otherwise require it. Eg, it might check the message's path
373  * in some way that is too complicated to do via the other methods, or
374  * it might check the message's method, and allow GETs but not PUTs.
375  *
376  * The filter function returns %TRUE if the request should still
377  * require authentication, or %FALSE if authentication is unnecessary
378  * for this request.
379  *
380  * To help prevent security holes, your filter should return %TRUE by
381  * default, and only return %FALSE under specifically-tested
382  * circumstances, rather than the other way around. Eg, in the example
383  * above, where you want to authenticate PUTs but not GETs, you should
384  * check if the method is GET and return %FALSE in that case, and then
385  * return %TRUE for all other methods (rather than returning %TRUE for
386  * PUT and %FALSE for all other methods). This way if it turned out
387  * (now or later) that some paths supported additional methods besides
388  * GET and PUT, those methods would default to being NOT allowed for
389  * unauthenticated users.
390  *
391  * You can also set the filter by setting the %SOUP_AUTH_DOMAIN_FILTER
392  * and %SOUP_AUTH_DOMAIN_FILTER_DATA properties, which can also be
393  * used to set the filter at construct time.
394  **/
395 void
396 soup_auth_domain_set_filter (SoupAuthDomain *domain,
397                              SoupAuthDomainFilter filter,
398                              gpointer        filter_data,
399                              GDestroyNotify  dnotify)
400 {
401         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
402
403         if (priv->filter_dnotify)
404                 priv->filter_dnotify (priv->filter_data);
405
406         priv->filter = filter;
407         priv->filter_data = filter_data;
408         priv->filter_dnotify = dnotify;
409
410         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER);
411         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA);
412 }
413
414 /**
415  * soup_auth_domain_get_realm:
416  * @domain: a #SoupAuthDomain
417  *
418  * Gets the realm name associated with @domain
419  *
420  * Return value: @domain's realm
421  **/
422 const char *
423 soup_auth_domain_get_realm (SoupAuthDomain *domain)
424 {
425         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
426
427         return priv->realm;
428 }
429
430 /**
431  * SoupAuthDomainGenericAuthCallback:
432  * @domain: a #SoupAuthDomain
433  * @msg: the #SoupMessage being authenticated
434  * @username: the username from @msg
435  * @user_data: the data passed to
436  * soup_auth_domain_set_generic_auth_callback()
437  *
438  * The prototype for a #SoupAuthDomain generic authentication callback.
439  *
440  * The callback should look up the user's password, call
441  * soup_auth_domain_check_password(), and use the return value from
442  * that method as its own return value.
443  *
444  * In general, for security reasons, it is preferable to use the
445  * auth-domain-specific auth callbacks (eg,
446  * #SoupAuthDomainBasicAuthCallback and
447  * #SoupAuthDomainDigestAuthCallback), because they don't require
448  * keeping a cleartext password database. Most users will use the same
449  * password for many different sites, meaning if any site with a
450  * cleartext password database is compromised, accounts on other
451  * servers might be compromised as well. For many of the cases where
452  * #SoupServer is used, this is not really relevant, but it may still
453  * be worth considering.
454  *
455  * Return value: %TRUE if @msg is authenticated, %FALSE if not.
456  **/
457
458 /**
459  * soup_auth_domain_set_generic_auth_callback:
460  * @domain: a #SoupAuthDomain
461  * @auth_callback: the auth callback
462  * @auth_data: data to pass to @auth_callback
463  * @dnotify: destroy notifier to free @auth_data when @domain
464  * is destroyed
465  *
466  * Sets @auth_callback as an authentication-handling callback for
467  * @domain. Whenever a request comes in to @domain which cannot be
468  * authenticated via a domain-specific auth callback (eg,
469  * #SoupAuthDomainDigestAuthCallback), the generic auth callback
470  * will be invoked. See #SoupAuthDomainGenericAuthCallback for information
471  * on what the callback should do.
472  **/
473 void
474 soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
475                                             SoupAuthDomainGenericAuthCallback auth_callback,
476                                             gpointer        auth_data,
477                                             GDestroyNotify  dnotify)
478 {
479         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
480
481         if (priv->auth_dnotify)
482                 priv->auth_dnotify (priv->auth_data);
483
484         priv->auth_callback = auth_callback;
485         priv->auth_data = auth_data;
486         priv->auth_dnotify = dnotify;
487
488         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK);
489         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA);
490 }
491
492 gboolean
493 soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
494                                             SoupMessage    *msg,
495                                             const char     *username)
496 {
497         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
498
499         if (priv->auth_callback)
500                 return priv->auth_callback (domain, msg, username, priv->auth_data);
501         else
502                 return FALSE;
503 }
504
505 /**
506  * soup_auth_domain_check_password:
507  * @domain: a #SoupAuthDomain
508  * @msg: a #SoupMessage
509  * @username: a username
510  * @password: a password
511  *
512  * Checks if @msg authenticates to @domain via @username and
513  * @password. This would normally be called from a
514  * #SoupAuthDomainGenericAuthCallback.
515  *
516  * Return value: whether or not the message is authenticated
517  **/
518 gboolean
519 soup_auth_domain_check_password (SoupAuthDomain *domain,
520                                  SoupMessage    *msg,
521                                  const char     *username,
522                                  const char     *password)
523 {
524         return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->check_password (domain, msg,
525                                                                     username,
526                                                                     password);
527 }
528
529 /**
530  * soup_auth_domain_covers:
531  * @domain: a #SoupAuthDomain
532  * @msg: a #SoupMessage
533  *
534  * Checks if @domain requires @msg to be authenticated (according to
535  * its paths and filter function). This does not actually look at
536  * whether @msg <emphasis>is</emphasis> authenticated, merely whether
537  * or not it needs to be.
538  *
539  * This is used by #SoupServer internally and is probably of no use to
540  * anyone else.
541  *
542  * Return value: %TRUE if @domain requires @msg to be authenticated
543  **/
544 gboolean
545 soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg)
546 {
547         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
548         const char *path;
549
550         if (!priv->proxy) {
551                 path = soup_message_get_uri (msg)->path;
552                 if (!soup_path_map_lookup (priv->paths, path))
553                         return FALSE;
554         }
555
556         if (priv->filter && !priv->filter (domain, msg, priv->filter_data))
557                 return FALSE;
558         else
559                 return TRUE;
560 }
561
562 /**
563  * soup_auth_domain_accepts:
564  * @domain: a #SoupAuthDomain
565  * @msg: a #SoupMessage
566  *
567  * Checks if @msg contains appropriate authorization for @domain to
568  * accept it. Mirroring soup_auth_domain_covers(), this does not check
569  * whether or not @domain <emphasis>cares</emphasis> if @msg is
570  * authorized.
571  *
572  * This is used by #SoupServer internally and is probably of no use to
573  * anyone else.
574  *
575  * Return value: the username that @msg has authenticated as, if in
576  * fact it has authenticated. %NULL otherwise.
577  **/
578 char *
579 soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg)
580 {
581         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
582         const char *header;
583
584         header = soup_message_headers_get_one (msg->request_headers,
585                                                priv->proxy ?
586                                                "Proxy-Authorization" :
587                                                "Authorization");
588         if (!header)
589                 return NULL;
590         return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header);
591 }
592
593 /**
594  * soup_auth_domain_challenge:
595  * @domain: a #SoupAuthDomain
596  * @msg: a #SoupMessage
597  *
598  * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg,
599  * requesting that the client authenticate, and sets @msg's status
600  * accordingly.
601  *
602  * This is used by #SoupServer internally and is probably of no use to
603  * anyone else.
604  **/
605 void
606 soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg)
607 {
608         SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
609         char *challenge;
610
611         challenge = SOUP_AUTH_DOMAIN_GET_CLASS (domain)->challenge (domain, msg);
612         soup_message_set_status (msg, priv->proxy ?
613                                  SOUP_STATUS_PROXY_UNAUTHORIZED :
614                                  SOUP_STATUS_UNAUTHORIZED);
615         soup_message_headers_append (msg->response_headers,
616                                      priv->proxy ?
617                                      "Proxy-Authenticate" :
618                                      "WWW-Authenticate",
619                                      challenge);
620         g_free (challenge);
621 }