using namespace opensaml::saml2;
using namespace opensaml::saml2p;
using namespace opensaml::saml2md;
+#else
+#include <xercesc/util/XMLUniDefs.hpp>
#endif
using namespace shibsp;
using namespace opensaml;
using namespace xmltooling;
-using namespace log4cpp;
using namespace std;
namespace shibsp {
) const;
string m_appId;
+ auto_ptr_char m_paosNS,m_ecpNS;
+ auto_ptr_XMLCh m_paosBinding;
#ifndef SHIBSP_LITE
XMLCh* m_outgoing;
vector<const XMLCh*> m_bindings;
map<const XMLCh*,MessageEncoder*> m_encoders;
+ MessageEncoder* m_ecp;
AuthnRequest* m_requestTemplate;
+#else
+ bool m_ecp;
#endif
};
};
SAML2SessionInitiator::SAML2SessionInitiator(const DOMElement* e, const char* appId)
- : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")), m_appId(appId)
+ : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.SAML2")), m_appId(appId),
+ m_paosNS(samlconstants::PAOS_NS), m_ecpNS(samlconstants::SAML20ECP_NS), m_paosBinding(samlconstants::SAML20_BINDING_PAOS)
{
-#ifndef SHIBSP_LITE
+ static const XMLCh ECP[] = UNICODE_LITERAL_3(E,C,P);
+ const XMLCh* flag = e ? e->getAttributeNS(NULL,ECP) : NULL;
+#ifdef SHIBSP_LITE
+ m_ecp = (flag && (*flag == chLatin_t || *flag == chDigit_1));
+#else
m_outgoing=NULL;
+ m_ecp = NULL;
m_requestTemplate=NULL;
+
if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
// Check for a template AuthnRequest to build from.
DOMElement* child = XMLHelper::getFirstChildElement(e, samlconstants::SAML20P_NS, AuthnRequest::LOCAL_NAME);
if (child)
m_requestTemplate = dynamic_cast<AuthnRequest*>(AuthnRequestBuilder::buildOneFromElement(child));
+ // If directed, build an ECP encoder.
+ if (flag && (*flag == chLatin_t || *flag == chDigit_1)) {
+ try {
+ m_ecp = SAMLConfig::getConfig().MessageEncoderManager.newPlugin(
+ samlconstants::SAML20_BINDING_PAOS, pair<const DOMElement*,const XMLCh*>(e,NULL)
+ );
+ }
+ catch (exception& ex) {
+ m_log.error("error building PAOS/ECP MessageEncoder: %s", ex.what());
+ }
+ }
+
// Handle outgoing binding setup.
pair<bool,const XMLCh*> outgoing = getXMLString("outgoingBindings");
if (outgoing.first) {
m_outgoing = XMLString::replicate(outgoing.second);
+ XMLString::trim(m_outgoing);
}
else {
// No override, so we'll install a default binding precedence.
string prec = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
m_outgoing = XMLString::transcode(prec.c_str());
- XMLString::trim(m_outgoing);
}
int pos;
m_bindings.push_back(start);
try {
auto_ptr_char b(start);
- MessageEncoder * encoder = SAMLConfig::getConfig().MessageEncoderManager.newPlugin(b.get(),e);
+ MessageEncoder * encoder = SAMLConfig::getConfig().MessageEncoderManager.newPlugin(
+ b.get(),pair<const DOMElement*,const XMLCh*>(e,NULL)
+ );
m_encoders[start] = encoder;
m_log.debug("supporting outgoing binding (%s)", b.get());
}
pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const
{
- // We have to know the IdP to function.
- if (!entityID || !*entityID)
- return make_pair(false,0);
+ // First check for ECP support, since that doesn't require an IdP to be known.
+ bool ECP = false;
+ if (m_ecp && request.getHeader("Accept").find("application/vnd.paos+xml") != string::npos) {
+ string PAOS = request.getHeader("PAOS");
+ if (PAOS.find(m_paosNS.get()) != string::npos && PAOS.find(m_ecpNS.get()) != string::npos)
+ ECP = true;
+ }
+
+ // We have to know the IdP to function unless this is ECP.
+ if (!ECP && (!entityID || !*entityID))
+ return make_pair(false,0L);
string target;
const Handler* ACS=NULL;
pair<bool,const char*> acComp;
bool isPassive=false,forceAuthn=false;
const Application& app=request.getApplication();
- pair<bool,bool> acsByIndex = getBool("acsByIndex");
+
+ // ECP means the ACS will be by value no matter what.
+ pair<bool,bool> acsByIndex = ECP ? make_pair(true,false) : getBool("acsByIndex");
if (isHandler) {
option=request.getParameter("acsIndex");
if (option) {
ACS = app.getAssertionConsumerServiceByIndex(atoi(option));
if (!ACS)
- throw ConfigurationException("AssertionConsumerService with index ($1) not found, check configuration.", params(1,option));
+ request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using default ACS location");
+ else if (ECP && !XMLString::equals(ACS->getString("Binding").second, samlconstants::SAML20_BINDING_PAOS)) {
+ request.log(SPRequest::SPWarn, "acsIndex in request referenced a non-PAOS ACS, using default ACS location");
+ ACS = NULL;
+ }
}
option = request.getParameter("target");
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.
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");
}
- m_log.debug("attempting to initiate session using SAML 2.0 with provider (%s)", entityID);
+ 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);
+
+ if (!ACS) {
+ if (ECP) {
+ const vector<const Handler*>& handlers = app.getAssertionConsumerServicesByBinding(m_paosBinding.get());
+ if (handlers.empty())
+ throw ConfigurationException("Unable to locate PAOS response endpoint.");
+ ACS = handlers.front();
+ }
+ else {
+ pair<bool,unsigned int> index = getUnsignedInt("defaultACSIndex");
+ if (index.first) {
+ ACS = app.getAssertionConsumerServiceByIndex(index.second);
+ if (!ACS)
+ request.log(SPRequest::SPWarn, "invalid defaultACSIndex, using default ACS location");
+ }
+ if (!ACS)
+ ACS = app.getDefaultAssertionConsumerService();
+ }
+ }
- // To invoke the request builder, the key requirement is to figure out how and whether
+ // To invoke the request builder, the key requirement is to figure out how
// to express the ACS, by index or value, and if by value, where.
SPConfig& conf = SPConfig::getConfig();
if (conf.isEnabled(SPConfig::OutOfProcess)) {
if (!acsByIndex.first || acsByIndex.second) {
- // Pass by Index. This also allows for defaulting it entirely and sending nothing.
+ // Pass by Index.
if (isHandler) {
// We may already have RelayState set if we looped back here,
// but just in case target is a resource, we reset it back.
}
// Since we're not passing by index, we need to fully compute the return URL and binding.
- if (!ACS)
- ACS = app.getDefaultAssertionConsumerService();
-
// Compute the ACS URL. We add the ACS location to the base handlerURL.
string ACSloc=request.getHandlerURL(target.c_str());
pair<bool,const char*> loc=ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
DDF out,in = DDF(m_address.c_str()).structure();
DDFJanitor jin(in), jout(out);
in.addmember("application_id").string(app.getId());
- in.addmember("entity_id").string(entityID);
+ if (entityID)
+ in.addmember("entity_id").string(entityID);
if (isPassive)
in.addmember("isPassive").integer(1);
else if (forceAuthn)
}
else {
// Since we're not passing by index, we need to fully compute the return URL and binding.
- if (!ACS)
- ACS = app.getDefaultAssertionConsumerService();
-
// Compute the ACS URL. We add the ACS location to the base handlerURL.
string ACSloc=request.getHandlerURL(target.c_str());
pair<bool,const char*> loc=ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
throw ConfigurationException("Unable to locate application for new session, deleted?");
}
- const char* entityID = in["entity_id"].string();
- if (!entityID)
- throw ConfigurationException("No entityID parameter supplied to remoted SessionInitiator.");
-
DDF ret(NULL);
DDFJanitor jout(ret);
// a false/0 return, which we just return as an empty structure, or a response/redirect,
// which we capture in the facade and send back.
doRequest(
- *app, *http.get(), entityID,
+ *app, *http.get(), in["entity_id"].string(),
index.get(), in["acsLocation"].string(), bind.get(),
in["isPassive"].integer()==1, in["forceAuthn"].integer()==1,
in["authnContextClassRef"].string(), in["authnContextComparison"].string(),
out << ret;
}
+#ifndef SHIBSP_LITE
+namespace {
+ class _sameIdP : public binary_function<const IDPEntry*, const XMLCh*, bool>
+ {
+ public:
+ bool operator()(const IDPEntry* entry, const XMLCh* entityID) const {
+ return entry ? XMLString::equals(entry->getProviderID(), entityID) : false;
+ }
+ };
+};
+#endif
+
pair<bool,long> SAML2SessionInitiator::doRequest(
const Application& app,
HTTPResponse& httpResponse,
) const
{
#ifndef SHIBSP_LITE
- // Use metadata to locate the IdP's SSO service.
+ bool ECP = XMLString::equals(acsBinding, m_paosBinding.get());
+
+ pair<const EntityDescriptor*,const RoleDescriptor*> entity = pair<const EntityDescriptor*,const RoleDescriptor*>(NULL,NULL);
+ 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);
- const EntityDescriptor* entity=m->getEntityDescriptor(entityID);
- if (!entity) {
- m_log.error("unable to locate metadata for provider (%s)", entityID);
- throw MetadataException("Unable to locate metadata for identity provider ($entityID)",
- namedparams(1, "entityID", entityID));
- }
- const IDPSSODescriptor* role=entity->getIDPSSODescriptor(samlconstants::SAML20P_NS);
- if (!role) {
- m_log.error("unable to locate SAML 2.0 identity provider role for provider (%s)", entityID);
- return make_pair(false,0);
+
+ if (ECP) {
+ encoder = m_ecp;
+ if (!encoder) {
+ m_log.error("MessageEncoder for PAOS binding not available");
+ return make_pair(false,0L);
+ }
}
+ else {
+ // Use metadata to locate the IdP's SSO service.
+ MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
+ entity=m->getEntityDescriptor(mc);
+ if (!entity.first) {
+ 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.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.
- const EndpointType* ep=NULL;
- const MessageEncoder* encoder=NULL;
- vector<const XMLCh*>::const_iterator b;
- for (b = m_bindings.begin(); b!=m_bindings.end(); ++b) {
- if (ep=EndpointManager<SingleSignOnService>(role->getSingleSignOnServices()).getByBinding(*b)) {
- map<const XMLCh*,MessageEncoder*>::const_iterator enc = m_encoders.find(*b);
- if (enc!=m_encoders.end())
- encoder = enc->second;
- break;
+ // Loop over the supportable outgoing bindings.
+ role = dynamic_cast<const IDPSSODescriptor*>(entity.second);
+ vector<const XMLCh*>::const_iterator b;
+ for (b = m_bindings.begin(); b!=m_bindings.end(); ++b) {
+ if (ep=EndpointManager<SingleSignOnService>(role->getSingleSignOnServices()).getByBinding(*b)) {
+ map<const XMLCh*,MessageEncoder*>::const_iterator enc = m_encoders.find(*b);
+ if (enc!=m_encoders.end())
+ encoder = enc->second;
+ break;
+ }
+ }
+ if (!ep || !encoder) {
+ 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));
}
- }
- if (!ep || !encoder) {
- m_log.error("unable to locate compatible SSO service for provider (%s)", entityID);
- return make_pair(false,0);
}
preserveRelayState(app, httpResponse, relayState);
req->setIssueInstant(time(NULL));
}
- req->setDestination(ep->getLocation());
+ if (ep)
+ req->setDestination(ep->getLocation());
if (acsIndex && *acsIndex)
req->setAssertionConsumerServiceIndex(acsIndex);
if (acsLocation) {
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();
}
}
- auto_ptr_char dest(ep->getLocation());
-
- // Check for signing.
- const PropertySet* relyingParty = app.getRelyingParty(entity);
- pair<bool,bool> flag = relyingParty->getBool("signRequests");
- if ((flag.first && flag.second) || role->WantAuthnRequestsSigned()) {
- CredentialResolver* credResolver=app.getCredentialResolver();
- if (credResolver) {
- Locker credLocker(credResolver);
- // Fill in criteria to use.
- MetadataCredentialCriteria mcc(*role);
- mcc.setUsage(CredentialCriteria::SIGNING_CREDENTIAL);
- pair<bool,const char*> keyName = relyingParty->getString("keyName");
- if (keyName.first)
- mcc.getKeyNames().insert(keyName.second);
- pair<bool,const XMLCh*> sigalg = relyingParty->getXMLString("signatureAlg");
- if (sigalg.first)
- mcc.setXMLAlgorithm(sigalg.second);
- const Credential* cred = credResolver->resolve(&mcc);
- if (cred) {
- // Signed request.
- long ret = encoder->encode(
- httpResponse,
- req.get(),
- dest.get(),
- entity,
- relayState.c_str(),
- &app,
- cred,
- sigalg.second,
- relyingParty->getXMLString("digestAlg").second
- );
- req.release(); // freed by encoder
- return make_pair(true,ret);
- }
- else {
- m_log.warn("no signing credential resolved, leaving AuthnRequest unsigned");
- }
+ if (ECP && entityID) {
+ auto_ptr_XMLCh wideid(entityID);
+ Scoping* scoping = req->getScoping();
+ if (!scoping) {
+ scoping = ScopingBuilder::buildScoping();
+ req->setScoping(scoping);
+ }
+ IDPList* idplist = scoping->getIDPList();
+ if (!idplist) {
+ idplist = IDPListBuilder::buildIDPList();
+ scoping->setIDPList(idplist);
+ }
+ VectorOf(IDPEntry) entries = idplist->getIDPEntrys();
+ if (find_if(entries, bind2nd(_sameIdP(), wideid.get())) == NULL) {
+ IDPEntry* entry = IDPEntryBuilder::buildIDPEntry();
+ entry->setProviderID(wideid.get());
+ entries.push_back(entry);
}
}
- // Unsigned request.
- long ret = encoder->encode(httpResponse, req.get(), dest.get(), entity, relayState.c_str(), &app);
+ auto_ptr_char dest(ep ? ep->getLocation() : NULL);
+
+ long ret = sendMessage(
+ *encoder, req.get(), relayState.c_str(), dest.get(), role, app, httpResponse, role ? role->WantAuthnRequestsSigned() : false
+ );
req.release(); // freed by encoder
return make_pair(true,ret);
#else
- return make_pair(false,0);
+ return make_pair(false,0L);
#endif
}