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"
16 #include "soup-path-map.h"
19 * SECTION:soup-auth-domain
20 * @short_description: Server-side authentication
21 * @see_also: #SoupServer
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().
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.)
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.
51 PROP_GENERIC_AUTH_CALLBACK,
52 PROP_GENERIC_AUTH_DATA,
62 SoupAuthDomainFilter filter;
64 GDestroyNotify filter_dnotify;
66 SoupAuthDomainGenericAuthCallback auth_callback;
68 GDestroyNotify auth_dnotify;
70 } SoupAuthDomainPrivate;
72 #define SOUP_AUTH_DOMAIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainPrivate))
74 G_DEFINE_ABSTRACT_TYPE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT)
77 soup_auth_domain_init (SoupAuthDomain *domain)
79 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
81 priv->paths = soup_path_map_new (NULL);
85 soup_auth_domain_finalize (GObject *object)
87 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
90 soup_path_map_free (priv->paths);
92 if (priv->filter_dnotify)
93 priv->filter_dnotify (priv->filter_data);
94 if (priv->auth_dnotify)
95 priv->auth_dnotify (priv->auth_data);
97 G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object);
101 soup_auth_domain_set_property (GObject *object, guint prop_id,
102 const GValue *value, GParamSpec *pspec)
104 SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object);
105 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
109 g_free (priv->realm);
110 priv->realm = g_value_dup_string (value);
113 priv->proxy = g_value_get_boolean (value);
116 soup_auth_domain_add_path (auth_domain,
117 g_value_get_string (value));
119 case PROP_REMOVE_PATH:
120 soup_auth_domain_remove_path (auth_domain,
121 g_value_get_string (value));
124 priv->filter = g_value_get_pointer (value);
126 case PROP_FILTER_DATA:
127 if (priv->filter_dnotify) {
128 priv->filter_dnotify (priv->filter_data);
129 priv->filter_dnotify = NULL;
131 priv->filter_data = g_value_get_pointer (value);
133 case PROP_GENERIC_AUTH_CALLBACK:
134 priv->auth_callback = g_value_get_pointer (value);
136 case PROP_GENERIC_AUTH_DATA:
137 if (priv->auth_dnotify) {
138 priv->auth_dnotify (priv->auth_data);
139 priv->auth_dnotify = NULL;
141 priv->auth_data = g_value_get_pointer (value);
144 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150 soup_auth_domain_get_property (GObject *object, guint prop_id,
151 GValue *value, GParamSpec *pspec)
153 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object);
157 g_value_set_string (value, priv->realm);
160 g_value_set_boolean (value, priv->proxy);
163 g_value_set_pointer (value, priv->filter);
165 case PROP_FILTER_DATA:
166 g_value_set_pointer (value, priv->filter_data);
168 case PROP_GENERIC_AUTH_CALLBACK:
169 g_value_set_pointer (value, priv->auth_callback);
171 case PROP_GENERIC_AUTH_DATA:
172 g_value_set_pointer (value, priv->auth_data);
175 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181 soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class)
183 GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class);
185 g_type_class_add_private (auth_domain_class, sizeof (SoupAuthDomainPrivate));
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;
192 * SOUP_AUTH_DOMAIN_REALM:
194 * Alias for the #SoupAuthDomain:realm property. (The realm of
197 g_object_class_install_property (
198 object_class, PROP_REALM,
199 g_param_spec_string (SOUP_AUTH_DOMAIN_REALM,
201 "The realm of this auth domain",
203 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
205 * SOUP_AUTH_DOMAIN_PROXY:
207 * Alias for the #SoupAuthDomain:proxy property. (Whether or
208 * not this is a proxy auth domain.)
210 g_object_class_install_property (
211 object_class, PROP_PROXY,
212 g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY,
214 "Whether or not this is a proxy auth domain",
216 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
218 * SOUP_AUTH_DOMAIN_ADD_PATH:
220 * Alias for the #SoupAuthDomain:add-path property. (Shortcut
221 * for calling soup_auth_domain_add_path().)
223 g_object_class_install_property (
224 object_class, PROP_ADD_PATH,
225 g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH,
227 "Add a path covered by this auth domain",
231 * SOUP_AUTH_DOMAIN_REMOVE_PATH:
233 * Alias for the #SoupAuthDomain:remove-path property.
234 * (Shortcut for calling soup_auth_domain_remove_path().)
236 g_object_class_install_property (
237 object_class, PROP_REMOVE_PATH,
238 g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH,
240 "Remove a path covered by this auth domain",
244 * SOUP_AUTH_DOMAIN_FILTER:
246 * Alias for the #SoupAuthDomain:filter property. (The
247 * #SoupAuthDomainFilter for the domain.)
249 g_object_class_install_property (
250 object_class, PROP_FILTER,
251 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER,
253 "A filter for deciding whether or not to require authentication",
256 * SOUP_AUTH_DOMAIN_FILTER_DATA:
258 * Alias for the #SoupAuthDomain:filter-data property. (Data
259 * to pass to the #SoupAuthDomainFilter.)
261 g_object_class_install_property (
262 object_class, PROP_FILTER_DATA,
263 g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA,
265 "Data to pass to filter",
268 * SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK:
270 * Alias for the #SoupAuthDomain:generic-auth-callback property.
271 * (The #SoupAuthDomainGenericAuthCallback.)
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",
280 * SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA:
282 * Alias for the #SoupAuthDomain:generic-auth-data property.
283 * (The data to pass to the #SoupAuthDomainGenericAuthCallback.)
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",
294 * soup_auth_domain_add_path:
295 * @domain: a #SoupAuthDomain
296 * @path: the path to add to @domain
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()).
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
307 soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path)
309 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
311 /* "" should not match "*" */
315 soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE));
319 * soup_auth_domain_remove_path:
320 * @domain: a #SoupAuthDomain
321 * @path: the path to remove from @domain
323 * Removes @path from @domain, such that requests under @path on
324 * @domain's server will NOT require authentication.
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.
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.
339 soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path)
341 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
343 /* "" should not match "*" */
347 soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE));
351 * SoupAuthDomainFilter:
352 * @domain: a #SoupAuthDomain
353 * @msg: a #SoupMessage
354 * @user_data: the data passed to soup_auth_domain_set_filter()
356 * The prototype for a #SoupAuthDomain filter; see
357 * soup_auth_domain_set_filter() for details.
359 * Return value: %TRUE if @msg requires authentication, %FALSE if not.
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
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.
376 * The filter function returns %TRUE if the request should still
377 * require authentication, or %FALSE if authentication is unnecessary
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.
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.
396 soup_auth_domain_set_filter (SoupAuthDomain *domain,
397 SoupAuthDomainFilter filter,
398 gpointer filter_data,
399 GDestroyNotify dnotify)
401 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
403 if (priv->filter_dnotify)
404 priv->filter_dnotify (priv->filter_data);
406 priv->filter = filter;
407 priv->filter_data = filter_data;
408 priv->filter_dnotify = dnotify;
410 g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER);
411 g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA);
415 * soup_auth_domain_get_realm:
416 * @domain: a #SoupAuthDomain
418 * Gets the realm name associated with @domain
420 * Return value: @domain's realm
423 soup_auth_domain_get_realm (SoupAuthDomain *domain)
425 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
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()
438 * The prototype for a #SoupAuthDomain generic authentication callback.
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.
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.
455 * Return value: %TRUE if @msg is authenticated, %FALSE if not.
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
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.
474 soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
475 SoupAuthDomainGenericAuthCallback auth_callback,
477 GDestroyNotify dnotify)
479 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
481 if (priv->auth_dnotify)
482 priv->auth_dnotify (priv->auth_data);
484 priv->auth_callback = auth_callback;
485 priv->auth_data = auth_data;
486 priv->auth_dnotify = dnotify;
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);
493 soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
495 const char *username)
497 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
499 if (priv->auth_callback)
500 return priv->auth_callback (domain, msg, username, priv->auth_data);
506 * soup_auth_domain_check_password:
507 * @domain: a #SoupAuthDomain
508 * @msg: a #SoupMessage
509 * @username: a username
510 * @password: a password
512 * Checks if @msg authenticates to @domain via @username and
513 * @password. This would normally be called from a
514 * #SoupAuthDomainGenericAuthCallback.
516 * Return value: whether or not the message is authenticated
519 soup_auth_domain_check_password (SoupAuthDomain *domain,
521 const char *username,
522 const char *password)
524 return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->check_password (domain, msg,
530 * soup_auth_domain_covers:
531 * @domain: a #SoupAuthDomain
532 * @msg: a #SoupMessage
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.
539 * This is used by #SoupServer internally and is probably of no use to
542 * Return value: %TRUE if @domain requires @msg to be authenticated
545 soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg)
547 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
551 path = soup_message_get_uri (msg)->path;
552 if (!soup_path_map_lookup (priv->paths, path))
556 if (priv->filter && !priv->filter (domain, msg, priv->filter_data))
563 * soup_auth_domain_accepts:
564 * @domain: a #SoupAuthDomain
565 * @msg: a #SoupMessage
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
572 * This is used by #SoupServer internally and is probably of no use to
575 * Return value: the username that @msg has authenticated as, if in
576 * fact it has authenticated. %NULL otherwise.
579 soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg)
581 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
584 header = soup_message_headers_get_one (msg->request_headers,
586 "Proxy-Authorization" :
590 return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header);
594 * soup_auth_domain_challenge:
595 * @domain: a #SoupAuthDomain
596 * @msg: a #SoupMessage
598 * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg,
599 * requesting that the client authenticate, and sets @msg's status
602 * This is used by #SoupServer internally and is probably of no use to
606 soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg)
608 SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain);
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,
617 "Proxy-Authenticate" :