/src/libsoup/libsoup/auth/soup-auth-manager.c
Line | Count | Source |
1 | | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
2 | | /* |
3 | | * soup-auth-manager.c: SoupAuth manager for SoupSession |
4 | | * |
5 | | * Copyright (C) 2007 Red Hat, Inc. |
6 | | */ |
7 | | |
8 | | #ifdef HAVE_CONFIG_H |
9 | | #include <config.h> |
10 | | #endif |
11 | | |
12 | | #include <string.h> |
13 | | |
14 | | #include "soup-auth-manager.h" |
15 | | #include "soup.h" |
16 | | #include "soup-connection-auth.h" |
17 | | #include "soup-message-private.h" |
18 | | #include "soup-connection.h" |
19 | | #include "soup-message-headers-private.h" |
20 | | #include "soup-path-map.h" |
21 | | #include "soup-session-private.h" |
22 | | #include "soup-session-feature-private.h" |
23 | | #include "soup-uri-utils-private.h" |
24 | | |
25 | | /** |
26 | | * SoupAuthManager: |
27 | | * |
28 | | * HTTP client-side authentication handler. |
29 | | * |
30 | | * [class@AuthManager] is the [iface@SessionFeature] that handles HTTP |
31 | | * authentication for a [class@Session]. |
32 | | * |
33 | | * A [class@AuthManager] is added to the session by default, and normally |
34 | | * you don't need to worry about it at all. However, if you want to |
35 | | * disable HTTP authentication, you can remove the feature from the |
36 | | * session with [method@Session.remove_feature_by_type] or disable it on |
37 | | * individual requests with [method@Message.disable_feature]. |
38 | | * |
39 | | * You can use this with [method@Session.remove_feature_by_type] or |
40 | | * [method@Message.disable_feature]. |
41 | | * |
42 | | * (Although this type has only been publicly visible since libsoup 2.42, it has |
43 | | * always existed in the background, and you can use `g_type_from_name |
44 | | * ("SoupAuthManager")` to get its [alias@GObject.Type] in earlier releases.) |
45 | | **/ |
46 | | static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); |
47 | | |
48 | | struct _SoupAuthManager { |
49 | | GObject parent_instance; |
50 | | }; |
51 | | |
52 | | typedef struct { |
53 | | SoupSession *session; |
54 | | GPtrArray *auth_types; |
55 | | |
56 | | SoupAuth *proxy_auth; |
57 | | char *proxy_auth_authority; |
58 | | GMutex mutex; |
59 | | GHashTable *auth_hosts; |
60 | | } SoupAuthManagerPrivate; |
61 | | |
62 | | typedef struct { |
63 | | GUri *uri; |
64 | | SoupPathMap *auth_realms; /* path -> scheme:realm */ |
65 | | GHashTable *auths; /* scheme:realm -> SoupAuth */ |
66 | | } SoupAuthHost; |
67 | | |
68 | 0 | G_DEFINE_FINAL_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT, |
69 | 0 | G_ADD_PRIVATE (SoupAuthManager) |
70 | 0 | G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, |
71 | 0 | soup_auth_manager_session_feature_init)) |
72 | 0 |
|
73 | 0 | static void soup_auth_host_free (SoupAuthHost *host); |
74 | 0 | static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv, |
75 | 0 | GUri *uri, SoupAuth *auth, |
76 | 0 | gboolean prior_auth_failed); |
77 | 0 |
|
78 | 0 | static void |
79 | 0 | soup_auth_manager_init (SoupAuthManager *manager) |
80 | 0 | { |
81 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
82 | |
|
83 | 0 | priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref); |
84 | 0 | priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash, |
85 | 0 | soup_uri_host_equal, |
86 | 0 | NULL, |
87 | 0 | (GDestroyNotify)soup_auth_host_free); |
88 | 0 | g_mutex_init (&priv->mutex); |
89 | 0 | } |
90 | | |
91 | | static void |
92 | | soup_auth_manager_finalize (GObject *object) |
93 | 0 | { |
94 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)object); |
95 | |
|
96 | 0 | g_ptr_array_free (priv->auth_types, TRUE); |
97 | |
|
98 | 0 | g_hash_table_destroy (priv->auth_hosts); |
99 | |
|
100 | 0 | g_clear_object (&priv->proxy_auth); |
101 | 0 | g_clear_pointer (&priv->proxy_auth_authority, g_free); |
102 | |
|
103 | 0 | g_mutex_clear (&priv->mutex); |
104 | |
|
105 | 0 | G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object); |
106 | 0 | } |
107 | | |
108 | | static void |
109 | | soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class) |
110 | 0 | { |
111 | 0 | GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class); |
112 | |
|
113 | 0 | object_class->finalize = soup_auth_manager_finalize; |
114 | 0 | } |
115 | | |
116 | | static int |
117 | | auth_type_compare_func (gconstpointer a, gconstpointer b) |
118 | 0 | { |
119 | 0 | SoupAuthClass **auth1 = (SoupAuthClass **)a; |
120 | 0 | SoupAuthClass **auth2 = (SoupAuthClass **)b; |
121 | |
|
122 | 0 | return (*auth1)->strength - (*auth2)->strength; |
123 | 0 | } |
124 | | |
125 | | static gboolean |
126 | | soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) |
127 | 0 | { |
128 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature); |
129 | 0 | SoupAuthClass *auth_class; |
130 | |
|
131 | 0 | if (!g_type_is_a (type, SOUP_TYPE_AUTH)) |
132 | 0 | return FALSE; |
133 | | |
134 | 0 | auth_class = g_type_class_ref (type); |
135 | 0 | g_ptr_array_add (priv->auth_types, auth_class); |
136 | 0 | g_ptr_array_sort (priv->auth_types, auth_type_compare_func); |
137 | |
|
138 | 0 | return TRUE; |
139 | 0 | } |
140 | | |
141 | | static gboolean |
142 | | soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) |
143 | 0 | { |
144 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature); |
145 | 0 | SoupAuthClass *auth_class; |
146 | 0 | guint i; |
147 | |
|
148 | 0 | if (!g_type_is_a (type, SOUP_TYPE_AUTH)) |
149 | 0 | return FALSE; |
150 | | |
151 | 0 | auth_class = g_type_class_peek (type); |
152 | |
|
153 | 0 | for (i = 0; i < priv->auth_types->len; i++) { |
154 | 0 | if (priv->auth_types->pdata[i] == (gpointer)auth_class) { |
155 | 0 | g_ptr_array_remove_index (priv->auth_types, i); |
156 | 0 | return TRUE; |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | 0 | return FALSE; |
161 | 0 | } |
162 | | |
163 | | static gboolean |
164 | | soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) |
165 | 0 | { |
166 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature); |
167 | 0 | SoupAuthClass *auth_class; |
168 | 0 | guint i; |
169 | |
|
170 | 0 | if (!g_type_is_a (type, SOUP_TYPE_AUTH)) |
171 | 0 | return FALSE; |
172 | | |
173 | 0 | auth_class = g_type_class_peek (type); |
174 | 0 | for (i = 0; i < priv->auth_types->len; i++) { |
175 | 0 | if (priv->auth_types->pdata[i] == (gpointer)auth_class) |
176 | 0 | return TRUE; |
177 | 0 | } |
178 | 0 | return FALSE; |
179 | 0 | } |
180 | | |
181 | | static void |
182 | | soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session) |
183 | 0 | { |
184 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature); |
185 | | |
186 | | /* FIXME: should support multiple sessions */ |
187 | 0 | priv->session = session; |
188 | 0 | } |
189 | | |
190 | | static inline const char * |
191 | | auth_header_for_message (SoupMessage *msg) |
192 | 0 | { |
193 | 0 | if (soup_message_get_status (msg) == SOUP_STATUS_PROXY_UNAUTHORIZED) { |
194 | 0 | return soup_message_headers_get_list_common (soup_message_get_response_headers (msg), |
195 | 0 | SOUP_HEADER_PROXY_AUTHENTICATE); |
196 | 0 | } else { |
197 | 0 | return soup_message_headers_get_list_common (soup_message_get_response_headers (msg), |
198 | 0 | SOUP_HEADER_WWW_AUTHENTICATE); |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | | static GSList * |
203 | | next_challenge_start (GSList *items) |
204 | 0 | { |
205 | | /* The relevant grammar (from httpbis): |
206 | | * |
207 | | * WWW-Authenticate = 1#challenge |
208 | | * Proxy-Authenticate = 1#challenge |
209 | | * challenge = auth-scheme [ 1*SP ( b64token / #auth-param ) ] |
210 | | * auth-scheme = token |
211 | | * auth-param = token BWS "=" BWS ( token / quoted-string ) |
212 | | * b64token = 1*( ALPHA / DIGIT / |
213 | | * "-" / "." / "_" / "~" / "+" / "/" ) *"=" |
214 | | * |
215 | | * The fact that quoted-strings can contain commas, equals |
216 | | * signs, and auth scheme names makes it tricky to "cheat" on |
217 | | * the parsing. So soup_auth_manager_extract_challenge() will |
218 | | * have used soup_header_parse_list() to split the header into |
219 | | * items. Given the grammar above, the possible items are: |
220 | | * |
221 | | * auth-scheme |
222 | | * auth-scheme 1*SP b64token |
223 | | * auth-scheme 1*SP auth-param |
224 | | * auth-param |
225 | | * |
226 | | * where the first three represent the start of a new challenge and |
227 | | * the last one does not. |
228 | | */ |
229 | |
|
230 | 0 | for (; items; items = items->next) { |
231 | 0 | const char *item = items->data; |
232 | 0 | const char *sp = strpbrk (item, "\t\r\n "); |
233 | 0 | const char *eq = strchr (item, '='); |
234 | |
|
235 | 0 | if (!eq) { |
236 | | /* No "=", so it can't be an auth-param */ |
237 | 0 | return items; |
238 | 0 | } |
239 | 0 | if (!sp || sp > eq) { |
240 | | /* No space, or first space appears after the "=", |
241 | | * so it must be an auth-param. |
242 | | */ |
243 | 0 | continue; |
244 | 0 | } |
245 | 0 | while (g_ascii_isspace (*++sp)) |
246 | 0 | ; |
247 | 0 | if (sp == eq) { |
248 | | /* First "=" appears immediately after the first |
249 | | * space, so this must be an auth-param with |
250 | | * space around the "=". |
251 | | */ |
252 | 0 | continue; |
253 | 0 | } |
254 | | |
255 | | /* "auth-scheme auth-param" or "auth-scheme b64token" */ |
256 | 0 | return items; |
257 | 0 | } |
258 | | |
259 | 0 | return NULL; |
260 | 0 | } |
261 | | |
262 | | static GStrv |
263 | | soup_auth_manager_extract_challenges (const char *challenges, const char *scheme) |
264 | 0 | { |
265 | 0 | GPtrArray *challenge_list = g_ptr_array_new (); |
266 | 0 | GSList *items, *i, *next; |
267 | 0 | int schemelen = strlen (scheme); |
268 | 0 | char *item; |
269 | 0 | GString *challenge; |
270 | |
|
271 | 0 | i = items = soup_header_parse_list (challenges); |
272 | | |
273 | | /* We need to split this list into individual challenges. */ |
274 | 0 | while (i) { |
275 | | /* First item will start with the scheme name, followed by |
276 | | * either nothing, or else a space and then the first |
277 | | * auth-param. |
278 | | */ |
279 | 0 | for (; i; i = next_challenge_start (i->next)) { |
280 | 0 | item = i->data; |
281 | 0 | if (!g_ascii_strncasecmp (item, scheme, schemelen) && |
282 | 0 | (!item[schemelen] || g_ascii_isspace (item[schemelen]))) |
283 | 0 | break; |
284 | 0 | } |
285 | 0 | if (!i) |
286 | 0 | break; |
287 | | |
288 | 0 | next = next_challenge_start (i->next); |
289 | 0 | challenge = g_string_new (item); |
290 | 0 | for (i = i->next; i != next; i = i->next) { |
291 | 0 | item = i->data; |
292 | 0 | g_string_append (challenge, ", "); |
293 | 0 | g_string_append (challenge, item); |
294 | 0 | } |
295 | |
|
296 | 0 | i = next; |
297 | 0 | g_ptr_array_add (challenge_list, g_string_free (challenge, FALSE)); |
298 | 0 | }; |
299 | |
|
300 | 0 | soup_header_free_list (items); |
301 | |
|
302 | 0 | if (challenge_list->len) |
303 | 0 | g_ptr_array_add (challenge_list, NULL); /* Trailing NULL for GStrv. */ |
304 | 0 | return (GStrv)g_ptr_array_free (challenge_list, FALSE); |
305 | 0 | } |
306 | | |
307 | | static SoupAuth * |
308 | | create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) |
309 | 0 | { |
310 | 0 | const char *header; |
311 | 0 | SoupAuthClass *auth_class; |
312 | 0 | GStrv challenges; |
313 | 0 | SoupAuth *auth = NULL; |
314 | 0 | int i; |
315 | |
|
316 | 0 | header = auth_header_for_message (msg); |
317 | 0 | if (!header) |
318 | 0 | return NULL; |
319 | | |
320 | 0 | for (i = priv->auth_types->len - 1; i >= 0; i--) { |
321 | 0 | auth_class = priv->auth_types->pdata[i]; |
322 | 0 | challenges = soup_auth_manager_extract_challenges (header, auth_class->scheme_name); |
323 | 0 | if (!challenges) |
324 | 0 | continue; |
325 | | |
326 | 0 | for (int j = 0; challenges[j]; j++) { |
327 | | /* TODO: We use the first successfully parsed auth, in the future this should |
328 | | * prioritise more secure ones when they are supported. */ |
329 | 0 | auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenges[j]); |
330 | 0 | if (auth) { |
331 | 0 | g_strfreev (challenges); |
332 | 0 | return auth; |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | 0 | g_strfreev (challenges); |
337 | 0 | } |
338 | | |
339 | 0 | return NULL; |
340 | 0 | } |
341 | | |
342 | | static gboolean |
343 | | check_auth (SoupMessage *msg, SoupAuth *auth) |
344 | 0 | { |
345 | 0 | const char *header, *scheme; |
346 | 0 | GStrv challenges = NULL; |
347 | 0 | gboolean ok = TRUE; |
348 | 0 | gboolean a_challenge_was_ok = FALSE; |
349 | |
|
350 | 0 | scheme = soup_auth_get_scheme_name (auth); |
351 | |
|
352 | 0 | header = auth_header_for_message (msg); |
353 | 0 | if (header) |
354 | 0 | challenges = soup_auth_manager_extract_challenges (header, scheme); |
355 | 0 | if (!challenges) { |
356 | 0 | challenges = g_new0 (char*, 2); |
357 | 0 | challenges[0] = g_strdup (scheme); |
358 | 0 | ok = FALSE; |
359 | 0 | } |
360 | |
|
361 | 0 | for (int i = 0; challenges[i]; i++) { |
362 | 0 | if (soup_auth_update (auth, msg, challenges[i])) { |
363 | 0 | a_challenge_was_ok = TRUE; |
364 | 0 | break; |
365 | 0 | } |
366 | 0 | } |
367 | |
|
368 | 0 | if (!a_challenge_was_ok) |
369 | 0 | ok = FALSE; |
370 | |
|
371 | 0 | g_strfreev (challenges); |
372 | 0 | return ok; |
373 | 0 | } |
374 | | |
375 | | static SoupAuthHost * |
376 | | get_auth_host_for_uri (SoupAuthManagerPrivate *priv, GUri *uri) |
377 | 0 | { |
378 | 0 | SoupAuthHost *host; |
379 | |
|
380 | 0 | host = g_hash_table_lookup (priv->auth_hosts, uri); |
381 | 0 | if (host) |
382 | 0 | return host; |
383 | | |
384 | 0 | host = g_slice_new0 (SoupAuthHost); |
385 | 0 | host->uri = soup_uri_copy_host (uri); |
386 | 0 | g_hash_table_insert (priv->auth_hosts, host->uri, host); |
387 | |
|
388 | 0 | return host; |
389 | 0 | } |
390 | | |
391 | | static void |
392 | | soup_auth_host_free (SoupAuthHost *host) |
393 | 0 | { |
394 | 0 | g_clear_pointer (&host->auth_realms, soup_path_map_free); |
395 | 0 | g_clear_pointer (&host->auths, g_hash_table_destroy); |
396 | |
|
397 | 0 | g_uri_unref (host->uri); |
398 | 0 | g_slice_free (SoupAuthHost, host); |
399 | 0 | } |
400 | | |
401 | | static void |
402 | | update_authorization_header (SoupMessage *msg, SoupAuth *auth, gboolean is_proxy) |
403 | 0 | { |
404 | 0 | SoupHeaderName authorization_header = is_proxy ? SOUP_HEADER_PROXY_AUTHORIZATION : SOUP_HEADER_AUTHORIZATION; |
405 | 0 | char *token; |
406 | |
|
407 | 0 | if (soup_message_get_auth (msg)) |
408 | 0 | soup_message_headers_remove_common (soup_message_get_request_headers (msg), authorization_header); |
409 | |
|
410 | 0 | if (!auth) |
411 | 0 | return; |
412 | | |
413 | 0 | token = soup_auth_get_authorization (auth, msg); |
414 | 0 | if (!token) |
415 | 0 | return; |
416 | | |
417 | 0 | soup_message_headers_replace_common (soup_message_get_request_headers (msg), authorization_header, token, SOUP_HEADER_VALUE_TRUSTED); |
418 | 0 | g_free (token); |
419 | 0 | } |
420 | | |
421 | | static SoupAuth * |
422 | | lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) |
423 | 0 | { |
424 | 0 | SoupAuthHost *host; |
425 | 0 | const char *path, *realm; |
426 | 0 | SoupAuth *auth; |
427 | 0 | GUri *uri; |
428 | | |
429 | | /* If the message already has a ready auth, use that instead */ |
430 | 0 | auth = soup_message_get_auth (msg); |
431 | 0 | if (auth && soup_auth_is_ready (auth, msg)) |
432 | 0 | return auth; |
433 | | |
434 | 0 | if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) |
435 | 0 | return NULL; |
436 | | |
437 | 0 | uri = soup_message_get_uri_for_auth (msg); |
438 | 0 | if (!uri) |
439 | 0 | return NULL; |
440 | | |
441 | 0 | host = get_auth_host_for_uri (priv, uri); |
442 | 0 | if (!host->auth_realms) |
443 | 0 | return NULL; |
444 | | |
445 | 0 | path = g_uri_get_path (uri); |
446 | 0 | if (!path) |
447 | 0 | path = "/"; |
448 | 0 | realm = soup_path_map_lookup (host->auth_realms, path); |
449 | 0 | if (realm) |
450 | 0 | return g_hash_table_lookup (host->auths, realm); |
451 | | |
452 | 0 | return NULL; |
453 | 0 | } |
454 | | |
455 | | static char * |
456 | | get_proxy_authority (SoupMessage *msg) |
457 | 0 | { |
458 | 0 | SoupConnection *conn = soup_message_get_connection (msg); |
459 | 0 | GUri *proxy_uri; |
460 | 0 | char *authority = NULL; |
461 | |
|
462 | 0 | if (!conn) |
463 | 0 | return NULL; |
464 | | |
465 | 0 | proxy_uri = soup_connection_get_proxy_uri (conn); |
466 | 0 | if (proxy_uri) |
467 | 0 | authority = g_strdup_printf ("%s:%d", g_uri_get_host (proxy_uri), g_uri_get_port (proxy_uri)); |
468 | |
|
469 | 0 | g_object_unref (conn); |
470 | |
|
471 | 0 | return authority; |
472 | 0 | } |
473 | | |
474 | | static gboolean |
475 | | proxy_auth_matches_msg (SoupAuthManagerPrivate *priv, SoupMessage *msg) |
476 | 0 | { |
477 | 0 | char *authority = get_proxy_authority (msg); |
478 | 0 | gboolean matches; |
479 | |
|
480 | 0 | matches = authority && priv->proxy_auth_authority && |
481 | 0 | g_strcmp0 (authority, priv->proxy_auth_authority) == 0; |
482 | 0 | g_free (authority); |
483 | |
|
484 | 0 | return matches; |
485 | 0 | } |
486 | | |
487 | | static SoupAuth * |
488 | | lookup_proxy_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) |
489 | 0 | { |
490 | 0 | SoupAuth *auth; |
491 | | |
492 | | /* If the message already has a ready auth, use that instead, but only |
493 | | * if it is still for the proxy this connection actually goes through. |
494 | | */ |
495 | 0 | auth = soup_message_get_proxy_auth (msg); |
496 | 0 | if (auth && soup_auth_is_ready (auth, msg) && proxy_auth_matches_msg (priv, msg)) |
497 | 0 | return auth; |
498 | | |
499 | 0 | if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) |
500 | 0 | return NULL; |
501 | | |
502 | | /* Only reuse the cached proxy credentials for the same proxy they were |
503 | | * obtained from, otherwise switching proxies would leak them. |
504 | | */ |
505 | 0 | if (priv->proxy_auth && proxy_auth_matches_msg (priv, msg)) |
506 | 0 | return priv->proxy_auth; |
507 | | |
508 | 0 | return NULL; |
509 | 0 | } |
510 | | |
511 | | static void |
512 | | async_auth_finished (SoupAuth *auth, |
513 | | GParamSpec *pspec, |
514 | | SoupMessage *msg) |
515 | 0 | { |
516 | 0 | SoupSession *session; |
517 | |
|
518 | 0 | session = g_object_steal_data (G_OBJECT (msg), "auth-msg-session"); |
519 | 0 | if (!session) |
520 | 0 | return; |
521 | | |
522 | 0 | soup_session_unpause_message (session, msg); |
523 | 0 | g_object_unref (session); |
524 | 0 | } |
525 | | |
526 | | static void |
527 | | authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, |
528 | | SoupMessage *msg, gboolean prior_auth_failed, |
529 | | gboolean proxy, gboolean can_interact) |
530 | 0 | { |
531 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
532 | 0 | GUri *uri; |
533 | |
|
534 | 0 | if (!soup_auth_can_authenticate (auth)) |
535 | 0 | return; |
536 | | |
537 | 0 | uri = soup_message_get_uri_for_auth (msg); |
538 | | |
539 | | /* If a password is specified explicitly in the URI, use it |
540 | | * even if the auth had previously already been authenticated. |
541 | | */ |
542 | 0 | if (g_uri_get_user (uri)) { |
543 | 0 | const char *password = g_uri_get_password (uri); |
544 | 0 | soup_auth_authenticate (auth, g_uri_get_user (uri), password ? password : ""); |
545 | |
|
546 | 0 | GUri *new_uri = soup_uri_copy (uri, SOUP_URI_USER, NULL, SOUP_URI_PASSWORD, NULL, SOUP_URI_NONE); |
547 | 0 | soup_message_set_uri (msg, new_uri); // QUESTION: This didn't emit a signal previously |
548 | 0 | g_uri_unref (new_uri); |
549 | 0 | } else if (!soup_auth_is_authenticated (auth) && can_interact) { |
550 | 0 | SoupMessage *original_msg; |
551 | 0 | gboolean handled; |
552 | |
|
553 | 0 | original_msg = soup_session_get_original_message_for_authentication (priv->session, |
554 | 0 | msg); |
555 | 0 | handled = soup_message_authenticate (original_msg, auth, prior_auth_failed); |
556 | 0 | if (handled && !soup_auth_is_authenticated (auth) && !soup_auth_is_cancelled (auth)) { |
557 | 0 | soup_session_pause_message (priv->session, msg); |
558 | 0 | g_object_set_data_full (G_OBJECT (msg), "auth-msg-session", |
559 | 0 | g_object_ref (priv->session), |
560 | 0 | g_object_unref); |
561 | 0 | g_signal_connect_object (auth, "notify::is-authenticated", |
562 | 0 | G_CALLBACK (async_auth_finished), |
563 | 0 | msg, 0); |
564 | 0 | g_signal_connect_object (auth, "notify::is-cancelled", |
565 | 0 | G_CALLBACK (async_auth_finished), |
566 | 0 | msg, 0); |
567 | 0 | } |
568 | 0 | } |
569 | 0 | } |
570 | | |
571 | | static SoupAuth * |
572 | | record_auth_for_uri (SoupAuthManagerPrivate *priv, GUri *uri, |
573 | | SoupAuth *auth, gboolean prior_auth_failed) |
574 | 0 | { |
575 | 0 | SoupAuthHost *host; |
576 | 0 | SoupAuth *old_auth; |
577 | 0 | const char *path; |
578 | 0 | char *auth_info, *old_auth_info; |
579 | 0 | GSList *pspace, *p; |
580 | |
|
581 | 0 | host = get_auth_host_for_uri (priv, uri); |
582 | 0 | auth_info = soup_auth_get_info (auth); |
583 | |
|
584 | 0 | if (!host->auth_realms) { |
585 | 0 | host->auth_realms = soup_path_map_new (g_free); |
586 | 0 | host->auths = g_hash_table_new_full (g_str_hash, g_str_equal, |
587 | 0 | g_free, g_object_unref); |
588 | 0 | } |
589 | | |
590 | | /* Record where this auth realm is used. */ |
591 | 0 | pspace = soup_auth_get_protection_space (auth, uri); |
592 | 0 | for (p = pspace; p; p = p->next) { |
593 | 0 | path = p->data; |
594 | 0 | old_auth_info = soup_path_map_lookup (host->auth_realms, path); |
595 | 0 | if (old_auth_info) { |
596 | 0 | if (!strcmp (old_auth_info, auth_info)) |
597 | 0 | continue; |
598 | 0 | soup_path_map_remove (host->auth_realms, path); |
599 | 0 | } |
600 | | |
601 | 0 | soup_path_map_add (host->auth_realms, path, |
602 | 0 | g_strdup (auth_info)); |
603 | 0 | } |
604 | 0 | soup_auth_free_protection_space (auth, pspace); |
605 | | |
606 | | /* Now, make sure the auth is recorded. (If there's a |
607 | | * pre-existing good auth, we keep that rather than the new one, |
608 | | * since the old one might already be authenticated.) |
609 | | */ |
610 | 0 | old_auth = g_hash_table_lookup (host->auths, auth_info); |
611 | 0 | if (old_auth && (old_auth != auth || !prior_auth_failed) && !soup_auth_is_cancelled (old_auth)) { |
612 | 0 | g_free (auth_info); |
613 | 0 | return old_auth; |
614 | 0 | } else { |
615 | 0 | g_hash_table_insert (host->auths, auth_info, |
616 | 0 | g_object_ref (auth)); |
617 | 0 | return auth; |
618 | 0 | } |
619 | 0 | } |
620 | | |
621 | | static void |
622 | | auth_got_headers (SoupMessage *msg, gpointer manager) |
623 | 0 | { |
624 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
625 | 0 | SoupAuth *auth, *prior_auth; |
626 | 0 | gboolean prior_auth_failed = FALSE; |
627 | |
|
628 | 0 | g_mutex_lock (&priv->mutex); |
629 | | |
630 | | /* See if we used auth last time */ |
631 | 0 | prior_auth = soup_message_get_auth (msg); |
632 | 0 | if (prior_auth && check_auth (msg, prior_auth)) { |
633 | 0 | auth = g_object_ref (prior_auth); |
634 | 0 | if (!soup_auth_is_ready (auth, msg)) |
635 | 0 | prior_auth_failed = TRUE; |
636 | 0 | } else { |
637 | 0 | auth = create_auth (priv, msg); |
638 | 0 | if (!auth) { |
639 | 0 | g_mutex_unlock (&priv->mutex); |
640 | 0 | return; |
641 | 0 | } |
642 | 0 | } |
643 | | |
644 | 0 | if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) { |
645 | 0 | SoupAuth *new_auth; |
646 | |
|
647 | 0 | new_auth = record_auth_for_uri (priv, soup_message_get_uri_for_auth (msg), |
648 | 0 | auth, prior_auth_failed); |
649 | 0 | g_set_object (&auth, new_auth); |
650 | 0 | } |
651 | |
|
652 | 0 | g_mutex_unlock (&priv->mutex); |
653 | | |
654 | | /* If we need to authenticate, try to do it. */ |
655 | 0 | authenticate_auth (manager, auth, msg, |
656 | 0 | prior_auth_failed, FALSE, TRUE); |
657 | 0 | soup_message_set_auth (msg, auth); |
658 | 0 | g_object_unref (auth); |
659 | 0 | } |
660 | | |
661 | | static void |
662 | | auth_got_body (SoupMessage *msg, gpointer manager) |
663 | 0 | { |
664 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
665 | 0 | SoupAuth *auth; |
666 | |
|
667 | 0 | g_mutex_lock (&priv->mutex); |
668 | |
|
669 | 0 | auth = lookup_auth (priv, msg); |
670 | 0 | if (auth && soup_auth_is_ready (auth, msg)) { |
671 | 0 | if (SOUP_IS_CONNECTION_AUTH (auth)) |
672 | 0 | soup_message_remove_flags (msg, SOUP_MESSAGE_NEW_CONNECTION); |
673 | | |
674 | | /* When not using cached credentials, update the Authorization header |
675 | | * right before requeuing the message. |
676 | | */ |
677 | 0 | if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) |
678 | 0 | update_authorization_header (msg, auth, FALSE); |
679 | |
|
680 | 0 | soup_session_requeue_message (priv->session, msg); |
681 | 0 | } |
682 | |
|
683 | 0 | g_mutex_unlock (&priv->mutex); |
684 | 0 | } |
685 | | |
686 | | static void |
687 | | proxy_auth_got_headers (SoupMessage *msg, gpointer manager) |
688 | 0 | { |
689 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
690 | 0 | SoupAuth *auth = NULL, *prior_auth; |
691 | 0 | gboolean prior_auth_failed = FALSE; |
692 | |
|
693 | 0 | g_mutex_lock (&priv->mutex); |
694 | | |
695 | | /* See if we used auth last time */ |
696 | 0 | prior_auth = soup_message_get_proxy_auth (msg); |
697 | 0 | if (prior_auth && check_auth (msg, prior_auth)) { |
698 | 0 | if (!soup_auth_is_ready (prior_auth, msg)) |
699 | 0 | prior_auth_failed = TRUE; |
700 | 0 | } |
701 | |
|
702 | 0 | if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE) && |
703 | 0 | priv->proxy_auth && proxy_auth_matches_msg (priv, msg)) |
704 | 0 | auth = g_object_ref (priv->proxy_auth); |
705 | |
|
706 | 0 | if (!auth) { |
707 | 0 | auth = create_auth (priv, msg); |
708 | 0 | if (!auth) { |
709 | 0 | g_mutex_unlock (&priv->mutex); |
710 | 0 | return; |
711 | 0 | } |
712 | | |
713 | 0 | if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) { |
714 | 0 | g_set_object (&priv->proxy_auth, auth); |
715 | 0 | g_free (priv->proxy_auth_authority); |
716 | 0 | priv->proxy_auth_authority = get_proxy_authority (msg); |
717 | 0 | } |
718 | 0 | } |
719 | | |
720 | 0 | g_mutex_unlock (&priv->mutex); |
721 | | |
722 | | /* If we need to authenticate, try to do it. */ |
723 | 0 | authenticate_auth (manager, auth, msg, |
724 | 0 | prior_auth_failed, TRUE, TRUE); |
725 | 0 | soup_message_set_proxy_auth (msg, auth); |
726 | 0 | g_object_unref (auth); |
727 | 0 | } |
728 | | |
729 | | static void |
730 | | proxy_auth_got_body (SoupMessage *msg, gpointer manager) |
731 | 0 | { |
732 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
733 | 0 | SoupAuth *auth; |
734 | |
|
735 | 0 | g_mutex_lock (&priv->mutex); |
736 | |
|
737 | 0 | auth = lookup_proxy_auth (priv, msg); |
738 | 0 | if (auth && soup_auth_is_ready (auth, msg)) { |
739 | | /* When not using cached credentials, update the Authorization header |
740 | | * right before requeuing the message. |
741 | | */ |
742 | 0 | if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) |
743 | 0 | update_authorization_header (msg, auth, TRUE); |
744 | 0 | soup_session_requeue_message (priv->session, msg); |
745 | 0 | } |
746 | |
|
747 | 0 | g_mutex_unlock (&priv->mutex); |
748 | 0 | } |
749 | | |
750 | | static void |
751 | | auth_msg_starting (SoupMessage *msg, gpointer manager) |
752 | 0 | { |
753 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
754 | 0 | SoupAuth *auth; |
755 | 0 | SoupConnection *conn; |
756 | 0 | gboolean tunnelled; |
757 | |
|
758 | 0 | if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) |
759 | 0 | return; |
760 | | |
761 | 0 | g_mutex_lock (&priv->mutex); |
762 | |
|
763 | 0 | if (soup_message_get_method (msg) != SOUP_METHOD_CONNECT) { |
764 | 0 | auth = lookup_auth (priv, msg); |
765 | 0 | if (auth) { |
766 | 0 | authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE); |
767 | 0 | if (!soup_auth_is_ready (auth, msg)) |
768 | 0 | auth = NULL; |
769 | 0 | } |
770 | 0 | soup_message_set_auth (msg, auth); |
771 | 0 | update_authorization_header (msg, auth, FALSE); |
772 | 0 | } |
773 | | |
774 | | /* Proxy-Authorization must only be sent to the proxy itself, never on |
775 | | * a request travelling through an established CONNECT tunnel to the |
776 | | * origin server, which would leak the proxy credentials to the origin. |
777 | | * The CONNECT request that sets the tunnel up is addressed to the |
778 | | * proxy, so it still needs the header. |
779 | | */ |
780 | 0 | conn = soup_message_get_connection (msg); |
781 | 0 | tunnelled = conn && soup_connection_is_tunnelled (conn) && |
782 | 0 | soup_message_get_method (msg) != SOUP_METHOD_CONNECT; |
783 | 0 | g_clear_object (&conn); |
784 | 0 | if (tunnelled) { |
785 | 0 | soup_message_set_proxy_auth (msg, NULL); |
786 | 0 | update_authorization_header (msg, NULL, TRUE); |
787 | 0 | g_mutex_unlock (&priv->mutex); |
788 | 0 | return; |
789 | 0 | } |
790 | | |
791 | 0 | auth = lookup_proxy_auth (priv, msg); |
792 | 0 | if (auth) { |
793 | 0 | authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE); |
794 | 0 | if (!soup_auth_is_ready (auth, msg)) |
795 | 0 | auth = NULL; |
796 | 0 | } |
797 | 0 | soup_message_set_proxy_auth (msg, auth); |
798 | 0 | update_authorization_header (msg, auth, TRUE); |
799 | |
|
800 | 0 | g_mutex_unlock (&priv->mutex); |
801 | 0 | } |
802 | | |
803 | | static void |
804 | | soup_auth_manager_request_queued (SoupSessionFeature *manager, |
805 | | SoupMessage *msg) |
806 | 0 | { |
807 | 0 | g_signal_connect (msg, "starting", |
808 | 0 | G_CALLBACK (auth_msg_starting), manager); |
809 | |
|
810 | 0 | soup_message_add_status_code_handler ( |
811 | 0 | msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, |
812 | 0 | G_CALLBACK (auth_got_headers), manager); |
813 | 0 | soup_message_add_status_code_handler ( |
814 | 0 | msg, "got_body", SOUP_STATUS_UNAUTHORIZED, |
815 | 0 | G_CALLBACK (auth_got_body), manager); |
816 | |
|
817 | 0 | soup_message_add_status_code_handler ( |
818 | 0 | msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED, |
819 | 0 | G_CALLBACK (proxy_auth_got_headers), manager); |
820 | 0 | soup_message_add_status_code_handler ( |
821 | 0 | msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED, |
822 | 0 | G_CALLBACK (proxy_auth_got_body), manager); |
823 | 0 | } |
824 | | |
825 | | static void |
826 | | soup_auth_manager_request_unqueued (SoupSessionFeature *manager, |
827 | | SoupMessage *msg) |
828 | 0 | { |
829 | 0 | g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA, |
830 | 0 | 0, 0, NULL, NULL, manager); |
831 | 0 | } |
832 | | |
833 | | /** |
834 | | * soup_auth_manager_use_auth: |
835 | | * @manager: a #SoupAuthManager |
836 | | * @uri: the #GUri under which @auth is to be used |
837 | | * @auth: the #SoupAuth to use |
838 | | * |
839 | | * Records that @auth is to be used under @uri, as though a |
840 | | * WWW-Authenticate header had been received at that URI. |
841 | | * |
842 | | * This can be used to "preload" @manager's auth cache, to avoid an extra HTTP |
843 | | * round trip in the case where you know ahead of time that a 401 response will |
844 | | * be returned. |
845 | | * |
846 | | * This is only useful for authentication types where the initial |
847 | | * Authorization header does not depend on any additional information |
848 | | * from the server. (Eg, Basic or NTLM, but not Digest.) |
849 | | * |
850 | | */ |
851 | | void |
852 | | soup_auth_manager_use_auth (SoupAuthManager *manager, |
853 | | GUri *uri, |
854 | | SoupAuth *auth) |
855 | 0 | { |
856 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
857 | |
|
858 | 0 | g_mutex_lock (&priv->mutex); |
859 | 0 | record_auth_for_uri (priv, uri, auth, FALSE); |
860 | 0 | g_mutex_unlock (&priv->mutex); |
861 | 0 | } |
862 | | |
863 | | /** |
864 | | * soup_auth_manager_clear_cached_credentials: |
865 | | * @manager: a #SoupAuthManager |
866 | | * |
867 | | * Clear all credentials cached by @manager. |
868 | | * |
869 | | */ |
870 | | void |
871 | | soup_auth_manager_clear_cached_credentials (SoupAuthManager *manager) |
872 | 0 | { |
873 | 0 | SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager); |
874 | |
|
875 | 0 | g_return_if_fail (SOUP_IS_AUTH_MANAGER (manager)); |
876 | | |
877 | 0 | g_mutex_lock (&priv->mutex); |
878 | 0 | g_hash_table_remove_all (priv->auth_hosts); |
879 | 0 | g_mutex_unlock (&priv->mutex); |
880 | 0 | } |
881 | | |
882 | | static void |
883 | | soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, |
884 | | gpointer interface_data) |
885 | 0 | { |
886 | 0 | feature_interface->attach = soup_auth_manager_attach; |
887 | 0 | feature_interface->request_queued = soup_auth_manager_request_queued; |
888 | 0 | feature_interface->request_unqueued = soup_auth_manager_request_unqueued; |
889 | 0 | feature_interface->add_feature = soup_auth_manager_add_feature; |
890 | 0 | feature_interface->remove_feature = soup_auth_manager_remove_feature; |
891 | 0 | feature_interface->has_feature = soup_auth_manager_has_feature; |
892 | 0 | } |