Back to index

nordugrid-arc-nox  1.1.0~rc6
aaservice.cpp
Go to the documentation of this file.
00001 #ifdef HAVE_CONFIG_H
00002 #include <config.h>
00003 #endif
00004 
00005 #include <iostream>
00006 
00007 #include <sys/types.h>
00008 #include <pwd.h>
00009 
00010 #include <arc/message/PayloadSOAP.h>
00011 #include <arc/message/SOAPEnvelope.h>
00012 #include <arc/DateTime.h>
00013 #include <arc/GUID.h>
00014 #include <arc/StringConv.h>
00015 #include <arc/credential/Credential.h>
00016 #include <arc/xmlsec/XmlSecUtils.h>
00017 #include <arc/xmlsec/XMLSecNode.h>
00018 #include <arc/MysqlWrapper.h>
00019 
00020 #include "aaservice.h"
00021 
00022 namespace ArcSec {
00023 
00024 #define SAML_NAMESPACE "urn:oasis:names:tc:SAML:2.0:assertion"
00025 #define SAMLP_NAMESPACE "urn:oasis:names:tc:SAML:2.0:protocol"
00026 
00027 #define XENC_NAMESPACE   "http://www.w3.org/2001/04/xmlenc#"
00028 #define DSIG_NAMESPACE   "http://www.w3.org/2000/09/xmldsig#"
00029 
00030 static Arc::Plugin* get_service(Arc::PluginArgument* arg) {
00031     Arc::ServicePluginArgument* servarg = arg?dynamic_cast<Arc::ServicePluginArgument*>(arg):NULL;
00032     return new Service_AA((Arc::Config*)(*servarg));
00033 }
00034 
00035 Arc::MCC_Status Service_AA::make_soap_fault(Arc::Message& outmsg, const std::string& reason) {
00036   Arc::PayloadSOAP* outpayload = new Arc::PayloadSOAP(ns_,true);
00037   Arc::SOAPFault* fault = outpayload?outpayload->Fault():NULL;
00038   if(fault) {
00039     fault->Code(Arc::SOAPFault::Sender);
00040     std::string str = "Failed processing request";
00041     str = str + reason;
00042     fault->Reason(str);
00043   };
00044   outmsg.Payload(outpayload);
00045   return Arc::MCC_Status(Arc::STATUS_OK);
00046 }
00047 
00048 static std::string get_cert_str(const std::string& cert) {
00049   std::size_t pos = cert.find("BEGIN CERTIFICATE");
00050   if(pos != std::string::npos) {
00051     std::size_t pos1 = cert.find_first_of("---", pos);
00052     std::size_t pos2 = cert.find_first_not_of("-", pos1);
00053     std::size_t pos3 = cert.find_first_of("---", pos2);
00054     std::string str = cert.substr(pos2+1, pos3-pos2-2);
00055     return str;
00056   }
00057   return ("");
00058 }
00059 
00060 Arc::MCC_Status Service_AA::process(Arc::Message& inmsg,Arc::Message& outmsg) {
00061 
00062   //The DN of peer certificate, which is the authentication result during tls;
00063   //It could also be set by using the authentication result in message level
00064   //authentication, such as saml token profile in WS-Security.
00065   //Note: since the peer DN is obtained from transport level authentication, the request 
00066   //(with the peer certificate) is exactly the principal inside the <saml:NameID>, which means
00067   //the request is unlike ServiceProvider in WebSSO scenario which gets the <AuthnQuery>
00068   //result from IdP, and uses the principal (which is not the principal of ServiceProvider) 
00069   //in <AuthnQuery> result to initiate a <AttributeQuery> to AA.
00070   //Hence the scenario is "SAML Attribute Self-Query Deployment Profile for X.509 Subjects" inside 
00071   //document "SAML V2.0 Deployment Profiles for X.509 Subjects"
00072 
00073   std::string peer_rdn = inmsg.Attributes()->get("TLS:PEERDN");
00074   std::string peer_dn = Arc::convert_to_rdn(peer_rdn);
00075 
00076   // Extracting payload
00077   Arc::PayloadSOAP* inpayload = NULL;
00078   try {
00079     inpayload = dynamic_cast<Arc::PayloadSOAP*>(inmsg.Payload());
00080   } catch(std::exception& e) { };
00081   if(!inpayload) {
00082     logger_.msg(Arc::ERROR, "input is not SOAP");
00083     return make_soap_fault(outmsg, "input is not SOAP");
00084   }
00085 
00086   Arc::XMLNode attrqry;
00087   attrqry = (*inpayload).Body().Child(0);
00088 
00089   std::string query_idname = "ID";
00090 
00091 #if 0
00092   Arc::XMLSecNode attrqry_secnode(attrqry);
00093   if(attrqry_secnode.VerifyNode(query_idname, cafile_, cadir_)) {
00094     logger_.msg(Arc::INFO, "Succeeded to verify the signature under <AttributeQuery/>");
00095   }
00096   else {     
00097     logger_.msg(Arc::ERROR, "Failed to verify the signature under <AttributeQuery/>");
00098     return Arc::MCC_Status();
00099   }
00100 #endif
00101 
00102   Arc::NS ns;
00103   ns["saml"] = SAML_NAMESPACE;
00104   ns["samlp"] = SAMLP_NAMESPACE;
00105   ns["xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
00106   ns["xsd"] = "http://www.w3.org/2001/XMLSchema";
00107 
00108   //Reset the namespaces of <AttributeQuery/> in case the prefix is not the same as "saml" and "samlp"
00109   attrqry.Namespaces(ns);
00110 
00111   Arc::XMLNode issuer = attrqry["saml:Issuer"];
00112 
00113   //Compare the <saml:NameID> inside the <AttributeQuery> message with the peer DN
00114   //which has been got from the former authentication
00115   //More complicated processing should be considered, according to 3.3.4 in SAML core specification
00116   Arc::XMLNode subject = attrqry["saml:Subject"];
00117   std::string name_id = (std::string)(subject["saml:NameID"]);
00118   if(name_id == peer_dn) {
00119     logger_.msg(Arc::INFO, "The NameID inside request is the same as the NameID from the tls authentication: %s", peer_dn.c_str());
00120   }
00121   else {
00122     std::string retstr = "The NameID inside request is: " + name_id + 
00123               "; not the same as the NameID from the tls authentication: " + peer_dn;
00124     logger_.msg(Arc::INFO, retstr);
00125     return make_soap_fault(outmsg, retstr);
00126   }
00127 
00128   //Get the <Attribute>s from <AttributeQuery> message, which is required by request; 
00129   //AA will only return those <Attribute> which is required by request
00130   std::vector<Arc::XMLNode> attributes;
00131   for(int i=0;; i++) {
00132     Arc::XMLNode cn =  attrqry["saml:Attribute"][i];
00133     if(!cn) break;
00134     attributes.push_back(cn);
00135   }
00136 
00137   //AA: Generate a response 
00138   //AA will try to query local attribute database, intersect the attribute result from
00139   //database and the attribute requirement from the request
00140   //Then, insert those <Attribute> into response
00141 
00142   //Access the attribute database, use the <NameID> and <Attribute> as searching key
00143   //The code itself here does not care the database schema, except that the code 
00144   //supposes the following aspects about database schema:
00145   //1. There is a map between user's DN and the user's ID in database schema;
00146   //2. The user's ID is the "must" searching key in database schema;
00147   //3. There are two "optional" searching keys in database schema: one for
00148   //     "role", and the other for "group"
00149   //4. There are 1, 2, or 4 colums in the serching result.
00150 
00151   //Firstly, get the user's ID from database 
00152   std::vector<std::string> userid;
00153   std::string udn = peer_rdn;
00154   std::vector<std::string> sqlargs;
00155   sqlargs.push_back(udn);
00156   std::string query_type("UID");
00157   get_results(userid, sqlargs, query_type, dbconf_);
00158   
00159   //Secondly, get the attributes/roles
00160   std::map<std::string, std::vector<std::string> > attribute_res;
00161 
00162   std::map<std::string, std::string> nameattrs;
00163   for(int i=0; i<attributes.size(); i++) {
00164     Arc::XMLNode nd = attributes[i];
00165     std::string attr_name = (std::string)(nd.Attribute("Name"));
00166     if(!attr_name.empty()) {
00167       std::string nameform = (std::string)(nd.Attribute("NameFormat"));
00168       std::string friendname = (std::string)(nd.Attribute("FriendlyName"));
00169       std::string nameattr;
00170       nameattr.append(nameform).append(friendname.empty()?"":"&&").append(friendname);
00171       nameattrs[attr_name] = nameattr;
00172     }
00173     else {
00174       std::string retstr = "There should be Name attribute in request's <Attribute> node";
00175       logger_.msg(Arc::INFO, retstr);
00176       return make_soap_fault(outmsg, retstr);
00177     }
00178     std::string query_type;
00179 /*
00180     if((attr_name!="Role") && (attr_name!="Group") && (attr_name!="GroupAndRole") &&
00181        (attr_name!="All") && (attr_name!="GroupAndRoleAttribute") && 
00182        (attr_name!="GroupAttribute") && (attr_name!="RoleAttribute") && (attr_name!="AllAttribute"))
00183       query_type = "All";
00184     else query_type=attr_name;
00185 */
00186     for(int i = 0;; i++) {
00187       Arc::XMLNode cn = nd["aa:SQLSet"][i];
00188       if(!cn) break;
00189       //If the SQLSet's name matches to the name in the query
00190       if(((std::string)(cn.Attribute("name"))) == attr_name) { 
00191         query_type=attr_name; break; 
00192       }
00193     }
00194     if(query_type.empty()) query_type="Default";
00195 
00196     std::vector<std::string> fqans;
00197     if(userid.size()==0) {
00198       std::string retstr = "Can not find the user record for DN: " + peer_dn; 
00199       logger_.msg(Arc::INFO, retstr);
00200       return make_soap_fault(outmsg, retstr);
00201     }
00202 
00203     std::string uid = userid[0];
00204     std::string role = (std::string)(nd["saml:AttributeValue"][0]);
00205     std::string group = (std::string)(nd["saml:AttributeValue"][1]);
00206     std::vector<std::string> sqlargs;
00207     //sequence of arguments mattes; and it should be corresponding to
00208     //the arguments squence in sql sentence
00209     sqlargs.push_back(uid);
00210     sqlargs.push_back(role);
00211     sqlargs.push_back(group);
00212 
00213     get_results(fqans, sqlargs, query_type, dbconf_);
00214 
00215     if(fqans.size()!=0) {
00216       std::cout<<"Got db query result"<<std::endl;
00217       for(int i=0; i<fqans.size(); i++)
00218         std::cout<<fqans[i]<<std::endl;
00219     }
00220     else {
00221       std::cout<<"Did not get db query result"<<std::endl;
00222       fqans.push_back("test_saml");
00223     }
00224     
00225     attribute_res[attr_name] = fqans;
00226   }
00227 
00228   //TODO: Compare the attribute name from database result and the attribute name,
00229   //Only use the intersect as the response
00230  
00231 
00232   Arc::NS samlp_ns, saml_ns;
00233   samlp_ns["samlp"] = SAMLP_NAMESPACE;
00234   saml_ns["saml"] = SAML_NAMESPACE;
00235   saml_ns["xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
00236  
00237   //Compose <saml:Response/>
00238   Arc::XMLNode attr_response(samlp_ns, "samlp:Response");
00239 
00240   Arc::Credential cred(certfile_, keyfile_, cadir_, cafile_);
00241   std::string local_dn = cred.GetDN();
00242   std::string aa_name = Arc::convert_to_rdn(local_dn);
00243   attr_response.NewChild("samlp:Issuer") = aa_name;
00244 
00245   std::string response_id = Arc::UUID();
00246   attr_response.NewAttribute("ID") = response_id;
00247   std::string responseto_id = (std::string)(attrqry.Attribute("ID"));
00248   attr_response.NewAttribute("InResponseTo") = responseto_id;
00249   Arc::Time t;
00250   std::string current_time = t.str(Arc::UTCTime);
00251   attr_response.NewAttribute("IssueInstant") = current_time;
00252   attr_response.NewAttribute("Version") = std::string("2.0");
00253  
00254   //<samlp:Status/> 
00255   Arc::XMLNode status = attr_response.NewChild("samlp:Status");
00256   Arc::XMLNode statuscode = status.NewChild("samlp:StatusCode");
00257   std::string statuscode_value = "urn:oasis:names:tc:SAML:2.0:status:Success";
00258   statuscode.NewAttribute("Value") = statuscode_value;
00259 
00260   //<saml:Assertion/>
00261   Arc::XMLNode assertion = attr_response.NewChild("saml:Assertion", saml_ns);
00262 
00263 
00264   assertion.NewAttribute("Version") = std::string("2.0");
00265   std::string assertion_id = Arc::UUID();
00266   assertion.NewAttribute("ID") = assertion_id;
00267   Arc::Time t1;
00268   std::string current_time1 = t1.str(Arc::UTCTime);
00269   assertion.NewAttribute("IssueInstant") = current_time1;
00270 
00271   //<saml:Issuer/>
00272   assertion.NewChild("saml:Issuer") = aa_name;
00273 
00274   //<saml:Subject/>
00275   //<saml:Subject/> is the same as the one in request
00276   Arc::XMLNode subj = assertion.NewChild("saml:Subject");
00277   Arc::XMLNode nmid = subj.NewChild("saml:NameID");
00278   nmid.NewAttribute("Format") = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName";
00279   nmid = peer_dn;
00280   Arc::XMLNode subject_confirmation = subj.NewChild("saml:SubjectConfirmation");
00281   subject_confirmation.NewAttribute("Method")=std::string("urn:oasis:names:tc:SAML:2.0:cm:holder-of-key");
00282   Arc::XMLNode subject_confirmation_data = subject_confirmation.NewChild("saml:SubjectConfirmationData");
00283   Arc::NS ds_ns("ds",DSIG_NAMESPACE);
00284   Arc::XMLNode key_info = subject_confirmation_data.NewChild("ds:KeyInfo",ds_ns);
00285   Arc::XMLNode x509_data = key_info.NewChild("ds:X509Data");
00286   Arc::XMLNode x509_cert = x509_data.NewChild("ds:X509Certificate");
00287   std::string x509_str = get_cert_str(inmsg.Attributes()->get("TLS:PEERCERT"));
00288   x509_cert = x509_str;
00289 
00290  
00291   //<saml:Conditions>
00292   Arc::XMLNode conditions = assertion.NewChild("saml:Conditions"); 
00293   //Arc::XMLNode audience_restriction = conditions.NewChild("saml:AudienceRestriction");
00294   //std::string client_name("https://sp.com/SAML"); //TODO
00295   //audience_restriction.NewChild("saml:Audience") = client_name; 
00296   Arc::Time t_start;
00297   std::string time_start = t_start.str(Arc::UTCTime);
00298   Arc::Time t_end = t_start + Arc::Period(43200);
00299   std::string time_end = t_end.str(Arc::UTCTime);
00300   conditions.NewAttribute("NotBefore") = time_start;  
00301   conditions.NewAttribute("NotOnOrAfter") = time_end;              
00302 
00303   //<saml:AttributeStatement/> 
00304   Arc::XMLNode attr_statement = assertion.NewChild("saml:AttributeStatement");
00305 
00306   //<saml:Attribute/>
00307   //Compose <Attribute> according to the database searching result
00308 
00309   std::map<std::string, std::vector<std::string> >::iterator it;
00310   for(it = attribute_res.begin(); it != attribute_res.end(); it++) {
00311     Arc::XMLNode attribute = attr_statement.NewChild("saml:Attribute");
00312     attribute.NewAttribute("Name") = (*it).first;
00313     std::string name_attr = nameattrs[(*it).first];
00314     if(!name_attr.empty()) {
00315       std::size_t pos = name_attr.find("&&");
00316       if(pos != std::string::npos) {
00317         attribute.NewAttribute("NameFormat")= name_attr.substr(0, pos);
00318         attribute.NewAttribute("FriendlyName")= name_attr.substr(pos+2);
00319       }
00320       else
00321         attribute.NewAttribute("NameFormat")= name_attr;
00322     }
00323     for(int k = 0; k< (*it).second.size(); k++) {
00324       Arc::XMLNode attr_value = attribute.NewChild("saml:AttributeValue");
00325       attr_value.NewAttribute("xsi:type") = std::string("xs:string");
00326       attr_value = (*it).second[k];
00327     }
00328   }
00329 
00330 
00331   Arc::XMLSecNode assertion_secnd(assertion);
00332   std::string assertion_idname("ID");
00333   std::string inclusive_namespaces = "saml ds xsi";
00334   assertion_secnd.AddSignatureTemplate(assertion_idname, Arc::XMLSecNode::RSA_SHA1, inclusive_namespaces);
00335   if(assertion_secnd.SignNode(keyfile_, certfile_)) {
00336     std::cout<<"Succeed to sign the signature under <saml:Assertion/>"<<std::endl;
00337     //std::string str;
00338     //assertion_secnd.GetXML(str);
00339     //std::cout<<"Signed node: "<<std::endl<<str<<std::endl;
00340   }
00341 
00342   Arc::XMLSecNode attr_response_secnd(attr_response);
00343   std::string attr_response_idname("ID");
00344   attr_response_secnd.AddSignatureTemplate(attr_response_idname, Arc::XMLSecNode::RSA_SHA1);
00345   if(attr_response_secnd.SignNode(keyfile_, certfile_)) {
00346     std::cout<<"Succeed to sign the signature under <samlp:Response/>"<<std::endl;
00347     //std::string str;
00348     //attr_response_secnd.GetXML(str);
00349     //std::cout<<"Signed node: "<<std::endl<<str<<std::endl;
00350   }
00351 
00352   //Put the <samlp:Response/> into soap body.
00353   Arc::NS soap_ns;
00354   Arc::SOAPEnvelope envelope(soap_ns);
00355   envelope.NewChild(attr_response);
00356   Arc::PayloadSOAP *outpayload = new Arc::PayloadSOAP(envelope);
00357 
00358   std::string tmp;
00359   outpayload->GetXML(tmp);
00360   std::cout<<"Output payload: "<<tmp<<std::endl;
00361 
00362   outmsg.Payload(outpayload);
00363   return Arc::MCC_Status(Arc::STATUS_OK);
00364 }
00365 
00366 Service_AA::Service_AA(Arc::Config *cfg):Service(cfg), logger_(Arc::Logger::rootLogger, "AA_Service") {
00367   keyfile_ = (std::string)((*cfg)["KeyPath"]);
00368   certfile_ = (std::string)((*cfg)["CertificatePath"]);
00369   cafile_ = (std::string)((*cfg)["CACertificatePath"]);
00370   cadir_ = (std::string)((*cfg)["CACertificatesDir"]);
00371   dbconf_ = (*cfg)["Database"];
00372   Arc::init_xmlsec();
00373 }
00374 
00375 Service_AA::~Service_AA(void) {
00376   Arc::final_xmlsec();
00377 }
00378 
00379 bool Service_AA::get_results(std::vector<std::string>& fqans, const std::vector<std::string>& sqlargs, 
00380       const std::string& idofsqlset, Arc::XMLNode& config) {
00381   Arc::QueryArrayResult attributes;
00382   std::vector<std::string> args;
00383   for(int j = 0; j< sqlargs.size(); j++) {
00384     std::string item;
00385     item.append("\"").append(sqlargs[j]).append("\"");
00386     args.push_back(item);
00387   }
00388   bool res;
00389   res = query_db(attributes, idofsqlset, args, config);
00390   if(!res) return res;
00391   for(int i = 0; i< attributes.size(); i++) {
00392     std::vector<std::string> item = attributes[i];
00393     int num = item.size();
00394     std::string fqan;
00395 
00396     if(num == 1) { // example:  UID
00397       fqan = item[0];
00398     }
00399     else if(num == 2) { // example:  /Group=knowarc/Role=physicist
00400       fqan = item[0].empty()? "":("/Group=" + item[0].substr(1)) + (item[1].empty() ? "":("/Role=" + item[1]));
00401     }
00402     else if(num == 4) { // example:  /Group=knowarc/Role=physicist:Degree=PhD
00403       std::string str = (item[2].empty()? "":("/Group=" + item[2].substr(1))) + (item[3].empty() ? "":("/Role=" + item[3]));
00404       fqan = str + (str.empty()?"":":") + item[0] + "=" + item[1];
00405     }
00406     fqans.push_back(fqan);
00407   }
00408   return true;
00409 }
00410 
00411 bool Service_AA::query_db(Arc::QueryArrayResult& attributes, const std::string& idofsqlset, std::vector<std::string>& sqlargs, Arc::XMLNode& config) {
00412   Arc::XMLNode nd;
00413   nd = config;
00414   std::string server, dbname, user, password, portstr;
00415   int port;
00416   server = (std::string)(nd.Attribute("ip"));
00417   dbname = (std::string)(nd.Attribute("dbname"));
00418   user = (std::string)(nd.Attribute("user"));
00419   password = (std::string)(nd.Attribute("password"));
00420   portstr = (std::string)(nd.Attribute("port"));
00421   port = atoi((portstr.c_str()));
00422 
00423   logger_.msg(Arc::VERBOSE, "Access database %s from server %s port %s, with user %s and password %s",
00424               dbname.c_str(), server.c_str(), portstr.c_str(), user.c_str(), password.c_str()); 
00425 
00426   //TODO: make the database and sql object dynamic loaded 
00427   //according to the "name" (mysql, oracle, etc.)
00428   Arc::MySQLDatabase mydb(server, port);
00429   bool res = false;
00430   res = mydb.connect(dbname,user,password);
00431   if(res == false) {
00432     logger_.msg(Arc::ERROR,"Can't establish connection to mysql database"); return false;
00433   }
00434 
00435   Arc::MySQLQuery myquery(&mydb);
00436     logger_.msg(Arc::VERBOSE, "Is connected to database? %s", mydb.isconnected()? "yes":"no");
00437 
00438   std::string querystr;
00439   for(int i = 0;; i++) {
00440     Arc::XMLNode cn = nd["aa:SQLSet"][i];
00441     if(!cn) break;
00442     if(((std::string)(cn.Attribute("name"))) == idofsqlset) {
00443       for(int k = 0;; k++) {
00444         Arc::XMLNode scn = cn["aa:SQL"][k];
00445         if(!scn) break;
00446         querystr = (std::string)scn;
00447         logger_.msg(Arc::VERBOSE, "Query: %s", querystr.c_str());
00448         myquery.get_array(querystr, attributes, sqlargs);
00449       }
00450     }
00451   }
00452   logger_.msg(Arc::VERBOSE, "Get result array with %d rows",attributes.size());
00453   return true;
00454 }
00455 
00456 } // namespace ArcSec
00457 
00458 Arc::PluginDescriptor PLUGINS_TABLE_NAME[] = {
00459     { "aa.service", "HED:SERVICE", 0, &ArcSec::get_service },
00460     { NULL, NULL, 0, NULL }
00461 };