summaryrefslogtreecommitdiff
path: root/libsoup/soup-auth-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/soup-auth-manager.c')
-rw-r--r--libsoup/soup-auth-manager.c343
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)