From 30c217c0dca4c7a72ff4248fc4d4504fc1d85fc0 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Tue, 7 Jul 2009 20:05:34 +0000 Subject: Store exchanged attributes in the cookie. --- module/consumer.cc | 24 +++- module/mod_auth_singleid.c | 273 +++++++++++++++++++++++++++++++-------------- module/mod_auth_singleid.h | 3 +- 3 files changed, 215 insertions(+), 85 deletions(-) diff --git a/module/consumer.cc b/module/consumer.cc index 32db8cb..9c5da89 100644 --- a/module/consumer.cc +++ b/module/consumer.cc @@ -16,6 +16,9 @@ using opkele::failed_discovery; using opkele::failed_lookup; using opkele::failed_xri_resolution; using opkele::id_res_bad_nonce; +using opkele::id_res_bad_return_to; +using opkele::id_res_failed; +using opkele::id_res_mismatch; using opkele::no_endpoint; using opkele::openid_endpoint_t; using opkele::openid_message_t; @@ -271,8 +274,13 @@ Consumer::retrieve_assoc(const string& server, const string& handle) } } + /* + * Yes, we return this when not found, it helps the user experience, if + * apache restarted. + */ + if (!assoc) - throw failed_lookup("could not retrieve association for server: " + server); + throw dumb_RP("could not retrieve association for server: " + server); return assoc_t(assoc); } @@ -362,7 +370,7 @@ process_ax_values (sid_request_t *req, sid_attribute_t *attr, const string_list& array[i] = it->c_str(); array[i] = NULL; - sid_request_attribute_values (req, attr, array); + sid_request_attribute_values (req, attr, array, values.size()); delete [] array; } @@ -483,6 +491,18 @@ complete_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms, string identity = consumer.get_claimed_id(); sid_request_authenticated (req, identity.c_str()); parse_ax_attributes(req, params, attributes); + } catch (id_res_mismatch &ex) { + sid_request_respond (req, 403, "Signature mismatch", NULL); + sid_request_log_error (req, "signature did not match data", ex.what()); + } catch (bad_input &ex) { + sid_request_respond (req, 403, "Bad authentication input", NULL); + sid_request_log_error (req, "bad input", ex.what()); + } catch (id_res_bad_return_to &ex) { + sid_request_respond (req, 403, "Bad authenticated address", NULL); + sid_request_log_error (req, "bad return to", ex.what()); + } catch (id_res_failed &ex) { + sid_request_respond (req, 503, "Service error, try again", NULL); + sid_request_log_error (req, "checking response failed", ex.what()); } catch (exception &ex) { sid_request_respond (req, 500, NULL, NULL); sid_request_log_error (req, "error while completing authentication", ex.what()); diff --git a/module/mod_auth_singleid.c b/module/mod_auth_singleid.c index e9d195a..41402ed 100644 --- a/module/mod_auth_singleid.c +++ b/module/mod_auth_singleid.c @@ -81,6 +81,16 @@ enum { REGEX = 2 }; +/* + * Per request information. + */ + +typedef struct sid_session { + char *identifier; + apr_array_header_t *values; + time_t expiry; +} sid_session_t; + /* * Per directory configuration. */ @@ -114,6 +124,8 @@ get_token (apr_pool_t *pool, const char **line, const char *delims) { const char *beg, *end; const char *str = *line; + char *result, *from, *at; + int quoted = 0; /* Find first non-white byte */ while (*str && apr_isspace(*str)) @@ -136,6 +148,7 @@ get_token (apr_pool_t *pool, const char **line, const char *delims) /* Trim the quotes if present */ if (beg + 1 < end && *beg == '"' && *(end - 1) == '"') { + quoted = 1; ++beg; --end; } @@ -149,7 +162,23 @@ get_token (apr_pool_t *pool, const char **line, const char *delims) ++str; *line = str; - return apr_pstrndup (pool, beg, end - beg); + result = apr_pstrndup (pool, beg, end - beg); + + /* Unquote vigorously */ + if (quoted) { + at = from = result; + for (;;) { + if (from[0] == '\\' && from[1] != '\0') + *(at++) = *(++from); + else + *(at++) = *from; + if (*from == '\0') + break; + ++from; + } + } + + return result; } /* ------------------------------------------------------------------------------- @@ -483,11 +512,6 @@ static const command_rec command_table[] = { * COOKIE SESSIONS */ -typedef struct sid_session { - char *identifier; - time_t expiry; -} sid_session_t; - /* Secret used to sign session cookies */ static unsigned char session_secret[40]; @@ -577,6 +601,7 @@ session_load_info (sid_context_t *ctx, request_rec *r) const char *value; char *token, *sig, *end; char *identifier; + char **here; long expiry; value = session_cookie_value (r, ctx->cookie_name); @@ -589,7 +614,7 @@ session_load_info (sid_context_t *ctx, request_rec *r) /* The version of the session info, only 1 supported for now */ token = get_token (r->pool, &value, " "); - if (!token || strcmp (token, "1") != 0) + if (!token || strcmp (token, "2") != 0) return NULL; token = get_token (r->pool, &value, " "); @@ -610,20 +635,38 @@ session_load_info (sid_context_t *ctx, request_rec *r) sess->expiry = expiry; sess->identifier = identifier; + /* The remainder are values */ + for (;;) { + token = get_token (r->pool, &value, " "); + if (!token) + break; + if (!sess->values) + sess->values = apr_array_make (r->pool, 8, sizeof (char*)); + here = (char**)apr_array_push (sess->values); + *here = token; + } + return sess; } static void session_send_info (sid_context_t *ctx, request_rec *r, sid_session_t *sess) { - char *cookie, *sig, *value; + char *cookie, *sig, *payload, *values; + + /* All the values */ + if (sess->values) + values = apr_array_pstrcat (r->pool, sess->values, ' '); + else + values = ""; /* Create the cookie value and sign it */ - value = apr_psprintf (r->pool, "1 %ld \"%s\"", (long)sess->expiry, ap_escape_quotes (r->pool, sess->identifier)); - sig = session_create_sig (r->pool, value); + payload = apr_psprintf (r->pool, "2 %ld \"%s\" %s", (long)sess->expiry, + ap_escape_quotes (r->pool, sess->identifier), values); + sig = session_create_sig (r->pool, payload); /* Build up the full cookie spec */ - cookie = apr_psprintf (r->pool, "%s=%s %s; httponly; max-age=86400", ctx->cookie_name, sig, value); + cookie = apr_psprintf (r->pool, "%s=%s %s; httponly", ctx->cookie_name, sig, payload); apr_table_addn (r->err_headers_out, "Set-Cookie", cookie); } @@ -631,8 +674,20 @@ static sid_session_t* session_copy_info (apr_pool_t *p, sid_session_t *sess) { sid_session_t *copy = apr_pcalloc (p, sizeof (*sess)); + char **data; + int i; + copy->expiry = sess->expiry; copy->identifier = apr_pstrdup (p, sess->identifier); + + /* Copy the values if necessary */ + if (sess->values) { + copy->values = apr_array_copy (p, sess->values); + data = (char**)copy->values->elts; + for (i = 0; i < copy->values->nelts; ++i) + data[i] = apr_pstrdup (p, data[i]); + } + return copy; } @@ -643,9 +698,9 @@ session_copy_info (apr_pool_t *p, sid_session_t *sess) struct sid_request { int result; request_rec *rec; + sid_session_t *sess; }; - void sid_request_log_error (sid_request_t *req, const char *message, const char *detail) { @@ -714,81 +769,39 @@ sid_request_respond (sid_request_t *req, int code, const char *reason, req->result = code; } -static void -set_request_authenticated (sid_context_t *ctx, request_rec *r, sid_session_t *sess) -{ - ap_regmatch_t matches[AP_MAX_REG_MATCH]; - char *user = NULL; - - /* Try and calculate a user name */ - switch (ctx->user_match) { - case SUFFIX: - if (ctx->identifier) { - user = (char*)ap_stripprefix (sess->identifier, ctx->identifier); - if (user != sess->identifier) { - /* Some delimiters that we strip from between value and identifier */ - while (strchr ("?/#", *user)) - ++user; - } - } - break; - - case REGEX: - assert (ctx->converter); - if (ap_regexec (ctx->converter, sess->identifier, AP_MAX_REG_MATCH, matches, 0) == 0) - user = ap_pregsub (r->pool, "$1", sess->identifier, AP_MAX_REG_MATCH, matches); - break; - } - - if (!user) - user = sess->identifier; - - r->user = user; - r->ap_auth_type = SID_AUTHTYPE; - apr_table_set (r->subprocess_env, "OPENID_IDENTIFIER", sess->identifier); - ap_set_module_config (r->request_config, &auth_singleid_module, sess); -} - void sid_request_authenticated (sid_request_t *req, const char *identifier) { - sid_context_t *ctx; sid_session_t *sess; sess = apr_pcalloc (req->rec->pool, sizeof (sid_session_t)); sess->identifier = apr_pstrdup (req->rec->pool, identifier); sess->expiry = time (NULL) + 86400; - - ctx = ap_get_module_config (req->rec->per_dir_config, &auth_singleid_module); - assert (ctx); - - set_request_authenticated (ctx, req->rec, sess); - session_send_info (ctx, req->rec, sess); + req->sess = sess; } void -sid_request_attribute_values (sid_request_t *req, sid_attribute_t *attr, const char **values) +sid_request_attribute_values (sid_request_t *req, sid_attribute_t *attr, + const char **values, size_t n_values) { + sid_session_t *sess; const char **val; - char buf[64]; - char *name; + char **here; int i = 0; + assert (req->sess); + + /* Put all the values into the session array */ + sess = req->sess; + if (!sess->values) + sess->values = apr_array_make (req->rec->pool, 8, sizeof (char*)); + /* Setup all the values */ for (i = 0, val = values; *val; ++val, ++i) { - snprintf (buf, sizeof (buf), "AX_VALUE_%s_%d", attr->alias, i); - name = apr_pstrdup (req->rec->pool, buf); - strupcase (name); - apr_table_setn (req->rec->subprocess_env, name, - apr_pstrdup (req->rec->pool, *val)); + here = apr_array_push (sess->values); + *here = apr_psprintf (req->rec->pool, "%s:\"%s\"", attr->alias, + ap_escape_quotes (req->rec->pool, *val)); } - - /* Put in the count */ - snprintf (buf, sizeof (buf), "AX_COUNT_%s", attr->alias); - name = apr_pstrdup (req->rec->pool, buf); - strupcase (name); - snprintf (buf, sizeof (buf), "%d", i); - apr_table_set (req->rec->subprocess_env, name, apr_pstrdup (req->rec->pool, buf)); } /* --------------------------------------------------------------------------------------- @@ -813,6 +826,90 @@ hook_child (apr_pool_t *p, server_rec *s) shared_child (p, s); } +static void +set_authenticated (sid_context_t *ctx, request_rec *r, sid_session_t *sess) +{ + ap_regmatch_t matches[AP_MAX_REG_MATCH]; + char *user = NULL; + + /* Try and calculate a user name */ + switch (ctx->user_match) { + case SUFFIX: + if (ctx->identifier) { + user = (char*)ap_stripprefix (sess->identifier, ctx->identifier); + if (user != sess->identifier) { + /* Some delimiters that we strip from between value and identifier */ + while (strchr ("?/#", *user)) + ++user; + } + } + break; + + case REGEX: + assert (ctx->converter); + if (ap_regexec (ctx->converter, sess->identifier, AP_MAX_REG_MATCH, matches, 0) == 0) + user = ap_pregsub (r->pool, "$1", sess->identifier, AP_MAX_REG_MATCH, matches); + break; + } + + if (!user) + user = apr_pstrdup (r->pool, sess->identifier); + + r->user = user; + r->ap_auth_type = SID_AUTHTYPE; +} + +static void +set_env_variables (sid_context_t *ctx, request_rec *r, sid_session_t *sess) +{ + char *name, *value, *prev; + const char *line; + char **data; + int i, count; + + apr_table_setn (r->subprocess_env, "OPENID_IDENTIFIER", sess->identifier); + + if (sess->values) { + data = (char**)sess->values->elts; + prev = name = NULL; + count = 0; + + for (i = 0; i < sess->values->nelts; ++i) { + line = data[i]; + + /* The two parts of the attribute value */ + name = get_token (r->pool, &line, ":"); + value = get_token (r->pool, &line, ":"); + if (!name || !value) + continue; + + strupcase (name); + + /* Set the last count if necessary */ + if (prev && strcmp (name, prev) != 0) { + apr_table_setn (r->subprocess_env, + apr_psprintf (r->pool, "AX_COUNT_%s", prev), + apr_psprintf (r->pool, "%d", count)); + count = 0; + } + + /* And dig out the value */ + apr_table_setn (r->subprocess_env, + apr_psprintf (r->pool, "AX_VALUE_%s_%d", name, count), value); + + prev = name; + ++count; + } + + /* Set the last count if necessary */ + if (name) { + apr_table_setn (r->subprocess_env, + apr_psprintf (r->pool, "AX_COUNT_%s", prev), + apr_psprintf (r->pool, "%d", count)); + } + } +} + static int hook_authenticate (request_rec* r) { @@ -821,6 +918,7 @@ hook_authenticate (request_rec* r) sid_request_t req; const char* authtype; request_rec* mainreq; + int authenticated = 0; /* Make sure it's for us */ if (!(authtype = ap_auth_type (r)) || strcasecmp (SID_AUTHTYPE, authtype) != 0) @@ -838,29 +936,40 @@ hook_authenticate (request_rec* r) while (mainreq->prev != NULL) mainreq = mainreq->prev; + req.result = OK; + req.rec = r; + req.sess = NULL; + /* Check if we've already authenticated this request */ sess = ap_get_module_config (mainreq->request_config, &auth_singleid_module); if (sess != NULL) { - if (mainreq != r) { + if (mainreq != r) sess = session_copy_info (r->pool, sess); - set_request_authenticated (ctx, r, sess); - } - return OK; } /* Load the session info from the request and see if we've authenticated */ - sess = session_load_info (ctx, r); - if (sess != NULL) { - set_request_authenticated (ctx, r, sess); - return OK; + if (sess == NULL) + sess = session_load_info (ctx, r); + + /* Allocate a new empty session info */ + if (sess == NULL) { + /* Do the OpenID magic */ + sid_consumer_authenticate (&req, ctx->store, ctx->trust_root, + ctx->identifier, ctx->attributes); + authenticated = 1; + sess = req.sess; } - req.result = OK; - req.rec = r; + /* Setup the request response */ + if (sess != NULL && req.result == OK) { + set_authenticated (ctx, r, sess); + set_env_variables (ctx, r, sess); + ap_set_module_config (r->request_config, &auth_singleid_module, sess); - /* Do the OpenID magic */ - sid_consumer_authenticate (&req, ctx->store, ctx->trust_root, - ctx->identifier, ctx->attributes); + /* If we actually authenticated the user, then set the cookie */ + if (authenticated) + session_send_info (ctx, r, sess); + } return req.result; } diff --git a/module/mod_auth_singleid.h b/module/mod_auth_singleid.h index a81652d..61e2c91 100644 --- a/module/mod_auth_singleid.h +++ b/module/mod_auth_singleid.h @@ -42,7 +42,8 @@ void sid_request_authenticated (sid_request_t *req, void sid_request_attribute_values (sid_request_t *req, sid_attribute_t *attr, - const char **values); + const char **values, + size_t n_values); /* ----------------------------------------------------------------------------------- * STORAGE: Actually, communications white-board between processes/threads. -- cgit v1.2.3