summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2009-07-07 20:05:29 +0000
committerStef Walter <stef@memberwebs.com>2009-07-07 20:05:29 +0000
commit2989ee8b72ddb3995e5a4686c988385d05493365 (patch)
tree566819988818d2478a556394d2a7243d8f1a8b1f
parent2622d0eb7d32ae035d9c04d92de4e61588b1950e (diff)
Implement simple AX attribute exchange.
* Does not yet handle setting attributes from the cookie.
-rw-r--r--ideas.txt2
-rw-r--r--module/consumer.cc255
-rw-r--r--module/mod_auth_singleid.c130
-rw-r--r--module/mod_auth_singleid.h16
4 files changed, 358 insertions, 45 deletions
diff --git a/ideas.txt b/ideas.txt
index f10cbb5..2848b71 100644
--- a/ideas.txt
+++ b/ideas.txt
@@ -3,7 +3,7 @@ AuthSingleIdIdentifier https://id.familymembers.com/
AuthSingleIdCookie openid
AuthSingleIdUserPrefix https://id.familymembers.com/
AuthSingleIdUserSuffix
-AuthSingleIdAttribute url alias count
+AuthSingleIdAttribute alias url [count [required]]
AX_TYPE_FNAME=http://example.com/schema/fullname
AX_VALUE_FNAME=John Smith
diff --git a/module/consumer.cc b/module/consumer.cc
index 49620db..32db8cb 100644
--- a/module/consumer.cc
+++ b/module/consumer.cc
@@ -26,6 +26,12 @@ using opkele::secret_t;
using std::string;
using std::vector;
+typedef std::list<std::string> string_list;
+
+/* -----------------------------------------------------------------------------
+ * UTILITY
+ */
+
class LockShared
{
public:
@@ -35,6 +41,102 @@ public:
{ sid_shared_unlock (); }
};
+
+static int
+parse_number (const string& str)
+{
+ char *end = NULL;
+ int result = strtoul (str.c_str(), &end, 10);
+ if (!end || *end != '\0')
+ result = 0;
+ return result;
+}
+
+static string
+format_number (int num)
+{
+ char buf[64];
+ snprintf (buf, sizeof (buf), "%d", num);
+ return string(buf);
+}
+
+static void
+parse_query_string (const char *qs, params_t &params)
+{
+ string pair, key, value;
+ const char *at;
+
+ if (qs == NULL)
+ return;
+
+ while (*qs != 0) {
+ at = strchr (qs, '&');
+ if (at == NULL)
+ at = qs + strlen (qs);
+ pair = string(qs, at);
+ string::size_type loc = pair.find('=', 0);
+ if (loc != string::npos) {
+ key = pair.substr (0, loc);
+ value = pair.substr (loc + 1);
+ } else {
+ key = pair;
+ value = "";
+ }
+
+ params[opkele::util::url_decode(key)] = opkele::util::url_decode(value);
+ if (*at == 0)
+ break;
+ qs = at + 1;
+ }
+}
+
+static void
+filter_prefixed_params (params_t &params, params_t &openid, const string& prefix)
+{
+ /*
+ * Expects message to have openid. prefix present, and strips
+ * openid. prefix from output
+ */
+
+ for (params_t::iterator it = params.begin(); it != params.end(); ) {
+ const string& name = it->first;
+ if (name.find (prefix) == 0) {
+ openid[name.substr(prefix.length())] = it->second;
+
+ /* We erase an increment together, must use post-increment operator */
+ params.erase (it++);
+ } else {
+ /* Did not match, just go to next element */
+ ++it;
+ }
+ }
+}
+
+static void
+filter_listed_params (const params_t &params, params_t &listed, const string& list)
+{
+ /* Expects message to have openid. prefix stripped from params */
+
+ if(!params.has_param(list))
+ return;
+
+ string slist = params.get_param(list);
+ string::size_type pos = 0;
+ for (;;) {
+ string::size_type at = slist.find(',', pos);
+ string name = slist.substr(pos, at - pos);
+ if(params.has_param(name))
+ listed[name] = params.get_param(name);
+ if(at == string::npos)
+ return;
+ pos = at + 1;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * CONSUMER CLASS
+ */
+
class Consumer :
public prequeue_RP
{
@@ -204,55 +306,132 @@ Consumer::check_nonce(const string& server, const string& nonce)
*/
static void
-filter_openid_params (params_t &params, params_t &openid)
+request_ax_attributes (sid_request_t *req, openid_message_t &message, sid_attribute_t *attrs)
{
- for (params_t::iterator it = params.begin(); it != params.end(); ) {
- const string& name = it->first;
- if (name.find ("openid.") == 0) {
- openid[name.substr(7)] = it->second;
+ sid_attribute_t *attr;
- /* We erase an increment together, must use post-increment operator */
- params.erase (it++);
- } else {
- /* Did not match, just go to next element */
- ++it;
+ /* Request attributes for attribute exchange */
+ if (!attrs)
+ return;
+
+ message["ns.ax"] = "http://openid.net/srv/ax/1.0";
+ message["ax.mode"] = "fetch_request";
+
+ string required, available;
+ for (attr = attrs; attr; attr = attr->next) {
+
+ /* Request the attribute */
+ string field("ax.type.");
+ field.append(attr->alias);
+ message[field] = attr->url;
+
+ /* Add to our reqired list */
+ string& list = attr->required ? required : available;
+ if(!list.empty())
+ list.append(",");
+ list.append(attr->alias);
+
+ /* How many to request? */
+ if (attr->count != 1) {
+ field.assign("ax.count.");
+ field.append(attr->alias);
+ if (attr->count == 0)
+ message[field] = "unlimited";
+ else
+ message[field] = format_number (attr->count);
}
}
+
+ if(!required.empty())
+ message["ax.required"] = required;
+ if(!available.empty())
+ message["ax.if_available"] = available;
}
static void
-parse_query_string (const char *qs, params_t &params)
+process_ax_values (sid_request_t *req, sid_attribute_t *attr, const string_list& values)
{
- string pair, key, value;
- const char *at;
+ const char** array = new const char*[values.size() + 1];
+ if (!array) {
+ sid_request_log_error (req, "out of memory", NULL);
+ return;
+ }
- if (qs == NULL)
+ int i = 0;
+ for(string_list::const_iterator it = values.begin(); it != values.end(); ++it, ++i)
+ array[i] = it->c_str();
+ array[i] = NULL;
+
+ sid_request_attribute_values (req, attr, array);
+ delete [] array;
+}
+
+static void
+parse_ax_attributes (sid_request_t *req, params_t &message, sid_attribute_t *attrs)
+{
+ /* Build a list of all signed params */
+ params_t checked;
+ filter_listed_params(message, checked, "signed");
+
+ /* Look through and find ax namespace */
+ string prefix;
+ for (params_t::iterator it = checked.begin(); it != checked.end(); ++it) {
+ if(it->first.find("ns.") == 0 && it->second == "http://openid.net/srv/ax/1.0") {
+ prefix = it->first.substr(3) + ".";
+ break;
+ }
+ }
+
+ if(prefix.empty())
return;
- while (*qs != 0) {
- at = strchr (qs, '&');
- if (at == NULL)
- at = qs + strlen (qs);
- pair = string(qs, at);
- string::size_type loc = pair.find('=', 0);
- if (loc != string::npos) {
- key = pair.substr (0, loc);
- value = pair.substr (loc + 1);
+ /* Look through and find all aliases */
+ params_t aliases;
+ string type_prefix = prefix + "type.";
+ for (params_t::iterator it = checked.begin(); it != checked.end(); ++it) {
+ if(it->first.find(type_prefix) == 0)
+ aliases[it->second] = it->first.substr(type_prefix.length());
+ }
+
+ if(aliases.empty())
+ return;
+
+ /* Now pull out the values for the attributes we want */
+ string_list values;
+ for(sid_attribute_t *attr = attrs; attr; attr = attr->next) {
+ if(!aliases.has_param(attr->url))
+ continue;
+
+ string alias = aliases.get_param(attr->url);
+ values.clear();
+
+ /* See if there's a count attribute */
+ string field = prefix + "count." + alias;
+ if(checked.has_param(field)) {
+
+ /* Get each value in turn */
+ int count = parse_number (checked.get_param(field));
+ for(int i = 0; i < count; ++i) {
+ field = prefix + "value." + alias + "." + format_number (i + 1);
+ if (checked.has_param (field))
+ values.push_back (checked.get_param (field));
+ }
+
+ /* No count, just a single value */
} else {
- key = pair;
- value = "";
+ field = prefix + "value." + alias;
+ if(checked.has_param(field))
+ values.push_back(checked.get_param(field));
}
- params[opkele::util::url_decode(key)] = opkele::util::url_decode(value);
- if (*at == 0)
- break;
- qs = at + 1;
+ /* Send off values */
+ process_ax_values(req, attr, values);
}
}
static void
-begin_auth (sid_request_t *req, Consumer &consumer, params_t &params,
- const string& trust_root, const string &identity)
+begin_auth (sid_request_t *req, Consumer &consumer, const string& trust_root,
+ const string &identity, sid_attribute_t *attributes)
{
params_t result;
string redirect;
@@ -261,6 +440,7 @@ begin_auth (sid_request_t *req, Consumer &consumer, params_t &params,
openid_message_t cm;
consumer.initiate (identity);
consumer.checkid_ (cm, opkele::mode_checkid_setup, consumer.get_this_url(), trust_root);
+ request_ax_attributes (req, cm, attributes);
redirect = cm.append_query (consumer.get_endpoint().uri);
} catch (failed_xri_resolution &ex) {
@@ -295,12 +475,14 @@ begin_auth (sid_request_t *req, Consumer &consumer, params_t &params,
}
static void
-complete_auth (sid_request_t *req, Consumer &consumer, params_t &params)
+complete_auth (sid_request_t *req, Consumer &consumer, params_t &params,
+ sid_attribute_t *attributes)
{
try {
consumer.id_res(params);
string identity = consumer.get_claimed_id();
sid_request_authenticated (req, identity.c_str());
+ parse_ax_attributes(req, params, attributes);
} catch (exception &ex) {
sid_request_respond (req, 500, NULL, NULL);
sid_request_log_error (req, "error while completing authentication", ex.what());
@@ -315,7 +497,8 @@ cancelled_auth (sid_request_t *req, Consumer &consumer, params_t &params)
extern "C" void
sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store,
- const char *trust_root, const char *identity)
+ const char *trust_root, const char *identity,
+ sid_attribute_t *attributes)
{
params_t params;
params_t openid;
@@ -324,7 +507,7 @@ sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store,
const char *qs = sid_request_qs (req);
parse_query_string (qs, params);
- filter_openid_params (params, openid);
+ filter_prefixed_params (params, openid, "openid.");
string url = sid_request_url (req, 1);
if (!params.empty())
@@ -333,7 +516,7 @@ sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store,
/* Returning (hopefully successful) authentication */
if (openid.has_param("assoc_handle")) {
- complete_auth (req, consumer, openid);
+ complete_auth (req, consumer, openid, attributes);
/* Returning cancelled authentication */
} else if (openid.has_param("mode") && openid.get_param("mode") == "cancel") {
@@ -343,6 +526,6 @@ sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store,
} else {
if (!trust_root)
trust_root = sid_request_url (req, 0);
- begin_auth (req, consumer, params, trust_root, identity);
+ begin_auth (req, consumer, trust_root, identity, attributes);
}
}
diff --git a/module/mod_auth_singleid.c b/module/mod_auth_singleid.c
index 2e46586..d2e53e8 100644
--- a/module/mod_auth_singleid.c
+++ b/module/mod_auth_singleid.c
@@ -73,6 +73,7 @@
extern module AP_MODULE_DECLARE_DATA auth_singleid_module;
#define VALID_NAME "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."
+#define VALID_ALIAS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
enum {
NONE = 0,
@@ -90,11 +91,36 @@ typedef struct sid_context {
int user_match;
ap_regex_t *converter;
sid_storage_t *store;
+ sid_attribute_t *attributes;
} sid_context_t;
#define SID_AUTHTYPE "SingleID"
/* -------------------------------------------------------------------------------
+ * UTILITY
+ */
+
+static void
+strupcase(char *str)
+{
+ while (*str) {
+ *str = apr_toupper(*str);
+ ++str;
+ }
+}
+
+static char*
+safe_get_token (apr_pool_t *pool, const char **line, int accept_white)
+{
+ /* HACK: ap_get_token() endless loop if string starts with delim */
+ const char *orig = *line;
+ char *result = ap_get_token (pool, line, accept_white);
+ if (orig == *line && orig[0])
+ (*line)++;
+ return result;
+}
+
+/* -------------------------------------------------------------------------------
* SHARED MEMORY and LOCKING
*/
@@ -344,6 +370,67 @@ set_cookie_name (cmd_parms *cmd, void *config, const char *val)
return NULL;
}
+static const char*
+set_attribute (cmd_parms *cmd, void *config, const char *val)
+{
+ sid_context_t *ctx = config;
+ sid_attribute_t attr, *at;
+ const char *flag;
+ char *end;
+
+ memset (&attr, 0, sizeof (attr));
+ attr.count = 1;
+
+ /* First word is the alias */
+ attr.alias = ap_getword_conf (cmd->pool, &val);
+ if (!attr.alias || strspn (attr.alias, VALID_ALIAS) != strlen (attr.alias))
+ return "Invalid attribute alias";
+
+ /* Check if we already have this alias */
+ for (at = ctx->attributes; at; at = at->next) {
+ if (strcasecmp (at->alias, attr.alias) == 0)
+ return "Duplicate attribute alias";
+ }
+
+ /* Next word is the url */
+ attr.url = ap_getword_conf (cmd->pool, &val);
+ if (!attr.url || !ap_is_url (attr.url))
+ return "Invalid attribute URL";
+
+ /* Now come all the various flags */
+ for (;;) {
+ flag = ap_getword_conf (cmd->pool, &val);
+ if (!flag || !flag[0])
+ break;
+
+ if (strcasecmp (flag, "required") == 0) {
+ attr.required = 1;
+
+ } else if (strcasecmp (flag, "unlimited") == 0) {
+ attr.count = 0;
+
+ } else {
+ attr.count = strtol (flag, &end, 10);
+ if (*end != '\0') {
+ if (attr.count != 0)
+ return "Invalid attribute count";
+ else
+ return "Unrecognized value or flag";
+ }
+ if (attr.count <= 0)
+ return "Attribute count must a number greater than zero or 'unlimited'";
+ }
+ }
+
+ /* Instantiate the copy */
+ attr.next = ctx->attributes;
+ at = apr_pcalloc (cmd->pool, sizeof (attr));
+ memcpy (at, &attr, sizeof (attr));
+ ctx->attributes = at;
+
+ return NULL;
+}
+
static const command_rec command_table[] = {
AP_INIT_TAKE1 ("SingleIdentifier", set_identifier, NULL, OR_AUTHCFG,
"The OpenID identifier we should perform ID selection on when authenticating" ),
@@ -355,6 +442,8 @@ static const command_rec command_table[] = {
"Set the cookie name used once user has logged in via OpenID"),
AP_INIT_RAW_ARGS ("SingleUserMatch", set_user_match, NULL, OR_AUTHCFG,
"How to convert an OpenID identifier into a user name" ),
+ AP_INIT_RAW_ARGS ("SingleAttribute", set_attribute, NULL, OR_AUTHCFG,
+ "Specify an attribute exchange url and alias."),
{ NULL }
};
@@ -399,8 +488,11 @@ session_cookie_value (request_rec *r, const char *name)
if (cookies == NULL)
return NULL;
+ while (cookies[0] == ',' || cookies[0] == ';')
+ ++cookies;
+
while (*cookies) {
- pair = ap_get_token (r->pool, &cookies, 1);
+ pair = safe_get_token (r->pool, &cookies, 1);
if (!pair)
break;
if (pair[0] == '$')
@@ -463,16 +555,16 @@ session_load_info (sid_context_t *ctx, request_rec *r)
if (!value)
return NULL;
- sig = ap_get_token (r->pool, &value, 0);
+ sig = safe_get_token (r->pool, &value, 0);
if (!session_validate_sig (r->pool, sig, value))
return NULL;
/* The version of the session info, only 1 supported for now */
- token = ap_get_token (r->pool, &value, 0);
+ token = safe_get_token (r->pool, &value, 0);
if (strcmp (token, "1") != 0)
return NULL;
- token = ap_get_token (r->pool, &value, 0);
+ token = safe_get_token (r->pool, &value, 0);
expiry = strtol (token, &end, 10);
if (*end != '\0')
return NULL;
@@ -482,7 +574,7 @@ session_load_info (sid_context_t *ctx, request_rec *r)
return NULL;
/* The identifier */
- identifier = ap_get_token (r->pool, &value, 0);
+ identifier = safe_get_token (r->pool, &value, 0);
len = strlen (identifier);
if (identifier[0] == '"' && identifier[len - 1] == '"') {
identifier[len - 1] = 0;
@@ -652,6 +744,31 @@ sid_request_authenticated (sid_request_t *req, const char *identifier)
session_send_info (ctx, req->rec, sess);
}
+void
+sid_request_attribute_values (sid_request_t *req, sid_attribute_t *attr, const char **values)
+{
+ const char **val;
+ char buf[64];
+ char *name;
+ int i = 0;
+
+ /* 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));
+ }
+
+ /* 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));
+}
+
/* ---------------------------------------------------------------------------------------
* MAIN HOOKS
*/
@@ -720,7 +837,8 @@ hook_authenticate (request_rec* r)
req.rec = r;
/* Do the OpenID magic */
- sid_consumer_authenticate (&req, ctx->store, ctx->trust_root, ctx->identifier);
+ sid_consumer_authenticate (&req, ctx->store, ctx->trust_root,
+ ctx->identifier, ctx->attributes);
return req.result;
}
diff --git a/module/mod_auth_singleid.h b/module/mod_auth_singleid.h
index 17e1f22..a81652d 100644
--- a/module/mod_auth_singleid.h
+++ b/module/mod_auth_singleid.h
@@ -8,6 +8,14 @@
extern "C" {
#endif
+typedef struct sid_attribute {
+ const char *url;
+ const char *alias;
+ int required;
+ int count;
+ struct sid_attribute *next;
+} sid_attribute_t;
+
void sid_shared_lock (void);
void sid_shared_unlock (void);
@@ -32,6 +40,10 @@ void sid_request_respond (sid_request_t *req,
void sid_request_authenticated (sid_request_t *req,
const char *identifier);
+void sid_request_attribute_values (sid_request_t *req,
+ sid_attribute_t *attr,
+ const char **values);
+
/* -----------------------------------------------------------------------------------
* STORAGE: Actually, communications white-board between processes/threads.
*/
@@ -75,8 +87,8 @@ void sid_storage_invalidate_assoc (sid_storage_t *storage,
void sid_consumer_authenticate (sid_request_t *req,
sid_storage_t *store,
const char *trust_root,
- const char *identity);
-
+ const char *identity,
+ sid_attribute_t *attributes);
#ifdef __cplusplus
} /* extern "C" */