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