diff options
Diffstat (limited to 'libsoup/soup-auth-manager.c')
-rw-r--r-- | libsoup/soup-auth-manager.c | 343 |
1 files changed, 248 insertions, 95 deletions
diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c index b3ef9f65..f40a9282 100644 --- a/libsoup/soup-auth-manager.c +++ b/libsoup/soup-auth-manager.c @@ -13,12 +13,43 @@ #include "soup-auth-manager.h" #include "soup.h" -#include "soup-marshal.h" +#include "soup-connection-auth.h" #include "soup-message-private.h" #include "soup-message-queue.h" #include "soup-path-map.h" #include "soup-session-private.h" +/** + * SECTION:soup-auth-manager + * @short_description: HTTP client-side authentication handler + * @see_also: #SoupSession, #SoupAuth + * + * #SoupAuthManager is the #SoupSessionFeature that handles HTTP + * authentication for a #SoupSession. + * + * A #SoupAuthManager is added to the session by default, and normally + * you don't need to worry about it at all. However, if you want to + * disable HTTP authentication, you can remove the feature from the + * session with soup_session_remove_feature_by_type(), or disable it on + * individual requests with soup_message_disable_feature(). + * + * Since: 2.42 + **/ + +/** + * SOUP_TYPE_AUTH_MANAGER: + * + * The #GType of #SoupAuthManager; you can use this with + * soup_session_remove_feature_by_type() or + * soup_message_disable_feature(). + * + * (Although this type has only been publicly visible since libsoup + * 2.42, it has always existed in the background, and you can use + * <literal><code>g_type_from_name ("SoupAuthManager")</code></literal> + * to get its #GType in earlier releases.) + * + * Since: 2.42 + */ static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); static SoupSessionFeatureInterface *soup_session_feature_default_interface; @@ -33,14 +64,15 @@ G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, soup_auth_manager_session_feature_init)) -typedef struct { +struct SoupAuthManagerPrivate { SoupSession *session; GPtrArray *auth_types; + gboolean auto_ntlm; + GMutex lock; SoupAuth *proxy_auth; GHashTable *auth_hosts; -} SoupAuthManagerPrivate; -#define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate)) +}; typedef struct { SoupURI *uri; @@ -49,23 +81,29 @@ typedef struct { } SoupAuthHost; static void soup_auth_host_free (SoupAuthHost *host); +static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv, + SoupURI *uri, SoupAuth *auth, + gboolean prior_auth_failed); static void soup_auth_manager_init (SoupAuthManager *manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv; + + priv = manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate); priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref); priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash, soup_uri_host_equal, NULL, (GDestroyNotify)soup_auth_host_free); + g_mutex_init (&priv->lock); } static void soup_auth_manager_finalize (GObject *object) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (object)->priv; g_ptr_array_free (priv->auth_types, TRUE); @@ -73,6 +111,8 @@ soup_auth_manager_finalize (GObject *object) g_clear_object (&priv->proxy_auth); + g_mutex_clear (&priv->lock); + G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object); } @@ -85,13 +125,27 @@ soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class) object_class->finalize = soup_auth_manager_finalize; + /** + * SoupAuthManager::authenticate: + * @manager: the #SoupAuthManager + * @msg: the #SoupMessage being sent + * @auth: the #SoupAuth to authenticate + * @retrying: %TRUE if this is the second (or later) attempt + * + * Emitted when the manager requires the application to + * provide authentication credentials. + * + * #SoupSession connects to this signal and emits its own + * #SoupSession::authenticate signal when it is emitted, so + * you shouldn't need to use this signal directly. + */ signals[AUTHENTICATE] = g_signal_new ("authenticate", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate), NULL, NULL, - _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN, + NULL, G_TYPE_NONE, 3, SOUP_TYPE_MESSAGE, SOUP_TYPE_AUTH, @@ -111,7 +165,7 @@ auth_type_compare_func (gconstpointer a, gconstpointer b) static gboolean soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; if (!g_type_is_a (type, SOUP_TYPE_AUTH)) @@ -120,13 +174,21 @@ soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) auth_class = g_type_class_ref (type); g_ptr_array_add (priv->auth_types, auth_class); g_ptr_array_sort (priv->auth_types, auth_type_compare_func); + + /* Plain SoupSession does not get the backward-compat + * auto-NTLM behavior; SoupSession subclasses do. + */ + if (type == SOUP_TYPE_AUTH_NTLM && + G_TYPE_FROM_INSTANCE (priv->session) != SOUP_TYPE_SESSION) + priv->auto_ntlm = TRUE; + return TRUE; } static gboolean soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; int i; @@ -134,8 +196,12 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) return FALSE; auth_class = g_type_class_peek (type); + for (i = 0; i < priv->auth_types->len; i++) { if (priv->auth_types->pdata[i] == (gpointer)auth_class) { + if (type == SOUP_TYPE_AUTH_NTLM) + priv->auto_ntlm = FALSE; + g_ptr_array_remove_index (priv->auth_types, i); return TRUE; } @@ -147,7 +213,7 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) static gboolean soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; int i; @@ -162,22 +228,15 @@ soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) return FALSE; } -void -soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying); -} - static void -soup_auth_manager_attach (SoupSessionFeature *manager, SoupSession *session) +soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; /* FIXME: should support multiple sessions */ priv->session = session; - soup_session_feature_default_interface->attach (manager, session); + soup_session_feature_default_interface->attach (feature, session); } static inline const char * @@ -252,7 +311,7 @@ next_challenge_start (GSList *items) return NULL; } -char * +static char * soup_auth_manager_extract_challenge (const char *challenges, const char *scheme) { GSList *items, *i, *next; @@ -319,28 +378,30 @@ create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) static gboolean check_auth (SoupMessage *msg, SoupAuth *auth) { - const char *header; - char *challenge; - gboolean ok; + const char *header, *scheme; + char *challenge = NULL; + gboolean ok = TRUE; - header = auth_header_for_message (msg); - if (!header) - return FALSE; + scheme = soup_auth_get_scheme_name (auth); - challenge = soup_auth_manager_extract_challenge (header, soup_auth_get_scheme_name (auth)); - if (!challenge) - return FALSE; + header = auth_header_for_message (msg); + if (header) + challenge = soup_auth_manager_extract_challenge (header, scheme); + if (!challenge) { + ok = FALSE; + challenge = g_strdup (scheme); + } - ok = soup_auth_update (auth, msg, challenge); + if (!soup_auth_update (auth, msg, challenge)) + ok = FALSE; g_free (challenge); return ok; } static SoupAuthHost * -get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg) +get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri) { SoupAuthHost *host; - SoupURI *uri = soup_message_get_uri (msg); host = g_hash_table_lookup (priv->auth_hosts, uri); if (host) @@ -363,14 +424,30 @@ soup_auth_host_free (SoupAuthHost *host) g_slice_free (SoupAuthHost, host); } +static gboolean +make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host) +{ + SoupAuth *auth; + + if (!priv->auto_ntlm) + return FALSE; + + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, + SOUP_AUTH_HOST, host->uri->host, + NULL); + record_auth_for_uri (priv, host->uri, auth, FALSE); + g_object_unref (auth); + return TRUE; +} + static SoupAuth * lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) { SoupAuthHost *host; const char *path, *realm; - host = get_auth_host_for_message (priv, msg); - if (!host->auth_realms) + host = get_auth_host_for_uri (priv, soup_message_get_uri (msg)); + if (!host->auth_realms && !make_auto_ntlm_auth (priv, host)) return NULL; path = soup_message_get_uri (msg)->path; @@ -383,12 +460,12 @@ lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) return NULL; } -static gboolean +static void authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, SoupMessage *msg, gboolean prior_auth_failed, gboolean proxy, gboolean can_interact) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = manager->priv; SoupURI *uri; if (proxy) { @@ -404,48 +481,34 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, uri = NULL; if (!uri) - return FALSE; + return; } else uri = soup_message_get_uri (msg); /* If a password is specified explicitly in the URI, use it * even if the auth had previously already been authenticated. */ - if (uri->password) { - if (!prior_auth_failed) - soup_auth_authenticate (auth, uri->user, uri->password); + if (uri->password && uri->user) { + soup_auth_authenticate (auth, uri->user, uri->password); + soup_uri_set_password (uri, NULL); + soup_uri_set_user (uri, NULL); } else if (!soup_auth_is_authenticated (auth) && can_interact) { - soup_auth_manager_emit_authenticate (manager, msg, auth, - prior_auth_failed); + g_signal_emit (manager, signals[AUTHENTICATE], 0, + msg, auth, prior_auth_failed); } - - return soup_auth_is_authenticated (auth); } -static void -update_auth (SoupMessage *msg, gpointer manager) +static SoupAuth * +record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri, + SoupAuth *auth, gboolean prior_auth_failed) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuthHost *host; - SoupAuth *auth, *prior_auth, *old_auth; + SoupAuth *old_auth; const char *path; char *auth_info, *old_auth_info; GSList *pspace, *p; - gboolean prior_auth_failed = FALSE; - - host = get_auth_host_for_message (priv, msg); - /* See if we used auth last time */ - prior_auth = soup_message_get_auth (msg); - if (prior_auth && check_auth (msg, prior_auth)) { - auth = prior_auth; - if (!soup_auth_is_authenticated (auth)) - prior_auth_failed = TRUE; - } else { - auth = create_auth (priv, msg); - if (!auth) - return; - } + host = get_auth_host_for_uri (priv, uri); auth_info = soup_auth_get_info (auth); if (!host->auth_realms) { @@ -455,7 +518,7 @@ update_auth (SoupMessage *msg, gpointer manager) } /* Record where this auth realm is used. */ - pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg)); + pspace = soup_auth_get_protection_space (auth, uri); for (p = pspace; p; p = p->next) { path = p->data; old_auth_info = soup_path_map_lookup (host->auth_realms, path); @@ -471,68 +534,116 @@ update_auth (SoupMessage *msg, gpointer manager) soup_auth_free_protection_space (auth, pspace); /* Now, make sure the auth is recorded. (If there's a - * pre-existing auth, we keep that rather than the new one, + * pre-existing good auth, we keep that rather than the new one, * since the old one might already be authenticated.) */ old_auth = g_hash_table_lookup (host->auths, auth_info); - if (old_auth) { + if (old_auth && (old_auth != auth || !prior_auth_failed)) { g_free (auth_info); - if (auth != old_auth && auth != prior_auth) { - g_object_unref (auth); - auth = old_auth; - } + return old_auth; + } else { + g_hash_table_insert (host->auths, auth_info, + g_object_ref (auth)); + return auth; + } +} + +static void +auth_got_headers (SoupMessage *msg, gpointer manager) +{ + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth, *prior_auth, *new_auth; + gboolean prior_auth_failed = FALSE; + + g_mutex_lock (&priv->lock); + + /* See if we used auth last time */ + prior_auth = soup_message_get_auth (msg); + if (prior_auth && check_auth (msg, prior_auth)) { + auth = g_object_ref (prior_auth); + if (!soup_auth_is_ready (auth, msg)) + prior_auth_failed = TRUE; } else { - g_hash_table_insert (host->auths, auth_info, auth); + auth = create_auth (priv, msg); + if (!auth) { + g_mutex_unlock (&priv->lock); + return; + } } + new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg), + auth, prior_auth_failed); + g_object_unref (auth); + /* If we need to authenticate, try to do it. */ - authenticate_auth (manager, auth, msg, + authenticate_auth (manager, new_auth, msg, prior_auth_failed, FALSE, TRUE); + g_mutex_unlock (&priv->lock); } static void -requeue_if_authenticated (SoupMessage *msg, gpointer manager) +auth_got_body (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); - SoupAuth *auth = lookup_auth (priv, msg); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth; + + g_mutex_lock (&priv->lock); + auth = lookup_auth (priv, msg); + if (auth && soup_auth_is_ready (auth, msg)) { + if (SOUP_IS_CONNECTION_AUTH (auth)) { + SoupMessageFlags flags; + + flags = soup_message_get_flags (msg); + soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); + } - if (auth && soup_auth_is_authenticated (auth)) soup_session_requeue_message (priv->session, msg); + } + g_mutex_unlock (&priv->lock); } static void -update_proxy_auth (SoupMessage *msg, gpointer manager) +proxy_auth_got_headers (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; SoupAuth *prior_auth; gboolean prior_auth_failed = FALSE; + g_mutex_lock (&priv->lock); + /* See if we used auth last time */ prior_auth = soup_message_get_proxy_auth (msg); if (prior_auth && check_auth (msg, prior_auth)) { - if (!soup_auth_is_authenticated (prior_auth)) + if (!soup_auth_is_ready (prior_auth, msg)) prior_auth_failed = TRUE; } if (!priv->proxy_auth) { priv->proxy_auth = create_auth (priv, msg); - if (!priv->proxy_auth) + if (!priv->proxy_auth) { + g_mutex_unlock (&priv->lock); return; + } } /* If we need to authenticate, try to do it. */ authenticate_auth (manager, priv->proxy_auth, msg, prior_auth_failed, TRUE, TRUE); + g_mutex_unlock (&priv->lock); } static void -requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager) +proxy_auth_got_body (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); - SoupAuth *auth = priv->proxy_auth; + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth; + + g_mutex_lock (&priv->lock); + auth = priv->proxy_auth; - if (auth && soup_auth_is_authenticated (auth)) + if (auth && soup_auth_is_ready (auth, msg)) soup_session_requeue_message (priv->session, msg); + g_mutex_unlock (&priv->lock); } static void @@ -542,17 +653,17 @@ soup_auth_manager_request_queued (SoupSessionFeature *manager, { soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (update_auth), manager); + G_CALLBACK (auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (requeue_if_authenticated), manager); + G_CALLBACK (auth_got_body), manager); soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (update_proxy_auth), manager); + G_CALLBACK (proxy_auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (requeue_if_proxy_authenticated), manager); + G_CALLBACK (proxy_auth_got_body), manager); } static void @@ -562,18 +673,30 @@ soup_auth_manager_request_started (SoupSessionFeature *feature, SoupSocket *socket) { SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature); - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = manager->priv; SoupAuth *auth; - auth = lookup_auth (priv, msg); - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) - auth = NULL; - soup_message_set_auth (msg, auth); + g_mutex_lock (&priv->lock); + + if (msg->method != SOUP_METHOD_CONNECT) { + auth = lookup_auth (priv, msg); + if (auth) { + authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE); + if (!soup_auth_is_ready (auth, msg)) + auth = NULL; + } + soup_message_set_auth (msg, auth); + } auth = priv->proxy_auth; - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE)) - auth = NULL; + if (auth) { + authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE); + if (!soup_auth_is_ready (auth, msg)) + auth = NULL; + } soup_message_set_proxy_auth (msg, auth); + + g_mutex_unlock (&priv->lock); } static void @@ -585,6 +708,36 @@ soup_auth_manager_request_unqueued (SoupSessionFeature *manager, 0, 0, NULL, NULL, manager); } +/** + * soup_auth_manager_use_auth: + * @manager: a #SoupAuthManager + * @uri: the #SoupURI under which @auth is to be used + * @auth: the #SoupAuth to use + * + * Records that @auth is to be used under @uri, as though a + * WWW-Authenticate header had been received at that URI. This can be + * used to "preload" @manager's auth cache, to avoid an extra HTTP + * round trip in the case where you know ahead of time that a 401 + * response will be returned. + * + * This is only useful for authentication types where the initial + * Authorization header does not depend on any additional information + * from the server. (Eg, Basic or NTLM, but not Digest.) + * + * Since: 2.42 + */ +void +soup_auth_manager_use_auth (SoupAuthManager *manager, + SoupURI *uri, + SoupAuth *auth) +{ + SoupAuthManagerPrivate *priv = manager->priv; + + g_mutex_lock (&priv->lock); + record_auth_for_uri (priv, uri, auth, FALSE); + g_mutex_unlock (&priv->lock); +} + static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data) |