From 2989ee8b72ddb3995e5a4686c988385d05493365 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Tue, 7 Jul 2009 20:05:29 +0000 Subject: Implement simple AX attribute exchange. * Does not yet handle setting attributes from the cookie. --- module/consumer.cc | 255 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 219 insertions(+), 36 deletions(-) (limited to 'module/consumer.cc') 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 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 ¶ms) +{ + 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 ¶ms, 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 ¶ms, 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 ¶ms, 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 ¶ms) +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 ¶ms, - 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 ¶ms, 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 ¶ms, } static void -complete_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms) +complete_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms, + 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 ¶ms) 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); } } -- cgit v1.2.3