1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * soup-auth.c: HTTP Authentication framework
4 *
5 * Copyright (C) 2001-2003, Ximian, Inc.
6 */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13
14 #include "soup-auth.h"
15 #include "soup.h"
16 #include "soup-connection-auth.h"
17
18 /**
19 * SECTION:soup-auth
20 * @short_description: HTTP client-side authentication support
21 * @see_also: #SoupSession
22 *
23 * #SoupAuth objects store the authentication data associated with a
24 * given bit of web space. They are created automatically by
25 * #SoupSession.
26 **/
27
28 /**
29 * SoupAuth:
30 *
31 * The abstract base class for handling authentication. Specific HTTP
32 * Authentication mechanisms are implemented by its subclasses, but
33 * applications never need to be aware of the specific subclasses
34 * being used.
35 **/
36
37 typedef struct {
38 gboolean proxy;
39 char *host;
40 } SoupAuthPrivate;
41
42 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SoupAuth, soup_auth, G_TYPE_OBJECT)
43
44 enum {
45 PROP_0,
46
47 PROP_SCHEME_NAME,
48 PROP_REALM,
49 PROP_HOST,
50 PROP_IS_FOR_PROXY,
51 PROP_IS_AUTHENTICATED,
52
53 LAST_PROP
54 };
55
56 static void
soup_auth_init(SoupAuth * auth)57 soup_auth_init (SoupAuth *auth)
58 {
59 }
60
61 static void
soup_auth_finalize(GObject * object)62 soup_auth_finalize (GObject *object)
63 {
64 SoupAuth *auth = SOUP_AUTH (object);
65 SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
66
67 g_free (auth->realm);
68 g_free (priv->host);
69
70 G_OBJECT_CLASS (soup_auth_parent_class)->finalize (object);
71 }
72
73 static void
soup_auth_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)74 soup_auth_set_property (GObject *object, guint prop_id,
75 const GValue *value, GParamSpec *pspec)
76 {
77 SoupAuth *auth = SOUP_AUTH (object);
78 SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
79
80 switch (prop_id) {
81 case PROP_REALM:
82 g_free (auth->realm);
83 auth->realm = g_value_dup_string (value);
84 break;
85 case PROP_HOST:
86 g_free (priv->host);
87 priv->host = g_value_dup_string (value);
88 break;
89 case PROP_IS_FOR_PROXY:
90 priv->proxy = g_value_get_boolean (value);
91 break;
92 default:
93 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
94 break;
95 }
96 }
97
98 static void
soup_auth_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)99 soup_auth_get_property (GObject *object, guint prop_id,
100 GValue *value, GParamSpec *pspec)
101 {
102 SoupAuth *auth = SOUP_AUTH (object);
103 SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
104
105 switch (prop_id) {
106 case PROP_SCHEME_NAME:
107 g_value_set_string (value, soup_auth_get_scheme_name (auth));
108 break;
109 case PROP_REALM:
110 g_value_set_string (value, soup_auth_get_realm (auth));
111 break;
112 case PROP_HOST:
113 g_value_set_string (value, soup_auth_get_host (auth));
114 break;
115 case PROP_IS_FOR_PROXY:
116 g_value_set_boolean (value, priv->proxy);
117 break;
118 case PROP_IS_AUTHENTICATED:
119 g_value_set_boolean (value, soup_auth_is_authenticated (auth));
120 break;
121 default:
122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123 break;
124 }
125 }
126
127 static gboolean
auth_can_authenticate(SoupAuth * auth)128 auth_can_authenticate (SoupAuth *auth)
129 {
130 return TRUE;
131 }
132
133 static void
soup_auth_class_init(SoupAuthClass * auth_class)134 soup_auth_class_init (SoupAuthClass *auth_class)
135 {
136 GObjectClass *object_class = G_OBJECT_CLASS (auth_class);
137
138 auth_class->can_authenticate = auth_can_authenticate;
139
140 object_class->finalize = soup_auth_finalize;
141 object_class->set_property = soup_auth_set_property;
142 object_class->get_property = soup_auth_get_property;
143
144 /* properties */
145 /**
146 * SOUP_AUTH_SCHEME_NAME:
147 *
148 * An alias for the #SoupAuth:scheme-name property. (The
149 * authentication scheme name.)
150 **/
151 g_object_class_install_property (
152 object_class, PROP_SCHEME_NAME,
153 g_param_spec_string (SOUP_AUTH_SCHEME_NAME,
154 "Scheme name",
155 "Authentication scheme name",
156 NULL,
157 G_PARAM_READABLE |
158 G_PARAM_STATIC_STRINGS));
159 /**
160 * SOUP_AUTH_REALM:
161 *
162 * An alias for the #SoupAuth:realm property. (The
163 * authentication realm.)
164 **/
165 g_object_class_install_property (
166 object_class, PROP_REALM,
167 g_param_spec_string (SOUP_AUTH_REALM,
168 "Realm",
169 "Authentication realm",
170 NULL,
171 G_PARAM_READWRITE |
172 G_PARAM_STATIC_STRINGS));
173 /**
174 * SOUP_AUTH_HOST:
175 *
176 * An alias for the #SoupAuth:host property. (The
177 * host being authenticated to.)
178 **/
179 g_object_class_install_property (
180 object_class, PROP_HOST,
181 g_param_spec_string (SOUP_AUTH_HOST,
182 "Host",
183 "Authentication host",
184 NULL,
185 G_PARAM_READWRITE |
186 G_PARAM_STATIC_STRINGS));
187 /**
188 * SOUP_AUTH_IS_FOR_PROXY:
189 *
190 * An alias for the #SoupAuth:is-for-proxy property. (Whether
191 * or not the auth is for a proxy server.)
192 **/
193 g_object_class_install_property (
194 object_class, PROP_IS_FOR_PROXY,
195 g_param_spec_boolean (SOUP_AUTH_IS_FOR_PROXY,
196 "For Proxy",
197 "Whether or not the auth is for a proxy server",
198 FALSE,
199 G_PARAM_READWRITE |
200 G_PARAM_STATIC_STRINGS));
201 /**
202 * SOUP_AUTH_IS_AUTHENTICATED:
203 *
204 * An alias for the #SoupAuth:is-authenticated property.
205 * (Whether or not the auth has been authenticated.)
206 **/
207 g_object_class_install_property (
208 object_class, PROP_IS_AUTHENTICATED,
209 g_param_spec_boolean (SOUP_AUTH_IS_AUTHENTICATED,
210 "Authenticated",
211 "Whether or not the auth is authenticated",
212 FALSE,
213 G_PARAM_READABLE |
214 G_PARAM_STATIC_STRINGS));
215 }
216
217 /**
218 * soup_auth_new:
219 * @type: the type of auth to create (a subtype of #SoupAuth)
220 * @msg: the #SoupMessage the auth is being created for
221 * @auth_header: the WWW-Authenticate/Proxy-Authenticate header
222 *
223 * Creates a new #SoupAuth of type @type with the information from
224 * @msg and @auth_header.
225 *
226 * This is called by #SoupSession; you will normally not create auths
227 * yourself.
228 *
229 * Return value: (nullable): the new #SoupAuth, or %NULL if it could
230 * not be created
231 **/
232 SoupAuth *
soup_auth_new(GType type,SoupMessage * msg,const char * auth_header)233 soup_auth_new (GType type, SoupMessage *msg, const char *auth_header)
234 {
235 SoupAuth *auth;
236 GHashTable *params;
237 const char *scheme, *realm;
238
239 g_return_val_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH), NULL);
240 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
241 g_return_val_if_fail (auth_header != NULL, NULL);
242
243 auth = g_object_new (type,
244 SOUP_AUTH_IS_FOR_PROXY, (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED),
245 SOUP_AUTH_HOST, soup_message_get_uri (msg)->host,
246 NULL);
247
248 scheme = soup_auth_get_scheme_name (auth);
249 if (g_ascii_strncasecmp (auth_header, scheme, strlen (scheme)) != 0) {
250 g_object_unref (auth);
251 return NULL;
252 }
253
254 params = soup_header_parse_param_list (auth_header + strlen (scheme));
255 if (!params)
256 params = g_hash_table_new (NULL, NULL);
257
258 realm = g_hash_table_lookup (params, "realm");
259 if (realm)
260 auth->realm = g_strdup (realm);
261
262 if (!SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params)) {
263 g_object_unref (auth);
264 auth = NULL;
265 }
266 soup_header_free_param_list (params);
267 return auth;
268 }
269
270 /**
271 * soup_auth_update:
272 * @auth: a #SoupAuth
273 * @msg: the #SoupMessage @auth is being updated for
274 * @auth_header: the WWW-Authenticate/Proxy-Authenticate header
275 *
276 * Updates @auth with the information from @msg and @auth_header,
277 * possibly un-authenticating it. As with soup_auth_new(), this is
278 * normally only used by #SoupSession.
279 *
280 * Return value: %TRUE if @auth is still a valid (but potentially
281 * unauthenticated) #SoupAuth. %FALSE if something about @auth_params
282 * could not be parsed or incorporated into @auth at all.
283 **/
284 gboolean
soup_auth_update(SoupAuth * auth,SoupMessage * msg,const char * auth_header)285 soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header)
286 {
287 GHashTable *params;
288 const char *scheme, *realm;
289 gboolean was_authenticated, success;
290
291 g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
292 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
293 g_return_val_if_fail (auth_header != NULL, FALSE);
294
295 scheme = soup_auth_get_scheme_name (auth);
296 if (g_ascii_strncasecmp (auth_header, scheme, strlen (scheme)) != 0)
297 return FALSE;
298
299 params = soup_header_parse_param_list (auth_header + strlen (scheme));
300 if (!params)
301 params = g_hash_table_new (NULL, NULL);
302
303 realm = g_hash_table_lookup (params, "realm");
304 if (realm && auth->realm && strcmp (realm, auth->realm) != 0) {
305 soup_header_free_param_list (params);
306 return FALSE;
307 }
308
309 was_authenticated = soup_auth_is_authenticated (auth);
310 success = SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params);
311 if (was_authenticated != soup_auth_is_authenticated (auth))
312 g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED);
313 soup_header_free_param_list (params);
314 return success;
315 }
316
317 /**
318 * soup_auth_authenticate:
319 * @auth: a #SoupAuth
320 * @username: the username provided by the user or client
321 * @password: the password provided by the user or client
322 *
323 * Call this on an auth to authenticate it; normally this will cause
324 * the auth's message to be requeued with the new authentication info.
325 **/
326 void
soup_auth_authenticate(SoupAuth * auth,const char * username,const char * password)327 soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password)
328 {
329 gboolean was_authenticated;
330
331 g_return_if_fail (SOUP_IS_AUTH (auth));
332 g_return_if_fail (username != NULL);
333 g_return_if_fail (password != NULL);
334
335 was_authenticated = soup_auth_is_authenticated (auth);
336 SOUP_AUTH_GET_CLASS (auth)->authenticate (auth, username, password);
337 if (was_authenticated != soup_auth_is_authenticated (auth))
338 g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED);
339 }
340
341 /**
342 * soup_auth_is_for_proxy:
343 * @auth: a #SoupAuth
344 *
345 * Tests whether or not @auth is associated with a proxy server rather
346 * than an "origin" server.
347 *
348 * Return value: %TRUE or %FALSE
349 **/
350 gboolean
soup_auth_is_for_proxy(SoupAuth * auth)351 soup_auth_is_for_proxy (SoupAuth *auth)
352 {
353 SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
354
355 g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
356
357 return priv->proxy;
358 }
359
360 /**
361 * soup_auth_get_scheme_name:
362 * @auth: a #SoupAuth
363 *
364 * Returns @auth's scheme name. (Eg, "Basic", "Digest", or "NTLM")
365 *
366 * Return value: the scheme name
367 **/
368 const char *
soup_auth_get_scheme_name(SoupAuth * auth)369 soup_auth_get_scheme_name (SoupAuth *auth)
370 {
371 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
372
373 return SOUP_AUTH_GET_CLASS (auth)->scheme_name;
374 }
375
376 /**
377 * soup_auth_get_host:
378 * @auth: a #SoupAuth
379 *
380 * Returns the host that @auth is associated with.
381 *
382 * Return value: the hostname
383 **/
384 const char *
soup_auth_get_host(SoupAuth * auth)385 soup_auth_get_host (SoupAuth *auth)
386 {
387 SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
388
389 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
390
391 return priv->host;
392 }
393
394 /**
395 * soup_auth_get_realm:
396 * @auth: a #SoupAuth
397 *
398 * Returns @auth's realm. This is an identifier that distinguishes
399 * separate authentication spaces on a given server, and may be some
400 * string that is meaningful to the user. (Although it is probably not
401 * localized.)
402 *
403 * Return value: the realm name
404 **/
405 const char *
soup_auth_get_realm(SoupAuth * auth)406 soup_auth_get_realm (SoupAuth *auth)
407 {
408 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
409
410 return auth->realm;
411 }
412
413 /**
414 * soup_auth_get_info:
415 * @auth: a #SoupAuth
416 *
417 * Gets an opaque identifier for @auth, for use as a hash key or the
418 * like. #SoupAuth objects from the same server with the same
419 * identifier refer to the same authentication domain (eg, the URLs
420 * associated with them take the same usernames and passwords).
421 *
422 * Return value: the identifier
423 **/
424 char *
soup_auth_get_info(SoupAuth * auth)425 soup_auth_get_info (SoupAuth *auth)
426 {
427 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
428
429 if (SOUP_IS_CONNECTION_AUTH (auth))
430 return g_strdup (SOUP_AUTH_GET_CLASS (auth)->scheme_name);
431 else {
432 return g_strdup_printf ("%s:%s",
433 SOUP_AUTH_GET_CLASS (auth)->scheme_name,
434 auth->realm);
435 }
436 }
437
438 /**
439 * soup_auth_is_authenticated:
440 * @auth: a #SoupAuth
441 *
442 * Tests if @auth has been given a username and password
443 *
444 * Return value: %TRUE if @auth has been given a username and password
445 **/
446 gboolean
soup_auth_is_authenticated(SoupAuth * auth)447 soup_auth_is_authenticated (SoupAuth *auth)
448 {
449 g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE);
450
451 return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth);
452 }
453
454 /**
455 * soup_auth_get_authorization:
456 * @auth: a #SoupAuth
457 * @msg: the #SoupMessage to be authorized
458 *
459 * Generates an appropriate "Authorization" header for @msg. (The
460 * session will only call this if soup_auth_is_authenticated()
461 * returned %TRUE.)
462 *
463 * Return value: the "Authorization" header, which must be freed.
464 **/
465 char *
soup_auth_get_authorization(SoupAuth * auth,SoupMessage * msg)466 soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg)
467 {
468 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
469 g_return_val_if_fail (msg != NULL, NULL);
470
471 return SOUP_AUTH_GET_CLASS (auth)->get_authorization (auth, msg);
472 }
473
474 /**
475 * soup_auth_is_ready:
476 * @auth: a #SoupAuth
477 * @msg: a #SoupMessage
478 *
479 * Tests if @auth is ready to make a request for @msg with. For most
480 * auths, this is equivalent to soup_auth_is_authenticated(), but for
481 * some auth types (eg, NTLM), the auth may be sendable (eg, as an
482 * authentication request) even before it is authenticated.
483 *
484 * Return value: %TRUE if @auth is ready to make a request with.
485 *
486 * Since: 2.42
487 **/
488 gboolean
soup_auth_is_ready(SoupAuth * auth,SoupMessage * msg)489 soup_auth_is_ready (SoupAuth *auth,
490 SoupMessage *msg)
491 {
492 g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE);
493 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), TRUE);
494
495 if (SOUP_AUTH_GET_CLASS (auth)->is_ready)
496 return SOUP_AUTH_GET_CLASS (auth)->is_ready (auth, msg);
497 else
498 return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth);
499 }
500
501 /**
502 * soup_auth_can_authenticate:
503 * @auth: a #SoupAuth
504 *
505 * Tests if @auth is able to authenticate by providing credentials to the
506 * soup_auth_authenticate().
507 *
508 * Return value: %TRUE if @auth is able to accept credentials.
509 *
510 * Since: 2.54
511 **/
512 gboolean
soup_auth_can_authenticate(SoupAuth * auth)513 soup_auth_can_authenticate (SoupAuth *auth)
514 {
515 g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
516
517 return SOUP_AUTH_GET_CLASS (auth)->can_authenticate (auth);
518 }
519
520 /**
521 * soup_auth_get_protection_space:
522 * @auth: a #SoupAuth
523 * @source_uri: the URI of the request that @auth was generated in
524 * response to.
525 *
526 * Returns a list of paths on the server which @auth extends over.
527 * (All subdirectories of these paths are also assumed to be part
528 * of @auth's protection space, unless otherwise discovered not to
529 * be.)
530 *
531 * Return value: (element-type utf8) (transfer full): the list of
532 * paths, which can be freed with soup_auth_free_protection_space().
533 **/
534 GSList *
soup_auth_get_protection_space(SoupAuth * auth,SoupURI * source_uri)535 soup_auth_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
536 {
537 g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
538 g_return_val_if_fail (source_uri != NULL, NULL);
539
540 return SOUP_AUTH_GET_CLASS (auth)->get_protection_space (auth, source_uri);
541 }
542
543 /**
544 * soup_auth_free_protection_space: (skip)
545 * @auth: a #SoupAuth
546 * @space: the return value from soup_auth_get_protection_space()
547 *
548 * Frees @space.
549 **/
550 void
soup_auth_free_protection_space(SoupAuth * auth,GSList * space)551 soup_auth_free_protection_space (SoupAuth *auth, GSList *space)
552 {
553 g_slist_free_full (space, g_free);
554 }
555
556 /**
557 * soup_auth_get_saved_users:
558 *
559 * Return value: (transfer full) (element-type utf8):
560 */
561 GSList *
soup_auth_get_saved_users(SoupAuth * auth)562 soup_auth_get_saved_users (SoupAuth *auth)
563 {
564 return NULL;
565 }
566
567 const char *
soup_auth_get_saved_password(SoupAuth * auth,const char * user)568 soup_auth_get_saved_password (SoupAuth *auth, const char *user)
569 {
570 return NULL;
571 }
572
573 void
soup_auth_has_saved_password(SoupAuth * auth,const char * username,const char * password)574 soup_auth_has_saved_password (SoupAuth *auth, const char *username,
575 const char *password)
576 {
577 }
578
579 void
soup_auth_save_password(SoupAuth * auth,const char * username,const char * password)580 soup_auth_save_password (SoupAuth *auth, const char *username,
581 const char *password)
582 {
583 }
584