1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-auth-domain.c: HTTP Authentication Domain (server-side)
5 * Copyright (C) 2007 Novell, Inc.
14 #include "soup-auth-domain.h"
15 #include "soup-message.h"
16 #include "soup-path-map.h"
20 * SECTION:soup-auth-domain
21 * @short_description: Server-side authentication
22 * @see_also: #SoupServer
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().
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.)
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.
52 PROP_GENERIC_AUTH_CALLBACK,
53 PROP_GENERIC_AUTH_DATA,
63 SoupAuthDomainFilter filter;
65 GDestroyNotify filter_dnotify;
67 SoupAuthDomainGenericAuthCallback auth_callback;
69 GDestroyNotify auth_dnotify;
71 } SoupAuthDomainPrivate;
73 #define SOUP_AUTH_DOMAIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainPrivate))
75 G_DEFINE_ABSTRACT_TYPE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT)
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);
83 soup_auth_domain_init (SoupAuthDomain *domain)
85 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
87 priv->paths = soup_path_map_new (NULL);
91 finalize (GObject *object)
93 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
96 soup_path_map_free (priv->paths);
98 if (priv->filter_dnotify)
99 priv->filter_dnotify (priv->filter_data);
100 if (priv->auth_dnotify)
101 priv->auth_dnotify (priv->auth_data);
103 G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object);
107 soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class)
109 GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class);
111 g_type_class_add_private (auth_domain_class, sizeof (SoupAuthDomainPrivate));
113 object_class->finalize = finalize;
114 object_class->set_property = set_property;
115 object_class->get_property = get_property;
118 * SOUP_AUTH_DOMAIN_REALM:
120 * Alias for the #SoupAuthDomain:realm property. (The realm of
123 g_object_class_install_property (
124 object_class, PROP_REALM,
125 g_param_spec_string (SOUP_AUTH_DOMAIN_REALM,
127 "The realm of this auth domain",
129 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
131 * SOUP_AUTH_DOMAIN_PROXY:
133 * Alias for the #SoupAuthDomain:proxy property. (Whether or
134 * not this is a proxy auth domain.)
136 g_object_class_install_property (
137 object_class, PROP_PROXY,
138 g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY,
140 "Whether or not this is a proxy auth domain",
142 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
144 * SOUP_AUTH_DOMAIN_ADD_PATH:
146 * Alias for the #SoupAuthDomain:add-path property. (Shortcut
147 * for calling soup_auth_domain_add_path().)
149 g_object_class_install_property (
150 object_class, PROP_ADD_PATH,
151 g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH,
153 "Add a path covered by this auth domain",
157 * SOUP_AUTH_DOMAIN_REMOVE_PATH:
159 * Alias for the #SoupAuthDomain:remove-path property.
160 * (Shortcut for calling soup_auth_domain_remove_path().)
162 g_object_class_install_property (
163 object_class, PROP_REMOVE_PATH,
164 g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH,
166 "Remove a path covered by this auth domain",
170 * SOUP_AUTH_DOMAIN_FILTER:
172 * Alias for the #SoupAuthDomain:filter property. (The
173 * #SoupAuthDomainFilter for the domain.)
175 g_object_class_install_property (
176 object_class, PROP_FILTER,
177 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER,
179 "A filter for deciding whether or not to require authentication",
182 * SOUP_AUTH_DOMAIN_FILTER_DATA:
184 * Alias for the #SoupAuthDomain:filter-data property. (Data
185 * to pass to the #SoupAuthDomainFilter.)
187 g_object_class_install_property (
188 object_class, PROP_FILTER_DATA,
189 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA,
191 "Data to pass to filter",
194 * SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK:
196 * Alias for the #SoupAuthDomain:generic-auth-callback property.
197 * (The #SoupAuthDomainGenericAuthCallback.)
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",
206 * SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA:
208 * Alias for the #SoupAuthDomain:generic-auth-data property.
209 * (The data to pass to the #SoupAuthDomainGenericAuthCallback.)
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",
220 set_property (GObject *object, guint prop_id,
221 const GValue *value, GParamSpec *pspec)
223 SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object);
224 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
228 g_free (priv->realm);
229 priv->realm = g_value_dup_string (value);
232 priv->proxy = g_value_get_boolean (value);
235 soup_auth_domain_add_path (auth_domain,
236 g_value_get_string (value));
238 case PROP_REMOVE_PATH:
239 soup_auth_domain_remove_path (auth_domain,
240 g_value_get_string (value));
243 priv->filter = g_value_get_pointer (value);
245 case PROP_FILTER_DATA:
246 if (priv->filter_dnotify) {
247 priv->filter_dnotify (priv->filter_data);
248 priv->filter_dnotify = NULL;
250 priv->filter_data = g_value_get_pointer (value);
252 case PROP_GENERIC_AUTH_CALLBACK:
253 priv->auth_callback = g_value_get_pointer (value);
255 case PROP_GENERIC_AUTH_DATA:
256 if (priv->auth_dnotify) {
257 priv->auth_dnotify (priv->auth_data);
258 priv->auth_dnotify = NULL;
260 priv->auth_data = g_value_get_pointer (value);
263 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269 get_property (GObject *object, guint prop_id,
270 GValue *value, GParamSpec *pspec)
272 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
276 g_value_set_string (value, priv->realm);
279 g_value_set_boolean (value, priv->proxy);
282 g_value_set_pointer (value, priv->filter);
284 case PROP_FILTER_DATA:
285 g_value_set_pointer (value, priv->filter_data);
287 case PROP_GENERIC_AUTH_CALLBACK:
288 g_value_set_pointer (value, priv->auth_callback);
290 case PROP_GENERIC_AUTH_DATA:
291 g_value_set_pointer (value, priv->auth_data);
294 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300 * soup_auth_domain_add_path:
301 * @domain: a #SoupAuthDomain
302 * @path: the path to add to @domain
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()).
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
313 soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path)
315 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
317 /* "" should not match "*" */
321 soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE));
325 * soup_auth_domain_remove_path:
326 * @domain: a #SoupAuthDomain
327 * @path: the path to remove from @domain
329 * Removes @path from @domain, such that requests under @path on
330 * @domain's server will NOT require authentication.
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.
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.
345 soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path)
347 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
349 /* "" should not match "*" */
353 soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE));
357 * SoupAuthDomainFilter:
358 * @domain: a #SoupAuthDomain
359 * @msg: a #SoupMessage
360 * @user_data: the data passed to soup_auth_domain_set_filter()
362 * The prototype for a #SoupAuthDomain filter; see
363 * soup_auth_domain_set_filter() for details.
365 * Return value: %TRUE if @msg requires authentication, %FALSE if not.
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
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.
382 * The filter function returns %TRUE if the request should still
383 * require authentication, or %FALSE if authentication is unnecessary
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.
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.
402 soup_auth_domain_set_filter (SoupAuthDomain *domain,
403 SoupAuthDomainFilter filter,
404 gpointer filter_data,
405 GDestroyNotify dnotify)
407 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
409 if (priv->filter_dnotify)
410 priv->filter_dnotify (priv->filter_data);
412 priv->filter = filter;
413 priv->filter_data = filter_data;
414 priv->filter_dnotify = dnotify;
416 g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER);
417 g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA);
421 * soup_auth_domain_get_realm:
422 * @domain: a #SoupAuthDomain
424 * Gets the realm name associated with @domain
426 * Return value: @domain's realm
429 soup_auth_domain_get_realm (SoupAuthDomain *domain)
431 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
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()
444 * The prototype for a #SoupAuthDomain generic authentication callback.
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.
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.
461 * Return value: %TRUE if @msg is authenticated, %FALSE if not.
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
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.
480 soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
481 SoupAuthDomainGenericAuthCallback auth_callback,
483 GDestroyNotify dnotify)
485 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
487 if (priv->auth_dnotify)
488 priv->auth_dnotify (priv->auth_data);
490 priv->auth_callback = auth_callback;
491 priv->auth_data = auth_data;
492 priv->auth_dnotify = dnotify;
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);
499 soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
501 const char *username)
503 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
505 if (priv->auth_callback)
506 return priv->auth_callback (domain, msg, username, priv->auth_data);
512 * soup_auth_domain_check_password:
513 * @domain: a #SoupAuthDomain
514 * @msg: a #SoupMessage
515 * @username: a username
516 * @password: a password
518 * Checks if @msg authenticates to @domain via @username and
519 * @password. This would normally be called from a
520 * #SoupAuthDomainGenericAuthCallback.
522 * Return value: whether or not the message is authenticated
525 soup_auth_domain_check_password (SoupAuthDomain *domain,
527 const char *username,
528 const char *password)
530 return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->check_password (domain, msg,
536 * soup_auth_domain_covers:
537 * @domain: a #SoupAuthDomain
538 * @msg: a #SoupMessage
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.
545 * This is used by #SoupServer internally and is probably of no use to
548 * Return value: %TRUE if @domain requires @msg to be authenticated
551 soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg)
553 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
557 path = soup_message_get_uri (msg)->path;
558 if (!soup_path_map_lookup (priv->paths, path))
562 if (priv->filter && !priv->filter (domain, msg, priv->filter_data))
569 * soup_auth_domain_accepts:
570 * @domain: a #SoupAuthDomain
571 * @msg: a #SoupMessage
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
578 * This is used by #SoupServer internally and is probably of no use to
581 * Return value: the username that @msg has authenticated as, if in
582 * fact it has authenticated. %NULL otherwise.
585 soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg)
587 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
590 header = soup_message_headers_get_one (msg->request_headers,
592 "Proxy-Authorization" :
596 return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header);
600 * soup_auth_domain_challenge:
601 * @domain: a #SoupAuthDomain
602 * @msg: a #SoupMessage
604 * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg,
605 * requesting that the client authenticate, and sets @msg's status
608 * This is used by #SoupServer internally and is probably of no use to
612 soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg)
614 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
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,
623 "Proxy-Authenticate" :