-rw-r--r-- | include/opkele/basic_op.h | 3 | ||||
-rw-r--r-- | include/opkele/extension.h | 49 | ||||
-rw-r--r-- | include/opkele/extension_chain.h | 6 | ||||
-rw-r--r-- | include/opkele/sreg.h | 31 | ||||
-rw-r--r-- | lib/basic_op.cc | 5 | ||||
-rw-r--r-- | lib/basic_rp.cc | 4 | ||||
-rw-r--r-- | lib/extension.cc | 25 | ||||
-rw-r--r-- | lib/extension_chain.cc | 27 | ||||
-rw-r--r-- | lib/sreg.cc | 28 | ||||
-rw-r--r-- | test/OP.cc | 10 |
10 files changed, 121 insertions, 67 deletions
diff --git a/include/opkele/basic_op.h b/include/opkele/basic_op.h index 7f4e481..5bba1bf 100644 --- a/include/opkele/basic_op.h +++ b/include/opkele/basic_op.h @@ -1,68 +1,69 @@ #ifndef __OPKELE_BASIC_OP_H #define __OPKELE_BASIC_OP_H #include <string> #include <opkele/types.h> #include <opkele/extension.h> namespace opkele { using std::string; class basic_op { public: mode_t mode; assoc_t assoc; bool openid2; string return_to; string realm; string claimed_id; string identity; string invalidate_handle; void reset_vars(); bool has_return_to() const; const string& get_return_to() const; const string& get_realm() const; bool has_identity() const; const string& get_claimed_id() const; const string& get_identity() const; bool is_id_select() const; void select_identity(const string& c,const string& i); void set_claimed_id(const string& c); basic_openid_message& associate( basic_openid_message& oum, const basic_openid_message& inm); void checkid_(const basic_openid_message& inm,extension_t *ext=0); - basic_openid_message& id_res(basic_openid_message& om); + basic_openid_message& id_res(basic_openid_message& om, + extension_t *ext=0); basic_openid_message& cancel(basic_openid_message& om); basic_openid_message& error(basic_openid_message& om, const string& error,const string& contact, const string& reference ); basic_openid_message& setup_needed( basic_openid_message& oum,const basic_openid_message& inm); basic_openid_message& check_authentication( basic_openid_message& oum,const basic_openid_message& inm); virtual void verify_return_to(); virtual assoc_t alloc_assoc(const string& t,size_t kl,bool sl) = 0; virtual assoc_t retrieve_assoc(const string& h) = 0; virtual string& alloc_nonce(string& nonce,bool sl) = 0; virtual bool check_nonce(const string& nonce) = 0; virtual void invalidate_nonce(const string& nonce) = 0; virtual const string get_op_endpoint() const = 0; }; } #endif /* __OPKELE_BASIC_OP_H */ diff --git a/include/opkele/extension.h b/include/opkele/extension.h index 3ee25ee..37bcb90 100644 --- a/include/opkele/extension.h +++ b/include/opkele/extension.h @@ -1,66 +1,61 @@ #ifndef __OPKELE_EXTENSION_H #define __OPKELE_EXTENSION_H /** * @file * @brief extensions framework basics */ +#include <opkele/opkele-config.h> #include <opkele/types.h> namespace opkele { /** * OpenID extension hooks base class */ class extension_t { public: virtual ~extension_t() { } /** - * hook called by consumer before submitting data to OpenID server. - * It is supposed to manipulate parameters list. - * @param p parameters about to be submitted to server - * @param identity identity being verified. It may differ from the - * one available in parameters list in case of delegation - * @see consumer_t::checkid_ - * @see consumer_t::checkid_immediate - * @see consumer_t::checkid_setup + * hook called by RP before submitting the message to OP. + * @param om openid message to be submit */ - virtual void checkid_hook(basic_openid_message& om); + virtual void rp_checkid_hook(basic_openid_message& om); + /** - * hook called by consumer after identity information received from - * OpenID server is verified. - * @param p parameters received from server - * @param sp signed parameters received from server with 'openid.' - * leader stripped - * @param identity identity confirmed. May differ from the one - * available in parameters list in case of delegation. May also be - * empty which means - extract one from parameters - * @see consumer_t::id_res + * hook called by RP after verifying information received from OP. + * @param om openid message received + * @param sp signed part of the message */ - virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); + virtual void rp_id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp); /** - * hook called by server before returning information to consumer. - * The hook may manipulate output parameters. It is important to - * note that modified pout["signed"] is used for signing response. - * @param pin request parameters list with "openid." prefix - * @param pout response parameters list without "openid." prefix - * @see server_t::checkid_ - * @see server_t::checkid_immediate - * @see server_t::checkid_setup + * hook called by OP after parsing incoming message + * @param inm message received from RP + */ + virtual void op_checkid_hook(const basic_openid_message& inm); + /** + * hook called by OP before signing the reply to RP + * @param oum message to be sent to RP */ + virtual void op_id_res_hook(basic_openid_message& oum); + + virtual void checkid_hook(basic_openid_message& om) OPKELE_DEPRECATE; + virtual void id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp) OPKELE_DEPRECATE; virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum); /** * Casts the object to pointer to itself. For convenient passing * of pointer. */ operator extension_t*(void) { return this; } }; } #endif /* __OPKELE_EXTENSION_H */ diff --git a/include/opkele/extension_chain.h b/include/opkele/extension_chain.h index fb9bc84..9692934 100644 --- a/include/opkele/extension_chain.h +++ b/include/opkele/extension_chain.h @@ -1,38 +1,44 @@ #ifndef __OPKELE_EXTENSION_CHAIN_H #define __OPKELE_EXTENSION_CHAIN_H /** * @file * @brief extension chain extension */ #include <list> #include <opkele/extension.h> namespace opkele { using std::list; /** * OpenID extensions chain used to combine extensions, it is actually an * stl list of pointers to extensions. */ class extension_chain_t : public extension_t, public list<extension_t*> { public: /** * Default constructor creates an empty chain */ extension_chain_t() { } /** * Create extension chain with a single extension in it */ extension_chain_t(extension_t *e) { push_back(e); } + virtual void rp_checkid_hook(basic_openid_message& om); + virtual void rp_id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp); + virtual void op_checkid_hook(const basic_openid_message& inm); + virtual void op_id_res_hook(basic_openid_message& oum); + virtual void checkid_hook(basic_openid_message& om); virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum); }; } #endif /* __OPKELE_EXTENSION_CHAIN_H */ diff --git a/include/opkele/sreg.h b/include/opkele/sreg.h index 24cb315..513e221 100644 --- a/include/opkele/sreg.h +++ b/include/opkele/sreg.h @@ -74,130 +74,131 @@ namespace opkele { /** * ASCII string from TimeZone database * * For example, "Europe/Paris" or "America/Los_Angeles". */ field_timezone = 256, /** * All fields bits combined */ fields_ALL = 511, /** * No fields */ fields_NONE = 0 }; /** * Bitmask for fields which, if absent from the response, will * prevent the Consumer from completing the registration without * End User interation. */ long fields_required; /** * Bitmask for fields that will be used by the Consumer, but whose * absence will not prevent the registration from completing. */ long fields_optional; /** * A URL which the Consumer provides to give the End User a place * to read about the how the profile data will be used. The * Identity Provider SHOULD display this URL to the End User if it * is given. */ string policy_url; /** * Bitmask for fields present in response */ long has_fields; /** * Container type for response fields values */ typedef map<fieldbit_t,string> response_t; /** * Response contents */ response_t response; /** * Fields bitmask to send in response */ long fields_response; /** * Consumer constructor. * @param fr required fields * @see fields_required * @param fo optional fields * @see fields_optional * @param pu policy url * @see policy_url */ sreg_t(long fr=fields_NONE,long fo=fields_NONE,const string& pu="") : fields_required(fr), fields_optional(fo), policy_url(pu), has_fields(0) { } - /** - * Implementation of consumer's checkid hook - */ + virtual void rp_checkid_hook(basic_openid_message& om); + virtual void rp_id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp); + virtual void op_checkid_hook(const basic_openid_message& inm); + virtual void op_id_res_hook(basic_openid_message& oum); + virtual void checkid_hook(basic_openid_message& om); - /** - * Implementation of consumer's id_res hook - */ - virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); - /** - * Implementation of server's checkid_hook - */ - virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum); + virtual void id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp); + virtual void checkid_hook(const basic_openid_message& inm, + basic_openid_message& oum); /** * Check and see if we have value for some particular field. * @param fb field in question * @see fieldbit_t * @return true if the value is available */ bool has_field(fieldbit_t fb) const { return has_fields&fb; } /** * Retrieve the value for a field. * @param fb field in question * @see fieldbit_t * @return field value * @throw failed_lookup if no data avaialble */ const string& get_field(fieldbit_t fb) const; /** * Set the value for a field. * @param fb field in question * @see fieldbit_t * @param fv field value */ void set_field(fieldbit_t fb,const string& fv); /** * Remove the value for a field. * @param fb field in question * @see fieldbit_t */ void reset_field(fieldbit_t fb); /** * Reset field data */ void clear(); /** * Function called after parsing sreg request to set up response * fields. The default implementation tries to send as much fields * as we have. The function is supposed to set the data and * fields_response. * @see fields_response - * @param pin input request parameters with "openid." prefix - * @param pout output request parameters without "openid." prefix. - * @see checkid_hook(const params_t&,params_t&) + * @param inm incoming openid message + * @param oum outgoing openid message */ - virtual void setup_response(const basic_openid_message& inm,basic_openid_message& oum); + virtual void setup_response(const basic_openid_message& inm, + basic_openid_message& oum); + + virtual void setup_response(); }; } #endif /* __OPKELE_SREG_H */ diff --git a/lib/basic_op.cc b/lib/basic_op.cc index c89d1d7..9e2ea5a 100644 --- a/lib/basic_op.cc +++ b/lib/basic_op.cc @@ -132,159 +132,162 @@ namespace opkele { } void basic_op::checkid_(const basic_openid_message& inm, extension_t *ext) { reset_vars(); string mode = inm.get_field("mode"); if(mode=="checkid_setup") mode = mode_checkid_setup; else if(mode=="checkid_immediate") mode = mode_checkid_immediate; else throw bad_input(OPKELE_CP_ "Invalid checkid_* mode"); try { assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle")); invalidate_handle.clear(); }catch(failed_lookup&) { // no handle specified or no valid assoc found, go dumb assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true); } try { openid2 = (inm.get_field("ns")==OIURI_OPENID20); }catch(failed_lookup&) { openid2 = false; } try { return_to = inm.get_field("return_to"); }catch(failed_lookup&) { } if(openid2) { try { realm = inm.get_field("realm"); }catch(failed_lookup&) { try { realm = inm.get_field("trust_root"); }catch(failed_lookup&) { if(return_to.empty()) throw bad_input(OPKELE_CP_ "Both realm and return_to are unset"); realm = return_to; } } }else{ try { realm = inm.get_field("trust_root"); }catch(failed_lookup&) { if(return_to.empty()) throw bad_input(OPKELE_CP_ "Both realm and return_to are unset"); realm = return_to; } } try { identity = inm.get_field("identity"); try { claimed_id = inm.get_field("claimed_id"); }catch(failed_lookup&) { if(openid2) throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); claimed_id = identity; } }catch(failed_lookup&) { if(openid2 && inm.has_field("claimed_id")) throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); } verify_return_to(); + if(ext) ext->op_checkid_hook(inm); } - basic_openid_message& basic_op::id_res(basic_openid_message& om) { + basic_openid_message& basic_op::id_res(basic_openid_message& om, + extension_t *ext) { assert(assoc); assert(!return_to.empty()); assert(!is_id_select()); time_t now = time(0); struct tm gmt; gmtime_r(&now,&gmt); char w3timestr[24]; if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt)) throw failed_conversion(OPKELE_CP_ "Failed to build time string for nonce" ); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","id_res"); om.set_field("op_endpoint",get_op_endpoint()); string ats = "ns,mode,op_endpoint,return_to,response_nonce," "assoc_handle,signed"; if(!identity.empty()) { om.set_field("identity",identity); om.set_field("claimed_id",claimed_id); ats += ",identity,claimed_id"; } om.set_field("return_to",return_to); string nonce = w3timestr; om.set_field("response_nonce",alloc_nonce(nonce,assoc->stateless())); if(!invalidate_handle.empty()) { om.set_field("invalidate_handle",invalidate_handle); ats += ",invalidate_handle"; } om.set_field("assoc_handle",assoc->handle()); om.add_to_signed(ats); + if(ext) ext->op_id_res_hook(om); om.set_field("sig",util::base64_signature(assoc,om)); return om; } basic_openid_message& basic_op::cancel(basic_openid_message& om) { assert(!return_to.empty()); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","cancel"); return om; } basic_openid_message& basic_op::error(basic_openid_message& om, const string& error,const string& contact, const string& reference ) { assert(!return_to.empty()); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","error"); om.set_field("error",error); om.set_field("contact",contact); om.set_field("reference",reference); return om; } basic_openid_message& basic_op::setup_needed( basic_openid_message& oum,const basic_openid_message& inm) { assert(mode==mode_checkid_immediate); assert(!return_to.empty()); if(openid2) { oum.set_field("ns",OIURI_OPENID20); oum.set_field("mode","setup_needed"); }else{ oum.set_field("mode","id_res"); static const string setupmode = "checkid_setup"; oum.set_field("user_setup_url", util::change_mode_message_proxy(inm,setupmode) .append_query(get_op_endpoint())); } return oum; } basic_openid_message& basic_op::check_authentication( basic_openid_message& oum, const basic_openid_message& inm) try { assert(inm.get_field("mode")=="check_authentication"); oum.reset_fields(); oum.set_field("ns",OIURI_OPENID20); bool o2; try { o2 = (inm.get_field("ns")==OIURI_OPENID20); }catch(failed_lookup&) { o2 = false; } string nonce; if(o2) { try { if(!check_nonce(nonce = inm.get_field("response_nonce"))) throw failed_check_authentication(OPKELE_CP_ "Invalid nonce"); }catch(failed_lookup&) { throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request"); } } try { assoc = retrieve_assoc(inm.get_field("assoc_handle")); if(!assoc->stateless()) throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle"); }catch(failed_lookup&) { diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc index a884583..bd45d99 100644 --- a/lib/basic_rp.cc +++ b/lib/basic_rp.cc @@ -68,129 +68,129 @@ namespace opkele { req.set_field("dh_gen",util::bignum_to_base64(dh->g)); req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key)); openid_message_t res; req.set_field("assoc_type","HMAC-SHA256"); req.set_field("session_type","DH-SHA256"); secret_t secret; int expires_in; try { direct_request(res,req,OP); dh_get_secret( secret, res, "HMAC-SHA256", "DH-SHA256", dh, SHA256_DIGEST_LENGTH, SHA256, SHA256_DIGEST_LENGTH ); expires_in = util::string_to_long(res.get_field("expires_in")); }catch(exception& e) { try { req.set_field("assoc_type","HMAC-SHA1"); req.set_field("session_type","DH-SHA1"); direct_request(res,req,OP); dh_get_secret( secret, res, "HMAC-SHA1", "DH-SHA1", dh, SHA_DIGEST_LENGTH, SHA1, SHA_DIGEST_LENGTH ); expires_in = util::string_to_long(res.get_field("expires_in")); }catch(bad_input& e) { throw dumb_RP(OPKELE_CP_ "OP failed to supply an association"); } } return store_assoc( OP, res.get_field("assoc_handle"), res.get_field("assoc_type"), secret, expires_in ); } basic_openid_message& basic_RP::checkid_( basic_openid_message& rv, mode_t mode, const string& return_to,const string& realm, extension_t *ext) { rv.reset_fields(); rv.set_field("ns",OIURI_OPENID20); if(mode==mode_checkid_immediate) rv.set_field("mode","checkid_immediate"); else if(mode==mode_checkid_setup) rv.set_field("mode","checkid_setup"); else throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); if(realm.empty() && return_to.empty()) throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty"); if(!realm.empty()) { rv.set_field("realm",realm); rv.set_field("trust_root",realm); } if(!return_to.empty()) rv.set_field("return_to",return_to); const openid_endpoint_t& ep = get_endpoint(); rv.set_field("claimed_id",ep.claimed_id); rv.set_field("identity",ep.local_id); try { rv.set_field("assoc_handle",find_assoc(ep.uri)->handle()); }catch(dumb_RP& drp) { }catch(failed_lookup& fl) { try { rv.set_field("assoc_handle",associate(ep.uri)->handle()); }catch(dumb_RP& drp) { } } OPKELE_RETHROW - if(ext) ext->checkid_hook(rv); + if(ext) ext->rp_checkid_hook(rv); return rv; } class signed_part_message_proxy : public basic_openid_message { public: const basic_openid_message& x; set<string> signeds; signed_part_message_proxy(const basic_openid_message& xx) : x(xx) { const string& slist = x.get_field("signed"); string::size_type p = 0; while(true) { string::size_type co = slist.find(',',p); string f = (co==string::npos) ?slist.substr(p):slist.substr(p,co-p); signeds.insert(f); if(co==string::npos) break; p = co+1; } } bool has_field(const string& n) const { return signeds.find(n)!=signeds.end() && x.has_field(n); } const string& get_field(const string& n) const { if(signeds.find(n)==signeds.end()) throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); return x.get_field(n); } fields_iterator fields_begin() const { return signeds.begin(); } fields_iterator fields_end() const { return signeds.end(); } }; static void parse_query(const string& u,string::size_type q, map<string,string>& p) { if(q==string::npos) return; assert(u[q]=='?'); ++q; string::size_type l = u.size(); while(q<l) { string::size_type eq = u.find('=',q); string::size_type am = u.find('&',q); if(am==string::npos) { if(eq==string::npos) { p[""] = u.substr(q); }else{ p[u.substr(q,eq-q)] = u.substr(eq+1); } break; }else{ if(eq==string::npos || eq>am) { p[""] = u.substr(q,eq-q); }else{ p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1); } q = ++am; } } } void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) { bool o2 = om.has_field("ns") @@ -214,84 +214,84 @@ namespace opkele { throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); }catch(dumb_RP& drp) { go_dumb=true; }catch(failed_lookup& e) { go_dumb=true; } OPKELE_RETHROW if(go_dumb) { try { string OP = o2 ?om.get_field("op_endpoint") :get_endpoint().uri; check_authentication(OP,om); }catch(failed_check_authentication& fca) { throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); } OPKELE_RETHROW } signed_part_message_proxy signeds(om); if(o2) { check_nonce(om.get_field("op_endpoint"), om.get_field("response_nonce")); static const char *mustsign[] = { "op_endpoint", "return_to", "response_nonce", "assoc_handle", "claimed_id", "identity" }; for(int ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) { if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms])) throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs"); } if( ( (om.has_field("claimed_id")?1:0) ^ (om.has_field("identity")?1:0) )&1 ) throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); string turl = util::rfc_3986_normalize_uri(get_this_url()); util::strip_uri_fragment_part(turl); string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to")); util::strip_uri_fragment_part(rurl); string::size_type tq = turl.find('?'), rq = rurl.find('?'); if( ((tq==string::npos)?turl:turl.substr(0,tq)) != ((rq==string::npos)?rurl:rurl.substr(0,rq)) ) throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url"); map<string,string> tp; parse_query(turl,tq,tp); map<string,string> rp; parse_query(rurl,rq,rp); for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) { map<string,string>::const_iterator tpi = tp.find(rpi->first); if(tpi==tp.end()) throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request"); if(tpi->second!=rpi->second) throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request"); } if(om.has_field("claimed_id")) { verify_OP( om.get_field("op_endpoint"), om.get_field("claimed_id"), om.get_field("identity") ); } } - if(ext) ext->id_res_hook(om,signeds); + if(ext) ext->rp_id_res_hook(om,signeds); } void basic_RP::check_authentication(const string& OP, const basic_openid_message& om){ openid_message_t res; static const string checkauthmode = "check_authentication"; direct_request(res,util::change_mode_message_proxy(om,checkauthmode),OP); if(res.has_field("is_valid")) { if(res.get_field("is_valid")=="true") { if(res.has_field("invalidate_handle")) invalidate_assoc(OP,res.get_field("invalidate_handle")); return; } } throw failed_check_authentication( OPKELE_CP_ "failed to verify response"); } } diff --git a/lib/extension.cc b/lib/extension.cc index 6451249..f7aaea5 100644 --- a/lib/extension.cc +++ b/lib/extension.cc @@ -1,15 +1,26 @@ #include <opkele/exception.h> #include <opkele/extension.h> namespace opkele { + void extension_t::rp_checkid_hook(basic_openid_message&) { + throw not_implemented(OPKELE_CP_ "RP checkid_* hook not implemented"); } + void extension_t::rp_id_res_hook(const basic_openid_message&, + const basic_openid_message&) { + throw not_implemented(OPKELE_CP_ "RP id_res hook not implemented"); } + + void extension_t::op_checkid_hook(const basic_openid_message&) { + throw not_implemented(OPKELE_CP_ "OP checkid_* hook not implemented"); } + void extension_t::op_id_res_hook(basic_openid_message& om) { + throw not_implemented(OPKELE_CP_ "OP id_res hook not implemented"); } + + void extension_t::checkid_hook(basic_openid_message&) { - throw not_implemented(OPKELE_CP_ "Consumer checkid_hook not implemented"); - } - void extension_t::id_res_hook(const basic_openid_message&,const basic_openid_message&) { - throw not_implemented(OPKELE_CP_ "Consumer id_res_hook not implemented"); - } + throw not_implemented(OPKELE_CP_ "deprecated consumer checkid_* hook not implemented"); } + void extension_t::id_res_hook(const basic_openid_message&, + const basic_openid_message&) { + throw not_implemented(OPKELE_CP_ "deprecated consumer id_res hook not implemented"); } + void extension_t::checkid_hook(const basic_openid_message&,basic_openid_message&) { - throw not_implemented(OPKELE_CP_ "Server checkid_hook not implemented"); - } + throw not_implemented(OPKELE_CP_ "deprecated server checkid hook not implemented"); } } diff --git a/lib/extension_chain.cc b/lib/extension_chain.cc index 5c2afd9..5483740 100644 --- a/lib/extension_chain.cc +++ b/lib/extension_chain.cc @@ -1,16 +1,27 @@ #include <cstdarg> #include <opkele/extension_chain.h> namespace opkele { + void extension_chain_t::rp_checkid_hook(basic_openid_message& om) { + for(iterator i=begin();i!=end();++i) (*i)->rp_checkid_hook(om); } + void extension_chain_t::rp_id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp) { + for(iterator i=begin();i!=end();++i) (*i)->rp_id_res_hook(om,sp); } + + void extension_chain_t::op_checkid_hook(const basic_openid_message& inm) { + for(iterator i=begin();i!=end();++i) (*i)->op_checkid_hook(inm); } + void extension_chain_t::op_id_res_hook(basic_openid_message& oum) { + for(iterator i=begin();i!=end();++i) (*i)->op_id_res_hook(oum); } + + void extension_chain_t::checkid_hook(basic_openid_message& om){ - for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om); - } - void extension_chain_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { - for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp); - } - void extension_chain_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { - for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum); - } + for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om); } + void extension_chain_t::id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp) { + for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp); } + void extension_chain_t::checkid_hook(const basic_openid_message& inm, + basic_openid_message& oum) { + for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum); } } diff --git a/lib/sreg.cc b/lib/sreg.cc index 7e2d588..b40cd45 100644 --- a/lib/sreg.cc +++ b/lib/sreg.cc @@ -1,140 +1,160 @@ #include <opkele/exception.h> #include <opkele/sreg.h> #include <opkele/uris.h> #include <algorithm> namespace opkele { using std::find; static const struct _sreg_field { const char *fieldname; sreg_t::fieldbit_t fieldbit; } fields[] = { { "nickname", sreg_t::field_nickname }, { "email", sreg_t::field_email }, { "fullname", sreg_t::field_fullname }, { "dob", sreg_t::field_dob }, { "gender", sreg_t::field_gender }, { "postcode", sreg_t::field_postcode }, { "country", sreg_t::field_country }, { "language", sreg_t::field_language }, { "timezone", sreg_t::field_timezone } }; # define fields_BEGIN fields # define fields_END &fields[sizeof(fields)/sizeof(*fields)] typedef const struct _sreg_field *fields_iterator; bool operator==(const struct _sreg_field& fd,const string& fn) { return fd.fieldname==fn; } - void sreg_t::checkid_hook(basic_openid_message& om) { + void sreg_t::rp_checkid_hook(basic_openid_message& om) { string fr, fo; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { if(f->fieldbit&fields_required) { if(!fr.empty()) fr+=","; fr += f->fieldname; } if(f->fieldbit&fields_optional) { if(!fo.empty()) fo+=","; fo += f->fieldname; } } string pfx = om.allocate_ns(OIURI_SREG11,"sreg"); if(!fr.empty()) om.set_field(pfx+".required",fr); if(!fo.empty()) om.set_field(pfx+".optional",fo); if(!policy_url.empty()) om.set_field(pfx+".policy_url",policy_url); } - void sreg_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { + void sreg_t::checkid_hook(basic_openid_message& om) { + rp_checkid_hook(om); } + + void sreg_t::rp_id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp) { clear(); string pfx; try { pfx = om.find_ns(OIURI_SREG11,"sreg"); }catch(failed_lookup& fl) { try { pfx = om.find_ns(OIURI_SREG10,"sreg"); }catch(failed_lookup& fl) { return; } } pfx += '.'; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { string fn = pfx; fn+=f->fieldname; if(!sp.has_field(fn)) continue; has_fields |= f->fieldbit; response[f->fieldbit]=sp.get_field(fn); } } + void sreg_t::id_res_hook(const basic_openid_message& om, + const basic_openid_message& sp) { + rp_id_res_hook(om,sp); } + const string& sreg_t::get_field(fieldbit_t fb) const { response_t::const_iterator i = response.find(fb); if(i==response.end()) throw failed_lookup(OPKELE_CP_ "no field data available"); return i->second; } void sreg_t::set_field(fieldbit_t fb,const string& fv) { response[fb] = fv; has_fields |= fb; } void sreg_t::reset_field(fieldbit_t fb) { has_fields &= ~fb; response.erase(fb); } void sreg_t::clear() { has_fields = 0; response.clear(); } static long fields_list_to_bitmask(string& fl) { long rv = 0; while(!fl.empty()) { string::size_type co = fl.find(','); string fn; if(co==string::npos) { fn = fl; fl.erase(); }else{ fn = fl.substr(0,co); fl.erase(0,co+1); } fields_iterator f = find(fields_BEGIN,fields_END,fn); if(f!=fields_END) rv |= f->fieldbit; } return rv; } - void sreg_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { + void sreg_t::op_checkid_hook(const basic_openid_message& inm) { string ins = inm.find_ns(OIURI_SREG11,"sreg"); fields_optional = 0; fields_required = 0; policy_url.erase(); fields_response = 0; try { string fl = inm.get_field(ins+".required"); fields_required = fields_list_to_bitmask(fl); }catch(failed_lookup&) { } try { string fl = inm.get_field(ins+".optional"); fields_optional = fields_list_to_bitmask(fl); }catch(failed_lookup&) { } try { policy_url = inm.get_field(ins+".policy_url"); }catch(failed_lookup&) { } - setup_response(inm,oum); + } + + void sreg_t::op_id_res_hook(basic_openid_message& oum) { string ons = oum.allocate_ns(OIURI_SREG11,"sreg"); fields_response &= has_fields; string signeds = "ns."+ons; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { if(!(f->fieldbit&fields_response)) continue; signeds +=','; string pn = ons; pn += '.'; pn += f->fieldname; signeds += pn; oum.set_field(pn,get_field(f->fieldbit)); } oum.add_to_signed(signeds); } + void sreg_t::checkid_hook(const basic_openid_message& inm, + basic_openid_message& oum) { + op_checkid_hook(inm); + setup_response(inm,oum); + op_id_res_hook(oum); + } + void sreg_t::setup_response(const basic_openid_message& /* inm */,basic_openid_message& /* oum */) { + setup_response(); + } + void sreg_t::setup_response() { fields_response = (fields_required|fields_optional)&has_fields; } } @@ -1,80 +1,81 @@ #include <uuid/uuid.h> #include <iostream> #include <cassert> #include <string> #include <ext/algorithm> using namespace std; #include <kingate/exception.h> #include <kingate/plaincgi.h> #include <kingate/cgi_gateway.h> #include <opkele/exception.h> #include <opkele/util.h> #include <opkele/uris.h> #include <opkele/extension.h> #include <opkele/association.h> #include <opkele/debug.h> #include <opkele/verify_op.h> +#include <opkele/sreg.h> #include "sqlite.h" #include "kingate_openid_message.h" static const string get_self_url(const kingate::cgi_gateway& gw) { bool s = gw.has_meta("SSL_PROTOCOL_VERSION"); string rv = s?"https://":"http://"; rv += gw.http_request_header("Host"); const string& port = gw.get_meta("SERVER_PORT"); if( port!=(s?"443":"80") ) { rv += ':'; rv += port; } rv += gw.get_meta("REQUEST_URI"); string::size_type q = rv.find('?'); if(q!=string::npos) rv.erase(q); return rv; } class opdb_t : public sqlite3_t { public: opdb_t() : sqlite3_t("/tmp/OP.db") { assert(_D); char **resp; int nr,nc; char *errm; if(sqlite3_get_table( _D, "SELECT a_op FROM assoc LIMIT 0", &resp,&nr,&nc,&errm)!=SQLITE_OK) { extern const char *__OP_db_bootstrap; DOUT_("Bootstrapping DB"); if(sqlite3_exec(_D,__OP_db_bootstrap,NULL,NULL,&errm)!=SQLITE_OK) throw opkele::exception(OPKELE_CP_ string("Failed to boostrap SQLite database: ")+errm); }else sqlite3_free_table(resp); } }; class example_op_t : public opkele::verify_op { public: kingate::cgi_gateway& gw; opdb_t db; kingate::cookie htc; example_op_t(kingate::cgi_gateway& gw) : gw(gw) { try { htc = gw.cookies.get_cookie("htop_session"); sqlite3_mem_t<char*> S = sqlite3_mprintf( "SELECT 1 FROM ht_sessions WHERE hts_id=%Q", htc.get_value().c_str()); sqlite3_table_t T; int nr,nc; db.get_table(S,T,&nr,&nc); if(nr<1) throw kingate::exception_notfound(CODEPOINT,"forcing cookie generation"); }catch(kingate::exception_notfound& kenf) { uuid_t uuid; uuid_generate(uuid); htc = kingate::cookie("htop_session",opkele::util::encode_base64(uuid,sizeof(uuid))); sqlite3_mem_t<char*> S = sqlite3_mprintf( "INSERT INTO ht_sessions (hts_id) VALUES (%Q)", htc.get_value().c_str()); db.exec(S); } } @@ -208,140 +209,145 @@ int main(int argc,char *argv[]) { string message; if(op=="set_password") { example_op_t OP(gw); string password = gw.get_param("password"); sqlite3_mem_t<char*> Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1"); sqlite3_table_t T; int nr,nc; OP.db.get_table(Sget,T,&nr,&nc); if(nr>=1) throw opkele::exception(OPKELE_CP_ "Password already set"); sqlite3_mem_t<char*> Sset = sqlite3_mprintf( "INSERT INTO setup (s_password) VALUES (%Q)", password.c_str()); OP.db.exec(Sset); op.clear(); message = "password set"; }else if(op=="login") { example_op_t OP(gw); string password = gw.get_param("password"); sqlite3_mem_t<char*> Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1"); sqlite3_table_t T; int nr,nc; OP.db.get_table(Sget,T,&nr,&nc); if(nr<1) throw opkele::exception(OPKELE_CP_ "no password set"); if(password!=T.get(1,0,nc)) throw opkele::exception(OPKELE_CP_ "wrong password"); OP.set_authorized(true); op.clear(); message = "logged in"; OP.cookie_header(cout); }else if(op=="logout") { example_op_t OP(gw); OP.set_authorized(false); op.clear(); message = "logged out"; } string om; try { om = gw.get_param("openid.mode"); }catch(kingate::exception_notfound&) { } if(op=="xrds") { cout << "Content-type: application/xrds+xml\n\n" "<?xml version='1.0' encoding='utf-8'?>" "<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns='xri://$xrd*($v*2.0)'>" "<XRD>" "<Service>" "<Type>" STURI_OPENID20 "</Type>" "<URI>" << get_self_url(gw) << "</URI>" "</Service>"; if(gw.has_param("idsel")){ cout << "<Service>" "<Type>" STURI_OPENID20_OP "</Type>" "<URI>" << get_self_url(gw) << "</URI>"; } cout << "</XRD>" "</xrds:XRDS>"; }else if(op=="id_res" || op=="cancel") { kingate_openid_message_t inm(gw); example_op_t OP(gw); if(gw.get_param("hts_id")!=OP.htc.get_value()) throw opkele::exception(OPKELE_CP_ "toying around, huh?"); - OP.checkid_(inm,0); + opkele::sreg_t sreg; + OP.checkid_(inm,sreg); OP.cookie_header(cout); opkele::openid_message_t om; if(op=="id_res") { if(!OP.get_authorized()) throw opkele::exception(OPKELE_CP_ "not logged in"); if(OP.is_id_select()) { OP.select_identity( get_self_url(gw), get_self_url(gw) ); } + sreg.set_field(opkele::sreg_t::field_nickname,"anonymous"); + sreg.set_field(opkele::sreg_t::field_fullname,"Ann O'Nymus"); + sreg.set_field(opkele::sreg_t::field_gender,"F"); + sreg.setup_response(); cout << "Status: 302 Going back to RP with id_res\n" - "Location: " << OP.id_res(om).append_query(OP.get_return_to()) + "Location: " << OP.id_res(om,sreg).append_query(OP.get_return_to()) << "\n\n"; }else{ cout << "Status: 302 Going back to RP with cancel\n" "Location: " << OP.cancel(om).append_query(OP.get_return_to()) << "\n\n"; } om.to_keyvalues(clog); }else if(om=="associate") { kingate_openid_message_t inm(gw); opkele::openid_message_t oum; example_op_t OP(gw); OP.associate(oum,inm); cout << "Content-type: text/plain\n\n"; oum.to_keyvalues(cout); }else if(om=="checkid_setup") { kingate_openid_message_t inm(gw); example_op_t OP(gw); OP.checkid_(inm,0); OP.cookie_header(cout) << "Content-type: text/html\n" "\n" "<html>" "<head>" "<title>test OP: confirm authentication</title>" "</head>" "<body>" "realm: " << OP.get_realm() << "<br/>" "return_to: " << OP.get_return_to() << "<br/>" "claimed_id: " << OP.get_claimed_id() << "<br/>" "identity: " << OP.get_identity() << "<br/>"; if(OP.is_id_select()) { OP.select_identity( get_self_url(gw), get_self_url(gw) ); cout << "selected claimed_id: " << OP.get_claimed_id() << "<br/>" "selected identity: " << OP.get_identity() << "<br/>"; } cout << "<form method='post'>"; inm.to_htmlhiddens(cout); cout << "<input type='hidden' name='hts_id'" " value='" << opkele::util::attr_escape(OP.htc.get_value()) << "'/>" "<input type='submit' name='op' value='id_res'/>" "<input type='submit' name='op' value='cancel'/>" "</form>" "</body>" "</html>"; }else if(om=="check_authentication") { kingate_openid_message_t inm(gw); example_op_t OP(gw); opkele::openid_message_t oum; OP.check_authentication(oum,inm); cout << "Content-type: text/plain\n\n"; oum.to_keyvalues(cout); oum.to_keyvalues(clog); }else{ example_op_t OP(gw); string idsel; if(gw.has_param("idsel")) idsel = "&idsel=idsel"; OP.cookie_header(cout) << "Content-type: text/html\n" |