summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--include/opkele/basic_op.h3
-rw-r--r--include/opkele/extension.h49
-rw-r--r--include/opkele/extension_chain.h6
-rw-r--r--include/opkele/sreg.h31
-rw-r--r--lib/basic_op.cc5
-rw-r--r--lib/basic_rp.cc4
-rw-r--r--lib/extension.cc25
-rw-r--r--lib/extension_chain.cc27
-rw-r--r--lib/sreg.cc28
-rw-r--r--test/OP.cc10
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;
}
}
diff --git a/test/OP.cc b/test/OP.cc
index 1196c0c..c919d7f 100644
--- a/test/OP.cc
+++ b/test/OP.cc
@@ -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"