#include "mod_auth_singleid.h" #include #include #include #include #include using opkele::assoc_t; using opkele::association; using opkele::bad_input; using opkele::dumb_RP; using opkele::exception; using opkele::failed_discovery; using opkele::failed_lookup; using opkele::failed_xri_resolution; using opkele::id_res_bad_nonce; using opkele::no_endpoint; using opkele::openid_endpoint_t; using opkele::openid_message_t; using opkele::params_t; using opkele::prequeue_RP; using opkele::secret_t; using std::string; using std::vector; class LockShared { public: LockShared() { sid_shared_lock (); } ~LockShared() { sid_shared_unlock (); } }; class Consumer : public prequeue_RP { private: // types typedef vector endpoints; public: // interface Consumer(const string& url, sid_storage_t *store) : _store(store), _url(url), _index(0) { } public: // overrides virtual void begin_queueing() { _endpoints.clear(); _index = 0; } virtual void queue_endpoint(const openid_endpoint_t& oep) { _endpoints.push_back(oep); } virtual const openid_endpoint_t& get_endpoint() const; virtual void next_endpoint() { _index++; } virtual void set_normalized_id(const string& nid) { _normalized = nid; } virtual const string get_normalized_id() const { return _normalized; } virtual const string get_this_url() const { return _url; } virtual assoc_t store_assoc(const string& server, const string& handle, const string& type, const secret_t& secret, int expires_in); virtual assoc_t find_assoc(const string& server); virtual assoc_t retrieve_assoc(const string& server, const string& handle); virtual void invalidate_assoc(const string& server, const string& handle); virtual void check_nonce(const string& server, const string& nonce); private: // data sid_storage_t *_store; endpoints _endpoints; string _normalized; string _url; endpoints::size_type _index; }; const openid_endpoint_t& Consumer::get_endpoint() const { if (_index >= _endpoints.size()) throw no_endpoint("no more endpoints"); return _endpoints[_index]; } assoc_t Consumer::store_assoc(const string& server, const string& handle, const string& type, const secret_t& secret, int expires_in) { sid_assoc_t data; int res; if (!_store) throw dumb_RP("no storage initialized"); data.server = server.c_str(); data.handle = handle.c_str(); data.type = type.c_str(); data.secret = secret.data(); data.n_secret = secret.size(); data.expires = expires_in; { LockShared lock; /* scoped lock */ res = sid_storage_store_assoc (_store, &data); } if (!res) throw dumb_RP("association data was too large to fit in shared storage"); return assoc_t(new association(server, handle, type, secret, expires_in, false)); } assoc_t Consumer::find_assoc(const string& server) { sid_assoc_t data = { 0, }; association *assoc = NULL; if (!_store) throw dumb_RP("no storage initialized"); { LockShared lock; if (sid_storage_find_assoc (_store, server.c_str(), NULL, &data)) { secret_t secret; secret.assign(data.secret, data.secret + data.n_secret); assoc = new association(data.server, data.handle, data.type, secret, data.expires, false); } } if (!assoc) throw failed_lookup("could not find association for server: " + server); return assoc_t(assoc); } assoc_t Consumer::retrieve_assoc(const string& server, const string& handle) { sid_assoc_t data = { 0, }; association *assoc = NULL; if (!_store) throw dumb_RP("no storage initialized"); { LockShared lock; if (sid_storage_find_assoc (_store, server.c_str(), handle.c_str(), &data)) { secret_t secret; secret.assign(data.secret, data.secret + data.n_secret); assoc = new association(data.server, data.handle, data.type, secret, data.expires, false); } } if (!assoc) throw failed_lookup("could not retrieve association for server: " + server); return assoc_t(assoc); } void Consumer::invalidate_assoc(const string& server, const string& handle) { if (!_store) throw dumb_RP("no storage initialized"); LockShared lock; sid_storage_invalidate_assoc (_store, server.c_str(), handle.c_str()); } void Consumer::check_nonce(const string& server, const string& nonce) { int res = 0; if (_store) { LockShared lock; res = sid_storage_check_nonce (_store, server.c_str(), nonce.c_str()); } if (res == 1) throw id_res_bad_nonce ("nonce is too old, or has already been used"); } /* ----------------------------------------------------------------------- * AUTHENTICATION */ static void filter_openid_params (params_t ¶ms, params_t &openid) { 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; /* 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 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 begin_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms, const string& trust_root, const string &identity) { params_t result; string redirect; try { openid_message_t cm; consumer.initiate (identity); consumer.checkid_ (cm, opkele::mode_checkid_setup, consumer.get_this_url(), trust_root); redirect = cm.append_query (consumer.get_endpoint().uri); } catch (failed_xri_resolution &ex) { sid_request_respond (req, 503, "Invalid Identifier", NULL); sid_request_log_error (req, "failed xri resolution while while discovering identity provider", ex.what ()); return; } catch (failed_discovery &ex) { sid_request_respond (req, 503, "Invalid Identifier", NULL); sid_request_log_error (req, "failed discovery while while discovering identity provider", ex.what ()); return; } catch (bad_input &ex) { sid_request_respond (req, 503, "Invalid Identifier", NULL); sid_request_log_error (req, "bad input while while discovering identity provider", ex.what()); return; } catch (exception &ex) { sid_request_respond (req, 500, NULL, NULL); sid_request_log_error (req, "error while while discovering identity provider", ex.what()); return; } sid_request_respond (req, 307, "Moved Temporarily", "Location", redirect.c_str(), "Cache-Control", "no-cache", NULL); } static void complete_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms) { try { consumer.id_res(params); string identity = consumer.get_claimed_id(); sid_request_authenticated (req, identity.c_str()); } catch (exception &ex) { sid_request_respond (req, 500, NULL, NULL); sid_request_log_error (req, "error while completing authentication", ex.what()); } } static void cancelled_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms) { sid_request_respond (req, 401, "Authentication Required", NULL); } void sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store, const char *trust_root, const char *identity) { params_t params; params_t openid; assert (req); const char *qs = sid_request_qs (req); parse_query_string (qs, params); filter_openid_params (params, openid); string url = sid_request_url (req, 1); if (!params.empty()) url = params.append_query (url, ""); Consumer consumer(url, store); /* Returning (hopefully successful) authentication */ if (openid.has_param("assoc_handle")) { complete_auth (req, consumer, openid); /* Returning cancelled authentication */ } else if (openid.has_param("mode") && openid.get_param("mode") == "cancel") { cancelled_auth (req, consumer, openid); /* Begin a new authentication */ } else { if (!trust_root) trust_root = sid_request_url (req, 0); begin_auth (req, consumer, params, trust_root, identity); } }