Change API to permit session initiators to modify the entityID mid-chain.
[shibboleth/sp.git] / shibsp / handler / impl / SAML2SessionInitiator.cpp
index 6d74a34..644772c 100644 (file)
@@ -71,7 +71,7 @@ namespace shibsp {
         
         void setParent(const PropertySet* parent);
         void receive(DDF& in, ostream& out);
-        pair<bool,long> run(SPRequest& request, const char* entityID=NULL, bool isHandler=true) const;
+        pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
 
     private:
         pair<bool,long> doRequest(
@@ -204,7 +204,7 @@ void SAML2SessionInitiator::setParent(const PropertySet* parent)
     }
 }
 
-pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const
+pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
 {
     // First check for ECP support, since that doesn't require an IdP to be known.
     bool ECP = false;
@@ -215,8 +215,8 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
     }
 
     // We have to know the IdP to function unless this is ECP.
-    if (!ECP && (!entityID || !*entityID))
-        return make_pair(false,0);
+    if (!ECP && (entityID.empty()))
+        return make_pair(false,0L);
 
     string target;
     const Handler* ACS=NULL;
@@ -247,20 +247,38 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
         if (acsByIndex.first && !acsByIndex.second) {
             // Since we're passing the ACS by value, we need to compute the return URL,
             // so we'll need the target resource for real.
-            recoverRelayState(request.getApplication(), request, target, false);
+            recoverRelayState(request.getApplication(), request, request, target, false);
         }
 
+        pair<bool,bool> flag;
         option = request.getParameter("isPassive");
-        isPassive = (option && (*option=='1' || *option=='t'));
+        if (option) {
+            isPassive = (*option=='1' || *option=='t');
+        }
+        else {
+            flag = getBool("isPassive");
+            isPassive = (flag.first && flag.second);
+        }
         if (!isPassive) {
             option = request.getParameter("forceAuthn");
-            forceAuthn = (option && (*option=='1' || *option=='t'));
+            if (option) {
+                forceAuthn = (*option=='1' || *option=='t');
+            }
+            else {
+                flag = getBool("forceAuthn");
+                forceAuthn = (flag.first && flag.second);
+            }
         }
 
-        acClass.second = request.getParameter("authnContextClassRef");
-        acClass.first = (acClass.second!=NULL);
-        acComp.second = request.getParameter("authnContextComparison");
-        acComp.first = (acComp.second!=NULL);
+        if (acClass.second = request.getParameter("authnContextClassRef"))
+            acClass.first = true;
+        else
+            acClass = getString("authnContextClassRef");
+
+        if (acComp.second = request.getParameter("authnContextComparison"))
+            acComp.first = true;
+        else
+            acComp = getString("authnContextComparison");
     }
     else {
         // We're running as a "virtual handler" from within the filter.
@@ -269,20 +287,28 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
         const PropertySet* settings = request.getRequestSettings().first;
 
         pair<bool,bool> flag = settings->getBool("isPassive");
+        if (!flag.first)
+            flag = getBool("isPassive");
         isPassive = flag.first && flag.second;
         if (!isPassive) {
             flag = settings->getBool("forceAuthn");
+            if (!flag.first)
+                flag = getBool("forceAuthn");
             forceAuthn = flag.first && flag.second;
         }
 
         acClass = settings->getString("authnContextClassRef");
+        if (!acClass.first)
+            acClass = getString("authnContextClassRef");
         acComp = settings->getString("authnContextComparison");
+        if (!acComp.first)
+            acComp = getString("authnContextComparison");
     }
 
     if (ECP)
         m_log.debug("attempting to initiate session using SAML 2.0 Enhanced Client Profile");
     else
-        m_log.debug("attempting to initiate session using SAML 2.0 with provider (%s)", entityID);
+        m_log.debug("attempting to initiate session using SAML 2.0 with provider (%s)", entityID.c_str());
 
     if (!ACS) {
         if (ECP) {
@@ -319,7 +345,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
                     target = option;
             }
             return doRequest(
-                app, request, entityID,
+                app, request, entityID.c_str(),
                 ACS ? ACS->getXMLString("index").second : NULL, NULL, NULL,
                 isPassive, forceAuthn,
                 acClass.first ? acClass.second : NULL,
@@ -344,7 +370,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
         }
 
         return doRequest(
-            app, request, entityID,
+            app, request, entityID.c_str(),
             NULL, ACSloc.c_str(), ACS ? ACS->getXMLString("Binding").second : NULL,
             isPassive, forceAuthn,
             acClass.first ? acClass.second : NULL,
@@ -357,8 +383,8 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entit
     DDF out,in = DDF(m_address.c_str()).structure();
     DDFJanitor jin(in), jout(out);
     in.addmember("application_id").string(app.getId());
-    if (entityID)
-        in.addmember("entity_id").string(entityID);
+    if (!entityID.empty())
+        in.addmember("entity_id").string(entityID.c_str());
     if (isPassive)
         in.addmember("isPassive").integer(1);
     else if (forceAuthn)
@@ -466,27 +492,31 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     const IDPSSODescriptor* role = NULL;
     const EndpointType* ep = NULL;
     const MessageEncoder* encoder = NULL;
+
+    // We won't need this for ECP, but safety dictates we get the lock here.
+    MetadataProvider* m=app.getMetadataProvider();
+    Locker locker(m);
     
     if (ECP) {
         encoder = m_ecp;
         if (!encoder) {
             m_log.error("MessageEncoder for PAOS binding not available");
-            return make_pair(false,0);
+            return make_pair(false,0L);
         }
     }
     else {
         // Use metadata to locate the IdP's SSO service.
-        MetadataProvider* m=app.getMetadataProvider();
-        Locker locker(m);
         MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
         entity=m->getEntityDescriptor(mc);
         if (!entity.first) {
-            m_log.error("unable to locate metadata for provider (%s)", entityID);
+            m_log.warn("unable to locate metadata for provider (%s)", entityID);
             throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID));
         }
         else if (!entity.second) {
-            m_log.error("unable to locate SAML 2.0 identity provider role for provider (%s)", entityID);
-            return make_pair(false,0);
+            m_log.warn("unable to locate SAML 2.0 identity provider role for provider (%s)", entityID);
+            if (getParent())
+                return make_pair(false,0L);
+            throw MetadataException("Unable to locate SAML 2.0 identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID));
         }
 
         // Loop over the supportable outgoing bindings.
@@ -501,8 +531,10 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
             }
         }
         if (!ep || !encoder) {
-            m_log.error("unable to locate compatible SSO service for provider (%s)", entityID);
-            return make_pair(false,0);
+            m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID);
+            if (getParent())
+                return make_pair(false,0L);
+            throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID));
         }
     }
 
@@ -532,7 +564,7 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     if (!req->getIssuer()) {
         Issuer* issuer = IssuerBuilder::buildIssuer();
         req->setIssuer(issuer);
-        issuer->setName(app.getXMLString("entityID").second);
+        issuer->setName(app.getRelyingParty(entity.first)->getXMLString("entityID").second);
     }
     if (!req->getNameIDPolicy()) {
         NameIDPolicy* namepol = NameIDPolicyBuilder::buildNameIDPolicy();
@@ -587,6 +619,6 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     req.release();  // freed by encoder
     return make_pair(true,ret);
 #else
-    return make_pair(false,0);
+    return make_pair(false,0L);
 #endif
 }