[evolution-patches] soup auth patch
- From: Dan Winship <danw ximian com>
- To: evolution-patches ximian com
- Cc: Joe Shaw <joe ximian com>
- Subject: [evolution-patches] soup auth patch
- Date: Thu, 07 Aug 2003 14:42:22 -0400
This fixes soup's auth handling, almost completely. I did it because I
wanted to fix 43332, 46724, 46725 and one other bug not in bugzilla, but
this patch actually fixes a lot more than that, because I already had a
lot of the logic (and the regression test) on the soup-refactoring
branch, and if I was going to fix a lot of things, I figured I might as
well fix everything.
Um... ok, so yes, it's a big patch. But it has a regression test! (And
I'm running connector with it now.)
-- Dan
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/libsoup/ChangeLog,v
retrieving revision 1.317
diff -u -w -r1.317 ChangeLog
--- ChangeLog 29 Jul 2003 15:04:00 -0000 1.317
+++ ChangeLog 7 Aug 2003 18:24:22 -0000
@@ -1,3 +1,68 @@
+2003-08-07 Dan Winship <danw ximian com>
+
+ * libsoup/soup-auth.c (soup_auth_lookup, soup_auth_set_context,
+ soup_auth_invalidate): These are all really SoupContext functions,
+ so move them to soup-context.c (and rename them appropriately).
+ (soup_auth_get_protection_space): New method to get the
+ "protection space" of an auth (paths where it is valid).
+ (soup_auth_invalidate): New method to try to un-authenticate an
+ auth (so we can keep the domain info cached even if the auth info
+ is wrong).
+ (basic_pspace_func): Basic protection space is all directories
+ below the current one.
+ (basic_invalidate_func): Clear the encoded username/password
+ (digest_pspace_func): Digest protection space is either the whole
+ server, or "what the domain parameter says" (though we don't deal
+ with cross-host domains).
+ (digest_invalidate_func): Return FALSE; bad digest auth info isn't
+ cacheable.
+ (digest_parse_func, digest_free): Set/free domain parameter
+ (ntlm_pspace): NTLM protection space is always the whole server.
+ (ntlm_invalidate): Clear the auth state.
+ (soup_auth_new_ntlm): Make this non-static
+ (SoupAuth): Replace the quad-state "status" field with an
+ "authenticated" boolean.
+
+ * libsoup/soup-private.h (SoupHost): Replace the "valid_auths"
+ hash with separate "auth_realms" (path->realm) and "auths"
+ (realm->auth) hashes. Also add a "use_ntlm" flag.
+
+ * libsoup/soup-context.c (soup_context_unref): Update SoupHost
+ freeing code.
+ (connection_free): Don't the connection's auth, just free it.
+ (soup_context_lookup_auth): Formerly soup_auth_lookup, but now
+ does two-stage lookup (path->realm then realm->auth) and also
+ deals with NTLM hacks.
+ (soup_context_update_auth): Mostly formerly soup_auth_set_context,
+ but also large parts of authorize_handler. Updates the auth hashes
+ based on information from a 401 or 407 response. Does a better job
+ than authorize_handler did of not throwing away good information.
+ (soup_context_preauthenticate): New; fakes up auth info so that
+ requests will end up using authentication without the server
+ needing to return an error first.
+ (soup_context_authenticate_auth): Moved out of authorize_handler
+ so it can be used at request-sending time too, if we know that we
+ need it. (That way we can avoid requeuing the request if it isn't
+ going to be able to be authenticated.)
+ (soup_context_invalidate_auth): Sort of like the old
+ soup_auth_invalidate, but only destroys the auth data, while still
+ remembering the path->realm mapping.
+
+ * libsoup/soup-message.c (authorize_handler): Mostly moved into
+ soup_context_update_auth.
+ (maybe_validate_auth): Remove this; it was only useful because of
+ bugs elsewhere in the auth handling.
+
+ * libsoup/soup-queue.c (soup_encode_http_auth): Update for
+ soup_context_lookup_auth. If the returned auth isn't
+ authenticated, call soup_context_authenticate_auth() on it.
+
+ * tests/auth-test.c: New (from soup-refactoring branch). Tests
+ that the Basic/Digest auth code does the right thing. (TODO: find
+ a good way to add NTLM tests too.)
+
+ * tests/Makefile.am (check_PROGRAMS): add auth-test
+
2003-07-29 Dan Winship <danw ximian com>
* configure.in: 1.99.25 ("Potato and Leek Soup")
Index: libsoup/soup-auth.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-auth.c,v
retrieving revision 1.21
diff -u -w -r1.21 soup-auth.c
--- libsoup/soup-auth.c 29 Jul 2003 15:04:01 -0000 1.21
+++ libsoup/soup-auth.c 7 Aug 2003 18:24:22 -0000
@@ -62,6 +62,21 @@
soup_header_param_destroy_hash (tokens);
}
+static GSList *
+basic_pspace_func (SoupAuth *auth, const SoupUri *source_uri)
+{
+ char *space, *p;
+
+ space = g_strdup (source_uri->path);
+
+ /* Strip query and filename component */
+ p = strrchr (space, '/');
+ if (p && p != space && p[1])
+ *p = '\0';
+
+ return g_slist_prepend (NULL, space);
+}
+
static void
basic_init_func (SoupAuth *auth, const SoupUri *uri)
{
@@ -71,6 +86,20 @@
user_pass = g_strdup_printf ("%s:%s", uri->user, uri->passwd);
basic->token = soup_base64_encode (user_pass, strlen (user_pass));
g_free (user_pass);
+
+ auth->authenticated = TRUE;
+}
+
+static gboolean
+basic_invalidate_func (SoupAuth *auth)
+{
+ SoupAuthBasic *basic = (SoupAuthBasic *) auth;
+
+ g_free (basic->token);
+ basic->token = NULL;
+ auth->authenticated = FALSE;
+
+ return TRUE;
}
static void
@@ -91,9 +120,12 @@
basic = g_new0 (SoupAuthBasic, 1);
auth = (SoupAuth *) basic;
auth->type = SOUP_AUTH_TYPE_BASIC;
+ auth->authenticated = FALSE;
auth->parse_func = basic_parse_func;
auth->init_func = basic_init_func;
+ auth->invalidate_func = basic_invalidate_func;
+ auth->pspace_func = basic_pspace_func;
auth->auth_func = basic_auth_func;
auth->free_func = basic_free;
@@ -126,6 +158,7 @@
char *nonce;
QOPType qop_options;
AlgorithmType algorithm;
+ char *domain;
/* These are generated by the client */
char *cnonce;
@@ -331,6 +364,7 @@
auth->realm = soup_header_param_copy_token (tokens, "realm");
digest->nonce = soup_header_param_copy_token (tokens, "nonce");
+ digest->domain = soup_header_param_copy_token (tokens, "domain");
tmp = soup_header_param_copy_token (tokens, "qop");
ptr = tmp;
@@ -356,6 +390,50 @@
soup_header_param_destroy_hash (tokens);
}
+static GSList *
+digest_pspace_func (SoupAuth *auth, const SoupUri *source_uri)
+{
+ SoupAuthDigest *digest = (SoupAuthDigest *) auth;
+ GSList *space = NULL;
+ SoupUri *uri;
+ char *domain, *d, *lasts, *dir, *slash;
+
+ if (!digest->domain) {
+ /* If no domain directive, the protection space is the
+ * whole server.
+ */
+ return g_slist_prepend (NULL, g_strdup (""));
+ }
+
+ domain = g_strdup (digest->domain);
+ for (d = strtok_r (domain, " ", &lasts); d; d = strtok_r (NULL, " ", &lasts)) {
+ if (*d == '/')
+ dir = g_strdup (d);
+ else {
+ uri = soup_uri_new (d);
+ if (uri && uri->protocol == source_uri->protocol &&
+ uri->port == source_uri->port &&
+ !strcmp (uri->host, source_uri->host))
+ dir = g_strdup (uri->path);
+ else
+ dir = NULL;
+ if (uri)
+ soup_uri_free (uri);
+ }
+
+ if (dir) {
+ slash = strrchr (dir, '/');
+ if (slash && !slash[1])
+ *slash = '\0';
+
+ space = g_slist_prepend (space, dir);
+ }
+ }
+ g_free (domain);
+
+ return space;
+}
+
static void
digest_init_func (SoupAuth *auth, const SoupUri *uri)
{
@@ -392,6 +470,17 @@
/* hexify A1 */
md5_final (&ctx, d);
digest_hex (d, digest->hex_a1);
+
+ auth->authenticated = TRUE;
+}
+
+static gboolean
+digest_invalidate_func (SoupAuth *auth)
+{
+ /* If we failed, we need to get a new nonce from the server
+ * next time, so this can't be reused.
+ */
+ return FALSE;
}
static void
@@ -400,6 +489,7 @@
SoupAuthDigest *digest = (SoupAuthDigest *) auth;
g_free (digest->user);
+ g_free (digest->domain);
g_free (digest->nonce);
g_free (digest->cnonce);
@@ -417,9 +507,12 @@
auth = (SoupAuth *) digest;
auth->type = SOUP_AUTH_TYPE_DIGEST;
+ auth->authenticated = FALSE;
auth->parse_func = digest_parse_func;
auth->init_func = digest_init_func;
+ auth->invalidate_func = digest_invalidate_func;
+ auth->pspace_func = digest_pspace_func;
auth->auth_func = digest_auth_func;
auth->free_func = digest_free;
@@ -452,15 +545,14 @@
ntlm_auth (SoupAuth *sa, SoupMessage *msg)
{
SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
- gchar *ret;
+ char *ret;
- if (sa->status == SOUP_AUTH_STATUS_PENDING)
+ if (!sa->authenticated)
return soup_ntlm_request ();
/* Otherwise, return the response; but only once */
ret = auth->response;
auth->response = NULL;
-
return ret;
}
@@ -495,13 +587,20 @@
g_strstrip (auth->header);
}
+static GSList *
+ntlm_pspace (SoupAuth *auth, const SoupUri *source_uri)
+{
+ /* The protection space is the whole server. */
+ return g_slist_prepend (NULL, g_strdup (""));
+}
+
static void
ntlm_init (SoupAuth *sa, const SoupUri *uri)
{
SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
gchar *host, *domain, *nonce;
- if (strlen (auth->header) < sizeof ("NTLM"))
+ if (!auth->header || strlen (auth->header) < sizeof ("NTLM"))
return;
if (auth->response)
@@ -525,14 +624,24 @@
g_free (host);
g_free (domain);
- /* Set this now so that if the server returns 401,
- * soup will fail instead of looping (since that
- * probably means the password was incorrect).
- */
- sa->status = SOUP_AUTH_STATUS_SUCCESSFUL;
+ g_free (auth->header);
+ auth->header = NULL;
+
+ sa->authenticated = TRUE;
+}
+
+static gboolean
+ntlm_invalidate (SoupAuth *sa)
+{
+ SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
+ g_free (auth->response);
+ auth->response = NULL;
g_free (auth->header);
auth->header = NULL;
+
+ sa->authenticated = FALSE;
+ return TRUE;
}
static void
@@ -545,16 +654,20 @@
g_free (auth);
}
-static SoupAuth *
-ntlm_new (void)
+SoupAuth *
+soup_auth_new_ntlm (void)
{
SoupAuthNTLM *auth;
auth = g_new0 (SoupAuthNTLM, 1);
auth->auth.type = SOUP_AUTH_TYPE_NTLM;
+ auth->auth.authenticated = FALSE;
+ auth->auth.realm = g_strdup ("");
auth->auth.parse_func = ntlm_parse;
auth->auth.init_func = ntlm_init;
+ auth->auth.invalidate_func = ntlm_invalidate;
+ auth->auth.pspace_func = ntlm_pspace;
auth->auth.auth_func = ntlm_auth;
auth->auth.free_func = ntlm_free;
@@ -566,92 +679,6 @@
* Generic Authentication Interface
*/
-SoupAuth *
-soup_auth_lookup (SoupContext *ctx)
-{
- GHashTable *auth_hash = ctx->server->valid_auths;
- SoupAuth *ret = NULL;
- gchar *mypath, *dir;
-
- if (!auth_hash) return NULL;
-
- mypath = g_strdup (ctx->uri->path);
- dir = mypath;
-
- do {
- ret = g_hash_table_lookup (auth_hash, mypath);
- if (ret) break;
-
- dir = strrchr (mypath, '/');
- if (dir) *dir = '\0';
- } while (dir);
-
- g_free (mypath);
- return ret;
-}
-
-void
-soup_auth_invalidate (SoupAuth *auth, SoupContext *ctx)
-{
- SoupHost *server;
- const SoupUri *uri;
- SoupAuth *old_auth;
- char *old_path;
-
- g_return_if_fail (ctx != NULL);
- g_return_if_fail (auth != NULL);
-
- server = ctx->server;
-
- if (!server->valid_auths)
- return;
-
- uri = soup_context_get_uri (ctx);
- if (g_hash_table_lookup_extended (server->valid_auths,
- uri->path,
- (gpointer *) &old_path,
- (gpointer *) &old_auth)) {
- g_hash_table_remove (server->valid_auths, old_path);
- g_free (old_path);
- soup_auth_free (old_auth);
- }
-}
-
-void
-soup_auth_set_context (SoupAuth *auth, SoupContext *ctx)
-{
- SoupHost *server;
- SoupAuth *old_auth = NULL;
- gchar *old_path;
- const SoupUri *uri;
-
- g_return_if_fail (ctx != NULL);
- g_return_if_fail (auth != NULL);
-
- server = ctx->server;
- uri = soup_context_get_uri (ctx);
-
- if (!server->valid_auths) {
- server->valid_auths = g_hash_table_new (g_str_hash,
- g_str_equal);
- }
- else if (g_hash_table_lookup_extended (server->valid_auths,
- uri->path,
- (gpointer *) &old_path,
- (gpointer *) &old_auth)) {
- if (auth == old_auth)
- return;
-
- g_hash_table_remove (server->valid_auths, old_path);
- g_free (old_path);
- soup_auth_free (old_auth);
- }
-
- g_hash_table_insert (server->valid_auths,
- g_strdup (uri->path),
- auth);
-}
-
typedef SoupAuth *(*SoupAuthNewFn) (void);
typedef struct {
@@ -662,7 +689,7 @@
static AuthScheme known_auth_schemes [] = {
{ "Basic", soup_auth_new_basic, 0 },
- { "NTLM", ntlm_new, 2 },
+ { "NTLM", soup_auth_new_ntlm, 2 },
{ "Digest", soup_auth_new_digest, 3 },
{ NULL }
};
@@ -727,6 +754,14 @@
auth->init_func (auth, uri);
}
+gboolean
+soup_auth_invalidate (SoupAuth *auth)
+{
+ g_return_val_if_fail (auth != NULL, FALSE);
+
+ return auth->invalidate_func (auth);
+}
+
gchar *
soup_auth_authorize (SoupAuth *auth, SoupMessage *msg)
{
@@ -743,4 +778,23 @@
g_free (auth->realm);
auth->free_func (auth);
+}
+
+GSList *
+soup_auth_get_protection_space (SoupAuth *auth, const SoupUri *source_uri)
+{
+ g_return_val_if_fail (auth != NULL, NULL);
+ g_return_val_if_fail (source_uri != NULL, NULL);
+
+ return auth->pspace_func (auth, source_uri);
+}
+
+void
+soup_auth_free_protection_space (SoupAuth *auth, GSList *space)
+{
+ GSList *s;
+
+ for (s = space; s; s = s->next)
+ g_free (s->data);
+ g_slist_free (space);
}
Index: libsoup/soup-auth.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-auth.h,v
retrieving revision 1.9
diff -u -w -r1.9 soup-auth.h
--- libsoup/soup-auth.h 15 Nov 2002 16:26:42 -0000 1.9
+++ libsoup/soup-auth.h 7 Aug 2003 18:24:22 -0000
@@ -18,19 +18,10 @@
typedef enum _SoupAuthStatus SoupAuthStatus;
typedef struct _SoupAuth SoupAuth;
-enum _SoupAuthStatus {
- SOUP_AUTH_STATUS_INVALID = 0,
- SOUP_AUTH_STATUS_PENDING,
- SOUP_AUTH_STATUS_FAILED,
- SOUP_AUTH_STATUS_SUCCESSFUL
-};
-
struct _SoupAuth {
SoupAuthType type;
- gchar *realm;
-
- SoupAuthStatus status;
- SoupMessage *controlling_msg;
+ char *realm;
+ gboolean authenticated;
void (*parse_func) (SoupAuth *auth,
const gchar *header);
@@ -38,29 +29,36 @@
void (*init_func) (SoupAuth *auth,
const SoupUri *uri);
+ gboolean (*invalidate_func) (SoupAuth *auth);
+
char *(*auth_func) (SoupAuth *auth,
SoupMessage *message);
+ GSList *(*pspace_func) (SoupAuth *auth,
+ const SoupUri *source_uri);
+
void (*free_func) (SoupAuth *auth);
};
-SoupAuth *soup_auth_lookup (SoupContext *ctx);
-
-void soup_auth_set_context (SoupAuth *auth,
- SoupContext *ctx);
-
-void soup_auth_invalidate (SoupAuth *auth,
- SoupContext *ctx);
-
SoupAuth *soup_auth_new_from_header_list (const SoupUri *uri,
const GSList *header);
+SoupAuth *soup_auth_new_ntlm (void);
+
void soup_auth_initialize (SoupAuth *auth,
const SoupUri *uri);
+gboolean soup_auth_invalidate (SoupAuth *auth);
+
void soup_auth_free (SoupAuth *auth);
gchar *soup_auth_authorize (SoupAuth *auth,
SoupMessage *msg);
+
+GSList *soup_auth_get_protection_space (SoupAuth *auth,
+ const SoupUri *source_uri);
+void soup_auth_free_protection_space (SoupAuth *auth,
+ GSList *space);
+
#endif /* SOUP_AUTH_H */
Index: libsoup/soup-context.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-context.c,v
retrieving revision 1.50
diff -u -w -r1.50 soup-context.c
--- libsoup/soup-context.c 29 Jul 2003 15:04:01 -0000 1.50
+++ libsoup/soup-context.c 7 Aug 2003 18:24:22 -0000
@@ -193,13 +193,18 @@
ctx->refcnt++;
}
-static gboolean
-remove_auth (gchar *path, SoupAuth *auth)
+static void
+free_path (gpointer path, gpointer realm, gpointer unused)
{
g_free (path);
- soup_auth_free (auth);
+ g_free (realm);
+}
- return TRUE;
+static void
+free_auth (gpointer realm, gpointer auth, gpointer unused)
+{
+ g_free (realm);
+ soup_auth_free (auth);
}
/**
@@ -231,12 +236,15 @@
/*
* Free all cached SoupAuths
*/
- if (serv->valid_auths) {
- g_hash_table_foreach_remove (
- serv->valid_auths,
- (GHRFunc) remove_auth,
- NULL);
- g_hash_table_destroy (serv->valid_auths);
+ if (serv->auth_realms) {
+ g_hash_table_foreach (serv->auth_realms,
+ free_path, NULL);
+ g_hash_table_destroy (serv->auth_realms);
+ }
+ if (serv->auths) {
+ g_hash_table_foreach (serv->auths,
+ free_auth, NULL);
+ g_hash_table_destroy (serv->auths);
}
g_hash_table_destroy (serv->contexts);
@@ -257,10 +265,8 @@
conn->server->connections =
g_slist_remove (conn->server->connections, conn);
- if (conn->auth) {
- soup_auth_invalidate (conn->auth, conn->context);
+ if (conn->auth)
soup_auth_free (conn->auth);
- }
g_io_channel_unref (conn->channel);
soup_context_unref (conn->context);
@@ -769,4 +775,224 @@
for (i = idle_conns; i; i = i->next)
connection_free (i->data);
g_slist_free (idle_conns);
+}
+
+
+/* Authentication */
+
+SoupAuth *
+soup_context_lookup_auth (SoupContext *ctx, SoupMessage *msg)
+{
+ char *path, *dir;
+ const char *realm;
+
+ g_return_val_if_fail (ctx != NULL, NULL);
+
+ if (ctx->server->use_ntlm && msg && msg->connection) {
+ if (!msg->connection->auth)
+ msg->connection->auth = soup_auth_new_ntlm ();
+ return msg->connection->auth;
+ }
+
+ if (!ctx->server->auth_realms)
+ return NULL;
+
+ path = g_strdup (ctx->uri->path);
+ dir = path;
+ do {
+ realm = g_hash_table_lookup (ctx->server->auth_realms, path);
+ if (realm)
+ break;
+
+ dir = strrchr (path, '/');
+ if (dir)
+ *dir = '\0';
+ } while (dir);
+
+ g_free (path);
+ if (realm)
+ return g_hash_table_lookup (ctx->server->auths, realm);
+ else
+ return NULL;
+}
+
+static gboolean
+update_auth_internal (SoupContext *ctx, SoupConnection *conn,
+ const GSList *headers, gboolean prior_auth_failed)
+{
+ SoupHost *server = ctx->server;
+ SoupAuth *new_auth, *prior_auth, *old_auth;
+ gpointer old_path, old_realm;
+ const char *path;
+ char *realm;
+ GSList *pspace, *p;
+
+ if (server->use_ntlm && conn && conn->auth) {
+ if (conn->auth->authenticated) {
+ /* This is a "permission denied", not a
+ * "password incorrect". There's nothing more
+ * we can do.
+ */
+ return FALSE;
+ }
+
+ /* Free the intermediate auth */
+ soup_auth_free (conn->auth);
+ conn->auth = NULL;
+ }
+
+ /* Try to construct a new auth from the headers; if we can't,
+ * there's no way we'll be able to authenticate.
+ */
+ new_auth = soup_auth_new_from_header_list (ctx->uri, headers);
+ if (!new_auth)
+ return FALSE;
+
+ /* See if this auth is the same auth we used last time */
+ prior_auth = soup_context_lookup_auth (ctx, NULL);
+ if (prior_auth && prior_auth->type == new_auth->type &&
+ !strcmp (prior_auth->realm, new_auth->realm)) {
+ soup_auth_free (new_auth);
+ if (prior_auth_failed) {
+ /* The server didn't like the username/password
+ * we provided before.
+ */
+ soup_context_invalidate_auth (ctx, prior_auth);
+ return FALSE;
+ } else {
+ /* The user is trying to preauthenticate using
+ * information we already have, so there's nothing
+ * that needs to be done.
+ */
+ return TRUE;
+ }
+ }
+
+ if (new_auth->type == SOUP_AUTH_TYPE_NTLM) {
+ server->use_ntlm = TRUE;
+ if (conn) {
+ conn->auth = new_auth;
+ return soup_context_authenticate_auth (ctx, new_auth);
+ } else {
+ soup_auth_free (new_auth);
+ return FALSE;
+ }
+ }
+
+ if (!server->auth_realms) {
+ server->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
+ server->auths = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ /* Record where this auth realm is used */
+ realm = g_strdup_printf ("%d:%s", new_auth->type, new_auth->realm);
+ pspace = soup_auth_get_protection_space (new_auth, ctx->uri);
+ for (p = pspace; p; p = p->next) {
+ path = p->data;
+ if (g_hash_table_lookup_extended (server->auth_realms, path,
+ &old_path, &old_realm)) {
+ g_hash_table_remove (server->auth_realms, old_path);
+ g_free (old_path);
+ g_free (old_realm);
+ }
+
+ g_hash_table_insert (server->auth_realms,
+ g_strdup (path), g_strdup (realm));
+ }
+ soup_auth_free_protection_space (new_auth, pspace);
+
+ /* Now, make sure the auth is recorded. (If there's a
+ * pre-existing auth, we keep that rather than the new one,
+ * since the old one might already be authenticated.)
+ */
+ old_auth = g_hash_table_lookup (server->auths, realm);
+ if (old_auth) {
+ g_free (realm);
+ soup_auth_free (new_auth);
+ new_auth = old_auth;
+ } else
+ g_hash_table_insert (server->auths, realm, new_auth);
+
+ /* Try to authenticate if needed. */
+ if (!new_auth->authenticated)
+ return soup_context_authenticate_auth (ctx, new_auth);
+
+ return TRUE;
+}
+
+gboolean
+soup_context_update_auth (SoupContext *ctx, SoupMessage *msg)
+{
+ const GSList *headers;
+
+ g_return_val_if_fail (ctx != NULL, FALSE);
+ g_return_val_if_fail (msg != NULL, FALSE);
+
+ if (msg->errorcode == SOUP_ERROR_PROXY_UNAUTHORIZED) {
+ headers = soup_message_get_header_list (msg->response_headers,
+ "Proxy-Authenticate");
+ } else {
+ headers = soup_message_get_header_list (msg->response_headers,
+ "WWW-Authenticate");
+ }
+
+ return update_auth_internal (ctx, msg->connection, headers, TRUE);
+}
+
+void
+soup_context_preauthenticate (SoupContext *ctx, const char *header)
+{
+ GSList *headers;
+
+ g_return_if_fail (ctx != NULL);
+ g_return_if_fail (header != NULL);
+
+ headers = g_slist_append (NULL, (char *)header);
+ update_auth_internal (ctx, NULL, headers, FALSE);
+ g_slist_free (headers);
+}
+
+gboolean
+soup_context_authenticate_auth (SoupContext *ctx, SoupAuth *auth)
+{
+ const SoupUri *uri = ctx->uri;
+
+ if (!uri->user && soup_auth_fn) {
+ (*soup_auth_fn) (auth->type,
+ (SoupUri *) uri,
+ auth->realm,
+ soup_auth_fn_user_data);
+ }
+
+ if (!uri->user)
+ return FALSE;
+
+ soup_auth_initialize (auth, uri);
+ return TRUE;
+}
+
+void
+soup_context_invalidate_auth (SoupContext *ctx, SoupAuth *auth)
+{
+ char *realm;
+ gpointer key, value;
+
+ g_return_if_fail (ctx != NULL);
+ g_return_if_fail (auth != NULL);
+
+ /* Try to just clean up the auth without removing it. */
+ if (soup_auth_invalidate (auth))
+ return;
+
+ /* Nope, need to remove it completely */
+ realm = g_strdup_printf ("%d:%s", auth->type, auth->realm);
+
+ if (g_hash_table_lookup_extended (ctx->server->auths, realm,
+ &key, &value) &&
+ auth == (SoupAuth *)value) {
+ g_hash_table_remove (ctx->server->auths, realm);
+ g_free (key);
+ soup_auth_free (auth);
+ }
+ g_free (realm);
}
Index: libsoup/soup-context.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-context.h,v
retrieving revision 1.17
diff -u -w -r1.17 soup-context.h
--- libsoup/soup-context.h 9 May 2003 15:25:31 -0000 1.17
+++ libsoup/soup-context.h 7 Aug 2003 18:24:22 -0000
@@ -65,4 +65,8 @@
void soup_connection_purge_idle (void);
+
+void soup_context_preauthenticate (SoupContext *ctx,
+ const char *header);
+
#endif /*SOUP_CONTEXT_H*/
Index: libsoup/soup-message.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-message.c,v
retrieving revision 1.59
diff -u -w -r1.59 soup-message.c
--- libsoup/soup-message.c 29 Jul 2003 15:04:01 -0000 1.59
+++ libsoup/soup-message.c 7 Aug 2003 18:24:22 -0000
@@ -8,6 +8,8 @@
* Copyright (C) 2000-2002, Ximian, Inc.
*/
+#include <string.h>
+
#include "soup-auth.h"
#include "soup-error.h"
#include "soup-message.h"
@@ -623,157 +625,19 @@
}
static void
-maybe_validate_auth (SoupMessage *msg, gpointer user_data)
-{
- gboolean proxy = GPOINTER_TO_INT (user_data);
- int auth_failure;
- SoupContext *ctx;
- SoupAuth *auth;
-
- if (proxy) {
- ctx = soup_get_proxy ();
- auth_failure = SOUP_ERROR_PROXY_UNAUTHORIZED; /* 407 */
- }
- else {
- ctx = msg->context;
- auth_failure = SOUP_ERROR_UNAUTHORIZED; /* 401 */
- }
-
- auth = soup_auth_lookup (ctx);
- if (!auth)
- return;
-
- if (msg->errorcode == auth_failure) {
- /* Pass through */
- }
- else if (msg->errorclass == SOUP_ERROR_CLASS_SERVER_ERROR) {
- /*
- * We have no way of knowing whether our auth is any good
- * anymore, so just invalidate it and start from the
- * beginning next time.
- */
- soup_auth_invalidate (auth, ctx);
- }
- else {
- auth->status = SOUP_AUTH_STATUS_SUCCESSFUL;
- auth->controlling_msg = NULL;
- }
-} /* maybe_validate_auth */
-
-static void
authorize_handler (SoupMessage *msg, gboolean proxy)
{
- const GSList *vals;
- SoupAuth *auth;
SoupContext *ctx;
- const SoupUri *uri;
-
- if (msg->connection->auth &&
- msg->connection->auth->status == SOUP_AUTH_STATUS_SUCCESSFUL)
- goto THROW_CANT_AUTHENTICATE;
ctx = proxy ? soup_get_proxy () : msg->context;
- uri = soup_context_get_uri (ctx);
-
- vals = soup_message_get_header_list (msg->response_headers,
- proxy ?
- "Proxy-Authenticate" :
- "WWW-Authenticate");
- if (!vals) goto THROW_CANT_AUTHENTICATE;
-
- auth = soup_auth_lookup (ctx);
- if (auth && auth->type == SOUP_AUTH_TYPE_NTLM)
- auth = NULL;
-
- if (auth) {
- g_assert (auth->status != SOUP_AUTH_STATUS_INVALID);
-
- if (auth->status == SOUP_AUTH_STATUS_PENDING) {
- if (auth->controlling_msg == msg) {
- auth->status = SOUP_AUTH_STATUS_FAILED;
- goto THROW_CANT_AUTHENTICATE;
- }
- else {
- soup_message_requeue (msg);
- return;
- }
- }
- else if (auth->status == SOUP_AUTH_STATUS_FAILED ||
- auth->status == SOUP_AUTH_STATUS_SUCCESSFUL) {
- /*
- * We've failed previously, but we'll give it
- * another go, or we've been successful
- * previously, but it's not working anymore.
- *
- * Invalidate the auth, so it's removed from the
- * hash and try it again as if we never had it
- * in the first place.
- */
- soup_auth_invalidate (auth, ctx);
- soup_message_requeue (msg);
- return;
- }
- }
-
- if (!auth) {
- auth = soup_auth_new_from_header_list (uri, vals);
-
- if (!auth) {
- soup_message_set_error_full (
- msg,
- proxy ?
- SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
- SOUP_ERROR_CANT_AUTHENTICATE,
- proxy ?
- "Unknown authentication scheme "
- "required by proxy" :
- "Unknown authentication scheme "
- "required");
- return;
- }
-
- auth->status = SOUP_AUTH_STATUS_PENDING;
- auth->controlling_msg = msg;
- soup_message_add_handler (msg, SOUP_HANDLER_PRE_BODY,
- maybe_validate_auth,
- GINT_TO_POINTER (proxy));
- }
-
- /*
- * Call registered authenticate handler
- */
- if (!uri->user && soup_auth_fn)
- (*soup_auth_fn) (auth->type,
- (SoupUri *) uri,
- auth->realm,
- soup_auth_fn_user_data);
-
- if (!uri->user) {
- soup_auth_free (auth);
- goto THROW_CANT_AUTHENTICATE;
- }
-
- /*
- * Initialize with auth data (possibly returned from
- * auth callback).
- */
- soup_auth_initialize (auth, uri);
-
- if (auth->type == SOUP_AUTH_TYPE_NTLM &&
- auth->status == SOUP_AUTH_STATUS_SUCCESSFUL)
- msg->connection->auth = auth;
- else
- soup_auth_set_context (auth, ctx);
-
+ if (soup_context_update_auth (ctx, msg))
soup_message_requeue (msg);
-
- return;
-
- THROW_CANT_AUTHENTICATE:
+ else {
soup_message_set_error (msg,
proxy ?
SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
SOUP_ERROR_CANT_AUTHENTICATE);
+ }
}
static void
Index: libsoup/soup-private.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-private.h,v
retrieving revision 1.55
diff -u -w -r1.55 soup-private.h
--- libsoup/soup-private.h 11 Jun 2003 18:11:30 -0000 1.55
+++ libsoup/soup-private.h 7 Aug 2003 18:24:22 -0000
@@ -48,7 +48,9 @@
gchar *host;
GSList *connections; /* CONTAINS: SoupConnection */
GHashTable *contexts; /* KEY: uri->path, VALUE: SoupContext */
- GHashTable *valid_auths; /* KEY: uri->path, VALUE: SoupAuth */
+ gboolean use_ntlm;
+ GHashTable *auth_realms; /* KEY: uri->path, VALUE: scheme:realm */
+ GHashTable *auths; /* KEY: scheme:realm, VALUE: SoupAuth */
} SoupHost;
struct _SoupSocket {
@@ -124,6 +126,20 @@
SoupServerMessage *server_msg;
};
+/* from soup-context.c */
+
+SoupAuth *soup_context_lookup_auth (SoupContext *ctx,
+ SoupMessage *msg);
+
+gboolean soup_context_update_auth (SoupContext *ctx,
+ SoupMessage *msg);
+
+gboolean soup_context_authenticate_auth (SoupContext *ctx,
+ SoupAuth *auth);
+
+void soup_context_invalidate_auth (SoupContext *ctx,
+ SoupAuth *auth);
+
/* from soup-message.c */
void soup_message_issue_callback (SoupMessage *req);
Index: libsoup/soup-queue.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-queue.c,v
retrieving revision 1.86
diff -u -w -r1.86 soup-queue.c
--- libsoup/soup-queue.c 23 Jun 2003 15:52:33 -0000 1.86
+++ libsoup/soup-queue.c 7 Aug 2003 18:24:22 -0000
@@ -295,23 +295,22 @@
ctx = proxy_auth ? soup_get_proxy () : msg->context;
- if (msg->connection->auth)
- auth = msg->connection->auth;
- else
- auth = soup_auth_lookup (ctx);
+ auth = soup_context_lookup_auth (ctx, msg);
+ if (!auth)
+ return;
+ if (!auth->authenticated &&
+ !soup_context_authenticate_auth (ctx, auth))
+ return;
- if (auth) {
token = soup_auth_authorize (auth, msg);
if (token) {
- g_string_sprintfa (header,
- "%s: %s\r\n",
+ g_string_sprintfa (header, "%s: %s\r\n",
proxy_auth ?
"Proxy-Authorization" :
"Authorization",
token);
g_free (token);
}
- }
}
struct SoupUsedHeaders {
Index: tests/.cvsignore
===================================================================
RCS file: /cvs/gnome/libsoup/tests/.cvsignore,v
retrieving revision 1.2
diff -u -w -r1.2 .cvsignore
--- tests/.cvsignore 13 Nov 2002 17:06:58 -0000 1.2
+++ tests/.cvsignore 7 Aug 2003 18:24:22 -0000
@@ -2,3 +2,4 @@
Makefile.in
get
timeserver
+auth-test
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/gnome/libsoup/tests/Makefile.am,v
retrieving revision 1.5
diff -u -w -r1.5 Makefile.am
--- tests/Makefile.am 15 Nov 2002 16:18:04 -0000 1.5
+++ tests/Makefile.am 7 Aug 2003 18:24:22 -0000
@@ -2,12 +2,17 @@
-I$(top_srcdir) \
$(GLIB_CFLAGS)
-noinst_PROGRAMS = get #timeserver
+noinst_PROGRAMS = get timeserver
get_SOURCES = get.c
get_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
-#timeserver_SOURCES = timeserver.c
+timeserver_SOURCES = timeserver.c
-#timeserver_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
+timeserver_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
+
+check_PROGRAMS = auth-test
+
+auth_test_SOURCES = auth-test.c
+auth_test_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
Index: tests/auth-test.c
===================================================================
RCS file: tests/auth-test.c
diff -N tests/auth-test.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ tests/auth-test.c 7 Aug 2003 18:24:22 -0000
@@ -0,0 +1,273 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libsoup/soup.h"
+#include "libsoup/soup-auth.h"
+#include "libsoup/soup-private.h"
+
+int errors = 0;
+
+typedef struct {
+ const char *explanation;
+ const char *url;
+ const char *expected;
+ gboolean success;
+} SoupAuthTest;
+
+SoupAuthTest tests[] = {
+ { "No auth available, should fail",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt",
+ "0", FALSE },
+
+ { "Should fail with no auth, fail again with bad password, and give up",
+ "http://user4:realm4 primates ximian com/~danw/soup-test/Basic/realm2/index.txt",
+ "04", FALSE },
+
+ { "Known realm, auth provided, so should succeed immediately",
+ "http://user1:realm1 primates ximian com/~danw/soup-test/Basic/realm1/index.txt",
+ "1", TRUE },
+
+ { "Now should automatically reuse previous auth",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt",
+ "1", TRUE },
+
+ { "Subdir should also automatically reuse auth",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/subdir/index.txt",
+ "1", TRUE },
+
+ { "Subdir should retry last auth, but will fail this time",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt",
+ "1", FALSE },
+
+ { "Now should use provided auth on first try",
+ "http://user2:realm2 primates ximian com/~danw/soup-test/Basic/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Reusing last auth. Should succeed on first try",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Reuse will fail, but 2nd try will succeed because it's a known realm",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/realm1/index.txt",
+ "21", TRUE },
+
+ { "Should succeed on first try. (Known realm with cached password)",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm2/index.txt",
+ "2", TRUE },
+
+ { "Fail once, then use password",
+ "http://user3:realm3 primates ximian com/~danw/soup-test/Basic/realm3/index.txt",
+ "03", TRUE },
+
+
+ { "No auth available, should fail",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt",
+ "0", FALSE },
+
+ { "Should fail with no auth, fail again with bad password, and give up",
+ "http://user4:realm4 primates ximian com/~danw/soup-test/Digest/realm2/index.txt",
+ "04", FALSE },
+
+ { "Known realm, auth provided, so should succeed immediately",
+ "http://user1:realm1 primates ximian com/~danw/soup-test/Digest/realm1/index.txt",
+ "1", TRUE },
+
+ { "Now should automatically reuse previous auth",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt",
+ "1", TRUE },
+
+ { "Subdir should also automatically reuse auth",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/subdir/index.txt",
+ "1", TRUE },
+
+ { "Subdir should retry last auth, but will fail this time",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt",
+ "1", FALSE },
+
+ { "Now should use provided auth on first try",
+ "http://user2:realm2 primates ximian com/~danw/soup-test/Digest/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Reusing last auth. Should succeed on first try",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Should succeed on first try because of earlier domain directive",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/realm1/index.txt",
+ "1", TRUE },
+
+ { "Should succeed on first try. (Known realm with cached password)",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm2/index.txt",
+ "2", TRUE },
+
+ { "Fail once, then use password",
+ "http://user3:realm3 primates ximian com/~danw/soup-test/Digest/realm3/index.txt",
+ "03", TRUE },
+
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt",
+ "1", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/realm1/index.txt",
+ "1", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm2/index.txt",
+ "2", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Basic/realm3/index.txt",
+ "3", TRUE },
+
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt",
+ "1", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt",
+ "2", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/realm1/index.txt",
+ "1", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm2/index.txt",
+ "2", TRUE },
+
+ { "Make sure we haven't forgotten anything",
+ "http://primates.ximian.com/~danw/soup-test/Digest/realm3/index.txt",
+ "3", TRUE }
+};
+int ntests = sizeof (tests) / sizeof (tests[0]);
+
+static const char *auths[] = {
+ "no password", "password 1",
+ "password 2", "password 3",
+ "intentionally wrong password",
+};
+
+static int
+identify_auth (SoupMessage *msg)
+{
+ SoupAuth *auth;
+ char *header;
+ int num;
+
+ auth = soup_context_lookup_auth (msg->context, msg);
+ if (!auth || !auth->authenticated)
+ return 0;
+
+ header = soup_auth_authorize (auth, msg);
+ if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
+ char *token;
+ int len;
+
+ token = soup_base64_decode (header + 6, &len);
+ num = token[len - 1] - '0';
+ g_free (token);
+ } else {
+ const char *user;
+
+ user = strstr (header, "username=\"user");
+ if (user)
+ num = user[14] - '0';
+ else
+ num = 0;
+ }
+
+ g_free (header);
+ return num;
+}
+
+static void
+handler (SoupMessage *msg, gpointer data)
+{
+ char *expected = data;
+ int auth, exp;
+
+ auth = identify_auth (msg);
+
+ printf (" %d %s (using %s)\n", msg->errorcode, msg->errorphrase,
+ auths[auth]);
+
+ if (*expected) {
+ exp = *expected - '0';
+ if (auth != exp) {
+ printf (" expected %s!\n", auths[exp]);
+ errors++;
+ }
+ memmove (expected, expected + 1, strlen (expected));
+ } else {
+ printf (" expected to be finished\n");
+ errors++;
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ SoupContext *ctx;
+ SoupMessage *msg;
+ char *expected;
+ int i;
+
+ for (i = 0; i < ntests; i++) {
+ printf ("Test %d: %s\n", i + 1, tests[i].explanation);
+
+ printf (" GET %s\n", tests[i].url);
+ ctx = soup_context_get (tests[i].url);
+ if (!ctx) {
+ fprintf (stderr, "auth-test: Could not parse URI\n");
+ exit (1);
+ }
+
+ msg = soup_message_new (ctx, SOUP_METHOD_GET);
+
+ expected = g_strdup (tests[i].expected);
+ soup_message_add_error_code_handler (
+ msg, SOUP_ERROR_UNAUTHORIZED,
+ SOUP_HANDLER_PRE_BODY, handler, expected);
+ soup_message_add_error_code_handler (
+ msg, SOUP_ERROR_OK, SOUP_HANDLER_PRE_BODY,
+ handler, expected);
+ soup_message_send (msg);
+ if (msg->errorcode != SOUP_ERROR_CANT_AUTHENTICATE &&
+ msg->errorcode != SOUP_ERROR_OK) {
+ printf (" %d %s !\n", msg->errorcode,
+ msg->errorphrase);
+ }
+ if (*expected) {
+ printf (" expected %d more round(s)\n",
+ strlen (expected));
+ errors++;
+ }
+ g_free (expected);
+
+ if (SOUP_ERROR_IS_SUCCESSFUL (msg->errorcode) !=
+ tests[i].success) {
+ printf (" expected %s\n",
+ tests[i].success ? "success" : "failure");
+ errors++;
+ }
+
+ printf ("\n");
+
+ soup_message_free (msg);
+ /* We don't free ctx because if we did, we'd end up
+ * discarding the cached auth info at the end of each
+ * test.
+ */
+ }
+
+ printf ("\nauth-test: %d errors\n", errors);
+ return errors;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]