Back to index

nordugrid-arc-nox  1.1.0~rc6
ClientSAML2SSO.cpp
Go to the documentation of this file.
00001 // -*- indent-tabs-mode: nil -*-
00002 
00003 #ifdef HAVE_CONFIG_H
00004 #include <config.h>
00005 #endif
00006 
00007 // This define is needed to have maximal values for types with fixed size
00008 #define __STDC_LIMIT_MACROS
00009 #include <stdlib.h>
00010 #include <map>
00011 
00012 #include <arc/StringConv.h>
00013 #include <arc/URL.h>
00014 #include <arc/message/PayloadRaw.h>
00015 #include <arc/message/MCC.h>
00016 #include <arc/xmlsec/XmlSecUtils.h>
00017 #include <arc/xmlsec/XMLSecNode.h>
00018 #include <arc/xmlsec/saml_util.h>
00019 
00020 #include "ClientSAML2SSO.h"
00021 
00022 namespace Arc {
00023   Logger ClientHTTPwithSAML2SSO::logger(Logger::getRootLogger(), "ClientHTTPwithSAML2SSO");
00024   Logger ClientSOAPwithSAML2SSO::logger(Logger::getRootLogger(), "ClientSOAPwithSAML2SSO");
00025 
00026   ClientHTTPwithSAML2SSO::ClientHTTPwithSAML2SSO(const BaseConfig& cfg,
00027                                                  const URL& url)
00028     : http_client_(NULL),
00029       authn_(false) {
00030 
00031     http_client_ = new ClientHTTP(cfg, url, 60);
00032     //Use the credential and trusted certificates from client's main chain to
00033     //contact IdP
00034     cert_file_ = cfg.cert;
00035     privkey_file_ = cfg.key;
00036     ca_file_ = cfg.cafile;
00037     ca_dir_ = cfg.cadir;
00038   }
00039 
00040   ClientHTTPwithSAML2SSO::~ClientHTTPwithSAML2SSO() {
00041     if (http_client_)
00042       delete http_client_;
00043   }
00044 
00045   static MCC_Status process_saml2sso(const std::string& idp_name, const std::string& username,
00046                                      const std::string& password, ClientHTTP *http_client, std::string& cert_file,
00047                                      std::string& privkey_file, std::string& ca_file, std::string& ca_dir, Logger& logger,
00048                                      std::string& cookie) {
00049 
00050     // -------------------------------------------
00051     // User-Agent: 1. Send an empty http request to SP;
00052     //
00053     // The saml2sso process share the same tcp/tls connection (on the service 
00054     // side, SP service and the functional/real service which is to be protected 
00055     // are supposed to be at the same service chain) with the
00056     // main client chain. And because of this connection sharing, if the
00057     // saml2sso process (interaction between user-agent/SP service/extenal IdP
00058     // service, see SAML2 SSO profile) is succeeded, we can suppose that the
00059     // later real client/real service interaction is authorized.
00060     //
00061     // 2. User-Agent then get back <AuthnRequest/>, and send it response to IdP
00062     //
00063     // SP Service: a service based on http service on the service side, which is
00064     // specifically in charge of the funtionality of Service Provider of SAML2
00065     // SSO profile.
00066     //
00067     // User-Agent: Since the SAML2 SSO profile is web-browser based, so here we
00068     // implement the code which is with the same functionality as browser's user agent.
00069     // -------------------------------------------
00070     //
00071 
00072     Arc::PayloadRaw requestSP;
00073     Arc::PayloadRawInterface *responseSP = NULL;
00074     Arc::HTTPClientInfo infoSP;
00075     requestSP.Insert(idp_name.c_str(), 0, idp_name.size());
00076     //Call the peer http endpoint with path "saml2sp", which
00077     //is the endpoint of saml SP service
00078     Arc::MCC_Status statusSP = http_client->process("POST", "saml2sp", &requestSP, &infoSP, &responseSP);
00079     if (!responseSP) {
00080       logger.msg(Arc::ERROR, "Request failed: No response from SPService");
00081       return MCC_Status();
00082     }
00083     if (!statusSP) {
00084       logger.msg(Arc::ERROR, "Request failed: response from SPService is not as expected");
00085       if (responseSP)
00086         delete responseSP;
00087       return MCC_Status();
00088     }
00089 
00090     //Parse the authenRequestUrl from response
00091     std::string authnRequestUrl(responseSP->Content());
00092     logger.msg(DEBUG, "Authentication Request URL: %s", authnRequestUrl);
00093 
00094     if (responseSP)
00095       delete responseSP;
00096 
00097     // -------------------------------------------
00098     //User-Agent: Send the AuthnRequest to IdP,
00099     //(IP-based authentication, and Username/Password authentication)
00100     //get back b64 encoded saml response, and
00101     //send this saml response to SP
00102     //-------------------------------------------
00103 
00104     Arc::URL url(authnRequestUrl);
00105 
00106     Arc::MCCConfig cfg;
00107     if (!cert_file.empty())
00108       cfg.AddCertificate(cert_file);
00109     if (!privkey_file.empty())
00110       cfg.AddPrivateKey(privkey_file);
00111     if (!ca_file.empty())
00112       cfg.AddCAFile(ca_file);
00113     if (!ca_dir.empty())
00114       cfg.AddCADir(ca_dir);
00115 
00116     ClientHTTP clientIdP(cfg, url);
00117 
00118     Arc::PayloadRaw requestIdP;
00119     Arc::PayloadRawInterface *responseIdP = NULL;
00120     Arc::HTTPClientInfo infoIdP;
00121 
00122     std::multimap<std::string, std::string> http_attributes;
00123     if (!(cookie.empty()))
00124       http_attributes.insert(std::pair<std::string,std::string>("Cookie",cookie));
00125 
00126     //Contact the IdP service
00127     Arc::MCC_Status statusIdP = clientIdP.process("GET", http_attributes, &requestIdP, &infoIdP, &responseIdP);
00128 
00129     if ((!(infoIdP.cookies.empty())) && infoIdP.code != 200)
00130       cookie = *(infoIdP.cookies.begin());
00131 
00132     if (!responseIdP) {
00133       logger.msg(Arc::ERROR, "Request failed: No response from IdP");
00134       return MCC_Status();
00135     }
00136     if (!statusIdP) {
00137       logger.msg(Arc::ERROR, "Request failed: response from SPService is not as expected");
00138       if (responseIdP)
00139         delete responseIdP;
00140       return MCC_Status();
00141     }
00142     //Record the returned html content in case something is wrong
00143     std::string html_content;
00144     if(responseIdP->Content() != NULL)
00145       html_content = responseIdP->Content();
00146     if (responseIdP)
00147       delete responseIdP;
00148 
00149     //The following code is for authentication (username/password)
00150     std::string resp_html;
00151     Arc::HTTPClientInfo redirect_info = infoIdP;
00152 
00153     if(redirect_info.code!= 200) {
00154       int count = 0;
00155       do {
00156         /*
00157         std::cout<<"Code: "<<redirect_info.code<<"  Reason: "<<redirect_info.reason<<"  Size: "<<
00158            redirect_info.size<<"  Type: "<<redirect_info.type<<"  Set-Cookie: "<<(redirect_info.cookies.empty()?"":(*(redirect_info.cookies.begin())))<< "  Location: "<<redirect_info.location<<std::endl;
00159         */
00160 
00161         //"break" if the response is not "redirection"
00162         if (redirect_info.code != 302)
00163           break;
00164 
00165         Arc::URL redirect_url(redirect_info.location);
00166         Arc::ClientHTTP redirect_client(cfg, redirect_url);
00167 
00168         Arc::PayloadRaw redirect_request;
00169         Arc::PayloadRawInterface *redirect_response = NULL;
00170 
00171         std::multimap<std::string, std::string> http_attributes;
00172 
00173         if (!(cookie.empty()))
00174           http_attributes.insert(std::pair<std::string,std::string>("Cookie",cookie));
00175 
00176         //Keep contacting IdP
00177         Arc::MCC_Status redirect_status = redirect_client.process("GET", http_attributes,
00178                                                                 &redirect_request, &redirect_info, &redirect_response);
00179 
00180         if (!(redirect_info.cookies.empty()))
00181           cookie = *(redirect_info.cookies.begin());
00182       
00183         if (!redirect_response) {
00184           logger.msg(Arc::ERROR, "Request failed: No response from IdP when doing redirecting");
00185           return MCC_Status();
00186         }
00187         if (!redirect_status) {
00188           logger.msg(Arc::ERROR, "Request failed: response from IdP is not as expected when doing redirecting");
00189           if (redirect_response)
00190             delete redirect_response;
00191           return MCC_Status();
00192         }
00193         char *content = redirect_response->Content();
00194         if (content != NULL) {
00195           resp_html.assign(redirect_response->Content());
00196           size_t pos = resp_html.find("j_username");
00197           if (pos != std::string::npos) {
00198             if (redirect_response)
00199               delete redirect_response;
00200             break;  
00201             //"break" if the "j_username" is found in response, 
00202             //here for different implentation of IdP, different 
00203             //name could be searched.
00204           }
00205         }
00206         if (redirect_response)
00207           delete redirect_response;
00208         count++;
00209         if (count > 5)
00210           break;             //At most loop 5 times
00211       } while (1);
00212 
00213 
00214 
00215       //Arc::URL redirect_url_final("https://idp.testshib.org:443/idp/Authn/UserPassword");
00216       Arc::URL redirect_url_final(infoIdP.location);
00217       Arc::ClientHTTP redirect_client_final(cfg, redirect_url_final);
00218       Arc::PayloadRaw redirect_request_final;
00219 
00220       //std::string login_html("j_username=myself&j_password=myself");
00221 
00222       std::multimap<std::string, std::string> http_attributes2;
00223 
00224       std::string login_html;
00225       login_html.append("j_username=").append(username).append("&j_password=").append(password);
00226 
00227       redirect_request_final.Insert(login_html.c_str(), 0, login_html.size());
00228 
00229       http_attributes2.insert(std::pair<std::string,std::string>("Content-Type","application/x-www-form-urlencoded"));
00230 
00231       if(!(cookie.empty()))
00232         http_attributes2.insert(std::pair<std::string,std::string>("Cookie",cookie));
00233 
00234       Arc::PayloadRawInterface *redirect_response_final = NULL;
00235       Arc::HTTPClientInfo redirect_info_final;
00236   
00237       //Contact IdP to send the username/password
00238       Arc::MCC_Status redirect_status_final = redirect_client_final.process("POST", http_attributes2,
00239                                       &redirect_request_final, &redirect_info_final, &redirect_response_final);
00240 
00241       if (!(redirect_info_final.cookies.empty()))
00242         cookie = *(redirect_info_final.cookies.begin());
00243 
00244       if (!redirect_response_final) {
00245         logger.msg(Arc::ERROR, "Request failed: No response from IdP when doing authentication");
00246         return MCC_Status();
00247       }
00248       if (!redirect_status_final) {
00249         logger.msg(Arc::ERROR, "Request failed: response from IdP is not as expected when doing authentication");
00250         if (redirect_response_final)
00251           delete redirect_response_final;
00252         return MCC_Status();
00253       }
00254 
00255       std::string html_body;
00256       for (int i = 0;; i++) {
00257       char *buf = redirect_response_final->Buffer(i);
00258         if (buf == NULL)
00259           break;
00260         html_body.append(redirect_response_final->Buffer(i), redirect_response_final->BufferSize(i));
00261         //std::cout<<"Buffer: "<<buf<<std::endl;
00262       }
00263       if (redirect_response_final)
00264         delete redirect_response_final;
00265 
00266       std::string type = redirect_info_final.type;
00267       size_t pos1 = type.find(';');
00268       if (pos1 != std::string::npos)
00269         type = type.substr(0, pos1);
00270       if (type != "text/html")
00271         std::cerr << "The html response from IdP is with wrong format" << std::endl;
00272 
00273 
00274       Arc::XMLNode html_node(html_body);
00275       //Get the samlp:Response from responded html message.
00276       //here for different implementation of IdP, the responded html 
00277       //messages could have different structures.
00278       std::string saml_resp = html_node["body"]["form"]["div"]["input"].Attribute("value");
00279       //std::cout<<"SAML Response: "<<saml_resp<<std::endl;
00280       Arc::XMLNode samlresp_nd;
00281       //Decode the saml response (b64 format)
00282       Arc::BuildNodefromMsg(saml_resp, samlresp_nd);
00283       std::string decoded_saml_resp;
00284       samlresp_nd.GetXML(decoded_saml_resp);
00285       //std::cout<<"Decoded SAML Response: "<<decoded_saml_resp<<std::endl;
00286 
00287       //Verify the signature of saml response
00288       std::string idname = "ID";
00289       Arc::XMLSecNode sec_samlresp_nd(samlresp_nd);
00290       //Since the certificate from idp.testshib.org which signs the saml response is self-signed
00291       //certificate, only check the signature here, while do not check the whole chain of 
00292       //certificates
00293       if (sec_samlresp_nd.VerifyNode(idname, "", "", false))
00294         logger.msg(Arc::INFO, "Succeeded to verify the signature under <samlp:Response/>");
00295       else
00296         logger.msg(Arc::ERROR, "Failed to verify the signature under <samlp:Response/>");
00297 
00298 
00299       //Send the encrypted saml assertion to service side through this main message chain
00300       //Get the encrypted saml assertion in this saml response
00301       Arc::XMLNode assertion_nd = samlresp_nd["saml:EncryptedAssertion"];
00302       std::string saml_assertion;
00303       assertion_nd.GetXML(saml_assertion);
00304       //std::cout<<"Encrypted saml assertion: "<<saml_assertion<<std::endl;
00305       requestSP.Truncate(0);
00306       requestSP.Insert(saml_assertion.c_str(), 0, saml_assertion.size());
00307       statusSP = http_client->process("POST", "saml2sp", &requestSP, &infoSP, &responseSP);
00308 
00309       if (!responseSP) {
00310         logger.msg(Arc::ERROR, "Request failed: No response from SP Service when sending saml assertion to SP");
00311         return MCC_Status();
00312       }
00313       if (!statusSP) {
00314         logger.msg(Arc::ERROR, "Request failed: response from SP Service is not as expected when sending saml assertion to SP");
00315         if (responseSP)
00316           delete responseSP;
00317         return MCC_Status();
00318       }
00319       if (responseSP)
00320         delete responseSP;
00321 
00322     } else { 
00323       // if "redirect_info.code == 200", then something could be wrong,
00324       // because "200" is not supposed to appear in this step
00325       logger.msg(Arc::ERROR,"IdP return some error message: %s", html_content.c_str());
00326       return MCC_Status();
00327     }
00328 
00329     return Arc::MCC_Status(Arc::STATUS_OK);
00330   }
00331 
00332   MCC_Status ClientHTTPwithSAML2SSO::process(const std::string& method,
00333                                              PayloadRawInterface *request,
00334                                              HTTPClientInfo *info,
00335                                              PayloadRawInterface **response, const std::string& idp_name,
00336                                              const std::string& username, const std::string& password, 
00337                                              const bool reuse_authn) {
00338     return (process(method, "", request, info, response, idp_name, username, password, reuse_authn));
00339   }
00340 
00341   MCC_Status ClientHTTPwithSAML2SSO::process(const std::string& method,
00342                                              const std::string& path,
00343                                              PayloadRawInterface *request,
00344                                              HTTPClientInfo *info,
00345                                              PayloadRawInterface **response, const std::string& idp_name,
00346                                              const std::string& username, const std::string& password, 
00347                                              const bool reuse_authn) {
00348     if (!authn_) { //If has not yet passed the saml2sso process
00349       //Do the saml2sso
00350       Arc::MCC_Status status = process_saml2sso(idp_name, username, password,
00351                                                 http_client_, cert_file_, privkey_file_, ca_file_, ca_dir_, logger, cookie);
00352       if (!status) {
00353         logger.msg(Arc::ERROR, "SAML2SSO process failed");
00354         return MCC_Status();
00355       }
00356       if(reuse_authn) authn_ = true; //Reuse or not reuse the result from saml2sso
00357     }
00358     //Send the real message
00359     Arc::MCC_Status status = http_client_->process(method, path, request, info, response);
00360     return status;
00361   }
00362 
00363   ClientSOAPwithSAML2SSO::ClientSOAPwithSAML2SSO(const BaseConfig& cfg,
00364                                                  const URL& url)
00365     : soap_client_(NULL),
00366       authn_(false) {
00367     soap_client_ = new ClientSOAP(cfg, url);
00368     //Use the credential and trusted certificates from client's main chain to
00369     //contact IdP
00370     cert_file_ = cfg.cert;
00371     privkey_file_ = cfg.key;
00372     ca_file_ = cfg.cafile;
00373     ca_dir_ = cfg.cadir;
00374   }
00375 
00376   ClientSOAPwithSAML2SSO::~ClientSOAPwithSAML2SSO() {
00377     if (soap_client_)
00378       delete soap_client_;
00379   }
00380 
00381   MCC_Status ClientSOAPwithSAML2SSO::process(PayloadSOAP *request, PayloadSOAP **response,
00382                                              const std::string& idp_name, const std::string& username, 
00383                                              const std::string& password, const bool reuse_authn) {
00384     return process("", request, response, idp_name, username, password, reuse_authn);
00385   }
00386 
00387   MCC_Status ClientSOAPwithSAML2SSO::process(const std::string& action, PayloadSOAP *request,
00388                                              PayloadSOAP **response, const std::string& idp_name,
00389                                              const std::string& username, const std::string& password, 
00390                                              const bool reuse_authn) {
00391     //Do the saml2sso
00392     if (!authn_) { //If has not yet passed the saml2sso process
00393       ClientHTTP *http_client = dynamic_cast<ClientHTTP*>(soap_client_);
00394       Arc::MCC_Status status = process_saml2sso(idp_name, username, password,
00395                                                 http_client, cert_file_, privkey_file_, 
00396                                                 ca_file_, ca_dir_, logger, cookie);
00397       if (!status) {
00398         logger.msg(Arc::ERROR, "SAML2SSO process failed");
00399         return MCC_Status();
00400       }
00401       if(reuse_authn) authn_ = true;  //Reuse or not reuse the result from saml2sso
00402     }
00403     //Send the real message
00404     Arc::MCC_Status status = soap_client_->process(action, request, response);
00405     return status;
00406   }
00407 
00408 } // namespace Arc