Resource requests are mapped in the Local section into an applicationId that
points into to this section.
-->
- <Applications id="default" policyId="default" providerId="https://sp.example.org/shibboleth"
+ <Applications id="default" policyId="default" entityID="https://sp.example.org/shibboleth"
homeURL="https://sp.example.org/index.html">
<!--
handlerURL="/Shibboleth.sso" handlerSSL="false" idpHistory="true" idpHistoryDays="7">
<!--
- SessionInitiators handle session requests and relay them to a WAYF or directly
- to an IdP, if possible. Automatic session setup will use the default or first
+ SessionInitiators handle session requests and relay them to a Discovery page,
+ or to an IdP if possible. Automatic session setup will use the default or first
element (or requireSessionWith can specify a specific id to use). Lazy sessions
- can be started with any initiator by redirecting to it. The only Binding supported
- is the "urn:mace:shibboleth:sp:1.3:SessionInit" lazy session profile using query
- string parameters:
- * target the resource to direct back to later (or homeURL will be used)
+ can be started with any initiator by redirecting to it using query string parameters:
+
+ * entityID optional direct invocation of a specific IdP
+ * target optional resource to direct back to later (or homeURL will be used)
* acsIndex optional index of an ACS to use on the way back in
- * providerId optional direct invocation of a specific IdP
-->
+
+ <!-- Default example directs to a specific IdP's Shibboleth 1.x SSO service. -->
+ <SessionInitiator type="Shibboleth" Location="/Login" isDefault="true" id="example.org"
+ relayState="cookie" entityID="https://idp.example.org/shibboleth"/>
- <!-- This default example directs users to a specific IdP's SSO service. -->
- <SessionInitiator isDefault="true" id="default" Location="/Login"
- Binding="urn:mace:shibboleth:sp:1.3:SessionInit" relayState="cookie"
- wayfURL="https://idp.example.org/shibboleth-idp/SSO"
- wayfBinding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"/>
+ <!-- An example using the Shibboleth 1.x protocol but with an external WAYF. -->
+ <SessionInitiator type="Shibboleth" Location="/WAYF" id="wayf"
+ relayState="cookie" wayfURL="https://wayf.example.org/WAYF"/>
<!--
- md:AssertionConsumerService elements replace the old shireURL function with an
- explicit handler for particular profiles, such as SAML 1.1 POST or Artifact.
- The isDefault and index attributes are used when sessions are initiated
- to determine how to tell the IdP where and how to return the response.
+ md:AssertionConsumerService elements handle specific SSO protocol bindings,
+ such as SAML 2.0 POST or SAML 1.1 Artifact. The isDefault and index attributes
+ are used when sessions are initiated to determine how to tell the IdP where and
+ how to return the response.
-->
<md:AssertionConsumerService Location="/SAML/POST" isDefault="true" index="1"
Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post"/>
void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
Settings getSettings(const SPRequest& request) const;
+ void setParent(const PropertySet*) {}
pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
<element ref="conf:Application" minOccurs="0" maxOccurs="unbounded"/>\r
</sequence>\r
<attribute name="id" type="conf:string" fixed="default"/>\r
- <attribute name="providerId" type="anyURI" use="required"/>\r
+ <attribute name="entityID" type="anyURI" use="required"/>\r
<attribute name="policyId" type="conf:string" use="required"/>\r
- <attribute name="homeURL" type="anyURI"/>\r
+ <attribute name="homeURL" type="anyURI" default="/"/>\r
<attribute name="attributeIds" type="conf:listOfStrings"/>\r
<anyAttribute namespace="##other" processContents="lax"/>\r
</complexType>\r
<element name="CredentialResolver" type="conf:PluggableType" minOccurs="0"/>\r
</sequence>\r
<attribute name="id" type="conf:string" use="required"/>\r
- <attribute name="providerId" type="anyURI"/>\r
+ <attribute name="entityID" type="anyURI"/>\r
<attribute name="policyId" type="conf:string"/>\r
- <attribute name="homeURL" type="anyURI"/>\r
+ <attribute name="homeURL" type="anyURI" default="/"/>\r
<attribute name="attributeIds" type="conf:listOfStrings"/>\r
<anyAttribute namespace="##other" processContents="lax"/>\r
</complexType>\r
\r
<element name="SessionInitiator">\r
<annotation>\r
- <documentation>Used to specify handlers that can issue AuthnRequests</documentation>\r
+ <documentation>Used to specify handlers that can issue AuthnRequests or perform discovery</documentation>\r
</annotation>\r
<complexType>\r
- <sequence>\r
- <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>\r
- </sequence>\r
- <attribute name="Location" type="anyURI" use="required"/>\r
- <attribute name="Binding" type="anyURI" use="required"/>\r
- <attribute name="id" type="conf:string"/>\r
- <attribute name="isDefault" type="boolean"/>\r
- <attribute name="wayfURL" type="anyURI"/>\r
- <attribute name="wayfBinding" type="anyURI"/>\r
- <attribute name="relayState" type="conf:string"/>\r
- <anyAttribute namespace="##any" processContents="lax"/>\r
+ <complexContent>\r
+ <restriction base="conf:PluggableType">\r
+ <sequence>\r
+ <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>\r
+ </sequence>\r
+ <attribute name="Location" type="anyURI"/>\r
+ <attribute name="id" type="conf:string"/>\r
+ <attribute name="isDefault" type="boolean"/>\r
+ <attribute name="wayfURL" type="anyURI"/>\r
+ <attribute name="relayState" type="conf:string"/>\r
+ <attribute name="entityID" type="anyURI"/>\r
+ <anyAttribute namespace="##any" processContents="lax"/>\r
+ </restriction>\r
+ </complexContent>\r
</complexType>\r
</element>\r
-\r
+ \r
<element name="Errors">\r
<annotation>\r
<documentation>Container for error templates and associated details</documentation>\r
binding/impl/SOAPClient.cpp \
handler/impl/AbstractHandler.cpp \
handler/impl/AssertionConsumerService.cpp \
+ handler/impl/ChainingSessionInitiator.cpp \
handler/impl/RemotedHandler.cpp \
handler/impl/SAML1Consumer.cpp \
handler/impl/SAML2Consumer.cpp \
+ handler/impl/Shib1SessionInitiator.cpp \
impl/RemotedSessionCache.cpp \
impl/StorageServiceSessionCache.cpp \
impl/XMLAccessControl.cpp \
vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
\r
auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
- const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
+ const char* relyingParty = ctx.getApplication().getString("entityID").second;\r
\r
#ifdef HAVE_GOOD_STL\r
map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
\r
auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
- const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
+ const char* relyingParty = ctx.getApplication().getString("entityID").second;\r
\r
#ifdef HAVE_GOOD_STL\r
map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
\r
const vector<saml2::EncryptedAttribute*>& encattrs = const_cast<const saml2::AttributeStatement*>(*s)->getEncryptedAttributes();\r
if (!encattrs.empty()) {\r
- const XMLCh* recipient = ctx.getApplication().getXMLString("providerId").second;\r
+ const XMLCh* recipient = ctx.getApplication().getXMLString("entityID").second;\r
CredentialResolver* cr = ctx.getApplication().getCredentialResolver();\r
if (!cr) {\r
Category::getInstance(SHIBSP_LOGCAT".AttributeResolver").warn(\r
if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
continue;\r
auto_ptr_char loc((*ep)->getLocation());\r
- auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
+ auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second);\r
saml1::Subject* subject = saml1::SubjectBuilder::buildSubject();\r
subject->setNameIdentifier(nameid.cloneNameIdentifier());\r
saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery();\r
if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
continue;\r
auto_ptr_char loc((*ep)->getLocation());\r
- auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
+ auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second);\r
saml2::Subject* subject = saml2::SubjectBuilder::buildSubject();\r
subject->setNameID(nameid.cloneNameID());\r
saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery();\r
if (!XMLString::equals((*ep)->getBinding(),binding.get()))
continue;
auto_ptr_char loc((*ep)->getLocation());
- auto_ptr_XMLCh issuer(sppolicy.getApplication().getString("providerId").second);
+ auto_ptr_XMLCh issuer(sppolicy.getApplication().getString("entityID").second);
ArtifactResolve* request = ArtifactResolveBuilder::buildArtifactResolve();
Issuer* iss = IssuerBuilder::buildIssuer();
request->setIssuer(iss);
namespace shibsp {
+ class SHIBSP_API SPRequest;
+
#if defined (_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4250 )
virtual void checkError(const xmltooling::XMLObject* response) const;
/**
+ * Implements various mechanisms to preserve RelayState,
+ * such as cookies or StorageService-backed keys.
+ *
+ * <p>If a supported mechanism can be identified, the input parameter will be
+ * replaced with a suitable state key, URL-encoded.
+ *
+ * @param request the active SPRequest
+ * @param relayState RelayState token to supply with message
+ */
+ virtual void preserveRelayState(SPRequest& request, std::string& relayState) const;
+
+ /**
* Implements various mechanisms to recover RelayState,
* such as cookies or StorageService-backed keys.
*
std::string processMessage(
const Application& application,
opensaml::HTTPRequest& httpRequest,
- std::string& providerId,
+ std::string& entityID,
std::string& relayState
) const;
std::pair<bool,long> sendRedirect(
- SPRequest& request, const char* key, const char* providerId, const char* relayState
+ SPRequest& request, const char* key, const char* entityID, const char* relayState
) const;
- void maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const;
+ void maintainHistory(SPRequest& request, const char* entityID, const char* cookieProps) const;
opensaml::MessageDecoder* m_decoder;
xmltooling::auto_ptr_char m_configNS;
/** Registers Handler implementations. */
void SHIBSP_API registerHandlers();
+
+ /** SessionInitiator that iterates through a set of protocol-specific versions. */
+ #define CHAINING_SESSION_INITIATOR "Chaining"
+
+ /** SessionInitiator that supports Shibboleth V1 AuthnRequest/WAYF redirects. */
+ #define SHIB1_SESSION_INITIATOR "Shibboleth"
};
#endif /* __shibsp_handler_h__ */
#include "internal.h"
#include "Application.h"
#include "exceptions.h"
+#include "ServiceProvider.h"
#include "SPRequest.h"
#include "handler/AbstractHandler.h"
+#include "remoting/ListenerService.h"
+#include <log4cpp/Category.hh>
#include <saml/saml1/core/Protocols.h>
#include <saml/saml2/core/Protocols.h>
#include <saml/util/SAMLConstants.h>
#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/StorageService.h>
#include <xmltooling/util/URLEncoder.h>
using namespace shibsp;
using namespace samlconstants;
using namespace opensaml;
using namespace xmltooling;
+using namespace log4cpp;
using namespace xercesc;
using namespace std;
namespace shibsp {
SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory SAML1ConsumerFactory;
SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory SAML2ConsumerFactory;
+ SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory ChainingSessionInitiatorFactory;
+ SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory Shib1SessionInitiatorFactory;
};
void SHIBSP_API shibsp::registerHandlers()
{
SPConfig& conf=SPConfig::getConfig();
+
conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_ARTIFACT, SAML1ConsumerFactory);
conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_POST, SAML1ConsumerFactory);
conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2ConsumerFactory);
conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2ConsumerFactory);
conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2ConsumerFactory);
+
+ conf.SessionInitiatorManager.registerFactory(CHAINING_SESSION_INITIATOR, ChainingSessionInitiatorFactory);
+ conf.SessionInitiatorManager.registerFactory(SHIB1_SESSION_INITIATOR, Shib1SessionInitiatorFactory);
}
AbstractHandler::AbstractHandler(
}
}
+void AbstractHandler::preserveRelayState(SPRequest& request, string& relayState) const
+{
+ pair<bool,const char*> mech=getString("relayState");
+ const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
+
+ // No setting means just pass it by value.
+ if (!mech.first || !mech.second || !*mech.second) {
+ relayState = urlenc->encode(relayState.c_str());
+ }
+ else if (!strcmp(mech.second, "cookie")) {
+ // Here we store the state in a cookie and send a fixed
+ // value so we can recognize it on the way back.
+ pair<string,const char*> shib_cookie=request.getApplication().getCookieNameProps("_shibstate_");
+ string stateval = urlenc->encode(relayState.c_str()) + shib_cookie.second;
+ request.setCookie(shib_cookie.first.c_str(),stateval.c_str());
+ relayState = "cookie";
+ }
+ else if (strstr(mech.second,"ss:")==mech.second) {
+ mech.second+=3;
+ if (*mech.second) {
+ DDF out,in = DDF("set::RelayState").structure();
+ in.addmember("id").string(mech.second);
+ in.addmember("value").string(relayState.c_str());
+ DDFJanitor jin(in),jout(out);
+ out = request.getServiceProvider().getListenerService()->send(in);
+ if (!out.isstring())
+ throw IOException("StorageService-backed RelayState mechanism did not return a state key.");
+ relayState = string(mech.second-3) + ':' + urlenc->encode(out.string());
+ }
+ }
+ else
+ throw ConfigurationException("Unsupported relayState mechanism ($1).", params(1,mech.second));
+}
+
void AbstractHandler::recoverRelayState(HTTPRequest& httpRequest, string& relayState) const
{
SPConfig& conf = SPConfig::getConfig();
- if (conf.isEnabled(SPConfig::OutOfProcess)) {
- // Out of process, we look for StorageService-backed state.
- // TODO: something like ss:SSID:key?
+
+ // Look for StorageService-backed state of the form "ss:SSID:key".
+ const char* state = relayState.c_str();
+ if (strstr(state,"ss:")==state) {
+ state += 3;
+ const char* key = strchr(state,':');
+ if (key) {
+ string ssid = relayState.substr(3, key - state);
+ key++;
+ if (!ssid.empty() && *key) {
+ if (conf.isEnabled(SPConfig::OutOfProcess)) {
+ StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
+ if (storage) {
+ if (storage->readString("RelayState",key,&relayState)>0)
+ storage->deleteString("RelayState",key);
+ else
+ relayState = "default";
+ }
+ else {
+ Category::getInstance(SHIBSP_LOGCAT".Handler").error(
+ "Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str()
+ );
+ relayState = "default";
+ }
+ }
+ else if (conf.isEnabled(SPConfig::InProcess)) {
+ // In process, we should be able to cast down to a full SPRequest.
+ SPRequest& request = dynamic_cast<SPRequest&>(httpRequest);
+ DDF out,in = DDF("get::RelayState").structure();
+ in.addmember("id").string(ssid.c_str());
+ in.addmember("key").string(key);
+ DDFJanitor jin(in),jout(out);
+ out = request.getServiceProvider().getListenerService()->send(in);
+ if (!out.isstring())
+ throw IOException("StorageService-backed RelayState mechanism did not return a state value.");
+ relayState = out.string();
+ }
+ }
+ }
}
-
- if (conf.isEnabled(SPConfig::InProcess)) {
+ else if (conf.isEnabled(SPConfig::InProcess)) {
// In process, we should be able to cast down to a full SPRequest.
SPRequest& request = dynamic_cast<SPRequest&>(httpRequest);
if (relayState.empty() || relayState == "cookie") {
if (conf.isEnabled(SPConfig::OutOfProcess)) {
// When out of process, we run natively and directly process the message.
// RelayState will be fully handled during message processing.
- string providerId;
- string key = processMessage(request.getApplication(), request, providerId, relayState);
- return sendRedirect(request, key.c_str(), providerId.c_str(), relayState.c_str());
+ string entityID;
+ string key = processMessage(request.getApplication(), request, entityID, relayState);
+ return sendRedirect(request, key.c_str(), entityID.c_str(), relayState.c_str());
}
else {
// When not out of process, we remote all the message processing.
throw FatalProfileException("Remote processing of SSO profile did not return a usable session key.");
// Take care of cookie business and wrap it up.
- return sendRedirect(request, out["key"].string(), out["provider_id"].string(), relayState.c_str());
+ return sendRedirect(request, out["key"].string(), out["entity_id"].string(), relayState.c_str());
}
}
catch (XMLToolingException& ex) {
auto_ptr<HTTPRequest> http(getRequest(in));
// Do the work.
- string relayState, providerId;
+ string relayState, entityID;
try {
- string key = processMessage(*app, *http.get(), providerId, relayState);
+ string key = processMessage(*app, *http.get(), entityID, relayState);
// Repack for return to caller.
DDF ret=DDF(NULL).structure();
DDFJanitor jret(ret);
ret.addmember("key").string(key.c_str());
- if (!providerId.empty())
- ret.addmember("provider_id").string(providerId.c_str());
+ if (!entityID.empty())
+ ret.addmember("entity_id").string(entityID.c_str());
if (!relayState.empty())
ret.addmember("RelayState").string(relayState.c_str());
out << ret;
}
string AssertionConsumerService::processMessage(
- const Application& application, HTTPRequest& httpRequest, string& providerId, string& relayState
+ const Application& application, HTTPRequest& httpRequest, string& entityID, string& relayState
) const
{
// Locate policy key.
auto_ptr_char issuer(policy.getIssuer() ? policy.getIssuer()->getName() : NULL);
if (issuer.get())
- providerId = issuer.get();
+ entityID = issuer.get();
return key;
}
pair<bool,long> AssertionConsumerService::sendRedirect(
- SPRequest& request, const char* key, const char* providerId, const char* relayState
+ SPRequest& request, const char* key, const char* entityID, const char* relayState
) const
{
// We've got a good session, so set the session cookie.
request.setCookie(shib_cookie.first.c_str(), k.c_str());
// History cookie.
- maintainHistory(request, providerId, shib_cookie.second);
+ maintainHistory(request, entityID, shib_cookie.second);
// Now redirect to the state value. By now, it should be set to *something* usable.
return make_pair(true, request.sendRedirect(relayState));
return NULL;
}
-void AssertionConsumerService::maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const
+void AssertionConsumerService::maintainHistory(SPRequest& request, const char* entityID, const char* cookieProps) const
{
- if (!providerId)
+ if (!entityID)
return;
const PropertySet* sessionProps=request.getApplication().getPropertySet("Sessions");
// Either leave in memory or set an expiration.
pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
if (!days.first || days.second==0) {
- string c = string(cdc.set(providerId)) + cookieProps;
+ string c = string(cdc.set(entityID)) + cookieProps;
request.setCookie(CommonDomainCookie::CDCName, c.c_str());
}
else {
#endif
char timebuf[64];
strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime);
- string c = string(cdc.set(providerId)) + cookieProps + "; expires=" + timebuf;
+ string c = string(cdc.set(entityID)) + cookieProps + "; expires=" + timebuf;
request.setCookie(CommonDomainCookie::CDCName, c.c_str());
}
}
--- /dev/null
+/*
+ * Copyright 2001-2007 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * ChainingSessionInitiator.cpp
+ *
+ * Chains together multiple SessionInitiator handlers in sequence.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "handler/AbstractHandler.h"
+#include "util/SPConstants.h"
+
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xmltooling/util/XMLHelper.h>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+ #pragma warning( push )
+ #pragma warning( disable : 4250 )
+#endif
+
+ class SHIBSP_DLLLOCAL ChainingSessionInitiator : public AbstractHandler
+ {
+ public:
+ ChainingSessionInitiator(const DOMElement* e, const char* appId);
+ virtual ~ChainingSessionInitiator() {
+ for_each(m_handlers.begin(), m_handlers.end(), xmltooling::cleanup<Handler>());
+ }
+
+ pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+ private:
+ vector<Handler*> m_handlers;
+ };
+
+#if defined (_MSC_VER)
+ #pragma warning( pop )
+#endif
+
+ Handler* SHIBSP_DLLLOCAL ChainingSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
+ {
+ return new ChainingSessionInitiator(p.first, p.second);
+ }
+
+ static const XMLCh SessionInitiator[] = UNICODE_LITERAL_16(S,e,s,s,i,o,n,I,n,i,t,i,a,t,o,r);
+ static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
+
+ class SHIBSP_DLLLOCAL SessionInitiatorNodeFilter : public DOMNodeFilter
+ {
+ public:
+ short acceptNode(const DOMNode* node) const {
+ if (XMLHelper::isNodeNamed(node,shibspconstants::SHIB2SPCONFIG_NS,SessionInitiator))
+ return FILTER_REJECT;
+ return FILTER_ACCEPT;
+ }
+ };
+
+ static SHIBSP_DLLLOCAL SessionInitiatorNodeFilter g_SINFilter;
+};
+
+ChainingSessionInitiator::ChainingSessionInitiator(const DOMElement* e, const char* appId)
+ : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator"), &g_SINFilter)
+{
+ SPConfig& conf = SPConfig::getConfig();
+
+ // Load up the chain of handlers.
+ e = e ? XMLHelper::getFirstChildElement(e, SessionInitiator) : NULL;
+ while (e) {
+ auto_ptr_char type(e->getAttributeNS(NULL,_type));
+ if (type.get() && *(type.get())) {
+ m_handlers.push_back(conf.SessionInitiatorManager.newPlugin(type.get(),make_pair(e, appId)));
+ m_handlers.back()->setParent(this);
+ }
+ e = XMLHelper::getNextSiblingElement(e, SessionInitiator);
+ }
+}
+
+pair<bool,long> ChainingSessionInitiator::run(SPRequest& request, bool isHandler) const
+{
+ pair<bool,long> ret;
+ for (vector<Handler*>::const_iterator i = m_handlers.begin(); i!=m_handlers.end(); ++i) {
+ ret = (*i)->run(request, isHandler);
+ if (ret.first)
+ return ret;
+ }
+ throw ConfigurationException("None of the configured SessionInitiators handled the request.");
+}
saml2::Assertion* decrypted=NULL;
try {
Locker credlocker(cr);
- auto_ptr<XMLObject> wrapper((*ea)->decrypt(*cr, application.getXMLString("providerId").second, &cc));
+ auto_ptr<XMLObject> wrapper((*ea)->decrypt(*cr, application.getXMLString("entityID").second, &cc));
decrypted = dynamic_cast<saml2::Assertion*>(wrapper.get());
if (decrypted) {
wrapper.release();
else {
Locker credlocker(cr);
try {
- auto_ptr<XMLObject> decryptedID(encname->decrypt(*cr,application.getXMLString("providerId").second,&cc));
+ auto_ptr<XMLObject> decryptedID(encname->decrypt(*cr,application.getXMLString("entityID").second,&cc));
ssoName = dynamic_cast<NameID*>(decryptedID.get());
if (ssoName) {
ownedName = true;
--- /dev/null
+/*
+ * Copyright 2001-2007 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Shib1SessionInitiator.cpp
+ *
+ * Shibboleth 1.x AuthnRequest/WAYF support.
+ */
+
+#include "internal.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "SPRequest.h"
+#include "handler/AbstractHandler.h"
+#include "util/SPConstants.h"
+
+#include <saml/saml2/metadata/Metadata.h>
+#include <saml/saml2/metadata/EndpointManager.h>
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/XMLHelper.h>
+#include <xmltooling/util/URLEncoder.h>
+
+using namespace shibsp;
+using namespace opensaml::saml2md;
+using namespace opensaml;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+ #pragma warning( push )
+ #pragma warning( disable : 4250 )
+#endif
+
+ class SHIBSP_DLLLOCAL Shib1SessionInitiator : public AbstractHandler
+ {
+ public:
+ Shib1SessionInitiator(const DOMElement* e, const char* appId)
+ : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")) {}
+ virtual ~Shib1SessionInitiator() {}
+
+ pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+ private:
+ pair<bool,long> doAuthnRequest(SPRequest& request, const Handler* shire, const char* dest, string& target) const;
+ };
+
+#if defined (_MSC_VER)
+ #pragma warning( pop )
+#endif
+
+ Handler* SHIBSP_DLLLOCAL Shib1SessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
+ {
+ return new Shib1SessionInitiator(p.first, p.second);
+ }
+
+};
+
+pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, bool isHandler) const
+{
+ string target;
+ const char* entityID=NULL;
+ const Handler* ACS=NULL;
+ const Application& app=request.getApplication();
+
+ if (isHandler) {
+ entityID=request.getParameter("acsIndex");
+ if (entityID)
+ ACS=app.getAssertionConsumerServiceByIndex(atoi(entityID));
+
+ entityID = request.getParameter("target");
+ if (entityID)
+ target = entityID;
+ recoverRelayState(request, target);
+
+ // Try and establish which IdP to use.
+ entityID=request.getParameter("entityID");
+ if (!entityID || !*entityID)
+ entityID=request.getParameter("providerId");
+ if (!entityID || !*entityID)
+ entityID=getString("entityID").second;
+ }
+ else {
+ // We're running as a "virtual handler" from within the filter.
+ // The target resource is the current one and everything else is defaulted.
+ entityID=getString("entityID").second;
+ target=request.getRequestURL();
+ }
+
+ if (entityID && *entityID) {
+ m_log.debug("attempting to initiate session using SAML 1.x with provider (%s)", entityID);
+
+ // Use metadata to invoke the SSO service directly.
+ 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);
+ return make_pair(false,0);
+ }
+ const IDPSSODescriptor* role=entity->getIDPSSODescriptor(shibspconstants::SHIB1_PROTOCOL_ENUM);
+ if (!role) {
+ m_log.error("unable to locate Shibboleth-aware identity provider role for provider (%s)", entityID);
+ return make_pair(false,0);
+ }
+ const EndpointType* ep=EndpointManager<SingleSignOnService>(role->getSingleSignOnServices()).getByBinding(
+ shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI
+ );
+ if (!ep) {
+ m_log.error("unable to locate compatible SSO service for provider (%s)", entityID);
+ return make_pair(false,0);
+ }
+ auto_ptr_char dest(ep->getLocation());
+ return doAuthnRequest(request, ACS ? ACS : app.getDefaultAssertionConsumerService(), dest.get(), target);
+ }
+
+ // Fall back to optional legacy discovery service.
+ pair<bool,const char*> wayfURL=getString("wayfURL");
+ if (!wayfURL.first)
+ return make_pair(false,0);
+ return doAuthnRequest(request, ACS ? ACS : app.getDefaultAssertionConsumerService(), wayfURL.second, target);
+}
+
+pair<bool,long> Shib1SessionInitiator::doAuthnRequest(SPRequest& request, const Handler* shire, const char* dest, string& target) const
+{
+ // 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=shire ? shire->getString("Location") : pair<bool,const char*>(false,NULL);
+ if (loc.first) ACSloc+=loc.second;
+
+ preserveRelayState(request, target);
+
+ char timebuf[16];
+ sprintf(timebuf,"%u",time(NULL));
+ const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
+ string req=string(dest) + "?shire=" + urlenc->encode(ACSloc.c_str()) + "&time=" + timebuf + "&target=" + target +
+ "&providerId=" + urlenc->encode(request.getApplication().getString("entityID").second);
+
+ return make_pair(true, request.sendRedirect(req.c_str()));
+}
XMLApplication(const ServiceProvider*, const DOMElement* e, const XMLApplication* base=NULL);\r
~XMLApplication() { cleanup(); }\r
\r
- // PropertySet\r
- pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;\r
- pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;\r
- pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;\r
- pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;\r
- pair<bool,int> getInt(const char* name, const char* ns=NULL) const;\r
- const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:sp:config:2.0") const;\r
-\r
// Application\r
const ServiceProvider& getServiceProvider() const {return *m_sp;}\r
const char* getId() const {return getString("id").second;}\r
}\r
\r
// PropertySet\r
+ void setParent(const PropertySet* parent) {return m_impl->setParent(parent);}\r
pair<bool,bool> getBool(const char* name, const char* ns=NULL) const {return m_impl->getBool(name,ns);}\r
pair<bool,const char*> getString(const char* name, const char* ns=NULL) const {return m_impl->getString(name,ns);}\r
pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const {return m_impl->getXMLString(name,ns);}\r
try {\r
// First load any property sets.\r
load(e,log,this);\r
+ if (base)\r
+ setParent(base);\r
\r
SPConfig& conf=SPConfig::getConfig();\r
SAMLConfig& samlConf=SAMLConfig::getConfig();\r
XMLToolingConfig& xmlConf=XMLToolingConfig::getConfig();\r
\r
m_hash=getId();\r
- m_hash+=getString("providerId").second;\r
+ m_hash+=getString("entityID").second;\r
m_hash=samlConf.hashSHA1(m_hash.c_str(), true);\r
\r
pair<bool,const char*> attributes = getString("attributeIds");\r
}\r
}\r
else if (XMLString::equals(child->getLocalName(),SessionInitiator)) {\r
- auto_ptr_char bindprop(child->getAttributeNS(NULL,EndpointType::BINDING_ATTRIB_NAME));\r
- if (!bindprop.get() || !*(bindprop.get())) {\r
- log.warn("SessionInitiator element has no Binding attribute, skipping it...");\r
+ auto_ptr_char type(child->getAttributeNS(NULL,_type));\r
+ if (!type.get() || !*(type.get())) {\r
+ log.warn("SessionInitiator element has no type attribute, skipping it...");\r
child = XMLHelper::getNextSiblingElement(child);\r
continue;\r
}\r
- handler=conf.SessionInitiatorManager.newPlugin(bindprop.get(),make_pair(child, getId()));\r
+ handler=conf.SessionInitiatorManager.newPlugin(type.get(),make_pair(child, getId()));\r
pair<bool,const char*> si_id=handler->getString("id");\r
if (si_id.first && si_id.second)\r
m_sessionInitMap[si_id.second]=handler;\r
if (nlist->item(i)->getParentNode()->isSameNode(e) && nlist->item(i)->hasChildNodes())\r
m_audiences.push_back(nlist->item(i)->getFirstChild()->getNodeValue());\r
\r
- // Always include our own providerId as an audience.\r
- m_audiences.push_back(getXMLString("providerId").second);\r
+ // Always include our own entityID as an audience.\r
+ m_audiences.push_back(getXMLString("entityID").second);\r
\r
if (conf.isEnabled(SPConfig::Metadata)) {\r
child = XMLHelper::getFirstChildElement(e,_MetadataProvider);\r
return FILTER_ACCEPT;\r
}\r
\r
-pair<bool,bool> XMLApplication::getBool(const char* name, const char* ns) const\r
-{\r
- pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);\r
- if (ret.first)\r
- return ret;\r
- return m_base ? m_base->getBool(name,ns) : ret;\r
-}\r
-\r
-pair<bool,const char*> XMLApplication::getString(const char* name, const char* ns) const\r
-{\r
- pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);\r
- if (ret.first)\r
- return ret;\r
- return m_base ? m_base->getString(name,ns) : ret;\r
-}\r
-\r
-pair<bool,const XMLCh*> XMLApplication::getXMLString(const char* name, const char* ns) const\r
-{\r
- pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);\r
- if (ret.first)\r
- return ret;\r
- return m_base ? m_base->getXMLString(name,ns) : ret;\r
-}\r
-\r
-pair<bool,unsigned int> XMLApplication::getUnsignedInt(const char* name, const char* ns) const\r
-{\r
- pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);\r
- if (ret.first)\r
- return ret;\r
- return m_base ? m_base->getUnsignedInt(name,ns) : ret;\r
-}\r
-\r
-pair<bool,int> XMLApplication::getInt(const char* name, const char* ns) const\r
-{\r
- pair<bool,int> ret=DOMPropertySet::getInt(name,ns);\r
- if (ret.first)\r
- return ret;\r
- return m_base ? m_base->getInt(name,ns) : ret;\r
-}\r
-\r
-const PropertySet* XMLApplication::getPropertySet(const char* name, const char* ns) const\r
-{\r
- const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);\r
- if (ret || !m_base)\r
- return ret;\r
- return m_base->getPropertySet(name,ns);\r
-}\r
-\r
const PropertySet* XMLApplication::getRelyingParty(const EntityDescriptor* provider) const\r
{\r
if (!m_partyDefault && m_base)\r
>\r
</File>\r
<File\r
+ RelativePath=".\handler\impl\ChainingSessionInitiator.cpp"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath=".\handler\impl\RemotedHandler.cpp"\r
>\r
</File>\r
RelativePath=".\handler\impl\SAML2Consumer.cpp"\r
>\r
</File>\r
+ <File\r
+ RelativePath=".\handler\impl\Shib1SessionInitiator.cpp"\r
+ >\r
+ </File>\r
</Filter>\r
</Filter>\r
</Filter>\r
pair<bool,bool> DOMPropertySet::getBool(const char* name, const char* ns) const
{
- pair<bool,bool> ret(false,false);
map<string,pair<char*,const XMLCh*> >::const_iterator i;
if (ns)
else
i=m_map.find(name);
- if (i!=m_map.end()) {
- ret.first=true;
- ret.second=(!strcmp(i->second.first,"true") || !strcmp(i->second.first,"1"));
- }
- return ret;
+ if (i!=m_map.end())
+ return make_pair(true,(!strcmp(i->second.first,"true") || !strcmp(i->second.first,"1")));
+ else if (m_parent)
+ return m_parent->getBool(name,ns);
+ return make_pair(false,false);
}
pair<bool,const char*> DOMPropertySet::getString(const char* name, const char* ns) const
else
i=m_map.find(name);
- if (i!=m_map.end()) {
- ret.first=true;
- ret.second=i->second.first;
- }
- return ret;
+ if (i!=m_map.end())
+ return make_pair(true,i->second.first);
+ else if (m_parent)
+ return m_parent->getString(name,ns);
+ return pair<bool,const char*>(false,NULL);
}
pair<bool,const XMLCh*> DOMPropertySet::getXMLString(const char* name, const char* ns) const
{
- pair<bool,const XMLCh*> ret(false,NULL);
map<string,pair<char*,const XMLCh*> >::const_iterator i;
if (ns)
else
i=m_map.find(name);
- if (i!=m_map.end()) {
- ret.first=true;
- ret.second=i->second.second;
- }
- return ret;
+ if (i!=m_map.end())
+ return make_pair(true,i->second.second);
+ else if (m_parent)
+ return m_parent->getXMLString(name,ns);
+ return pair<bool,const XMLCh*>(false,NULL);
}
pair<bool,unsigned int> DOMPropertySet::getUnsignedInt(const char* name, const char* ns) const
{
- pair<bool,unsigned int> ret(false,0);
map<string,pair<char*,const XMLCh*> >::const_iterator i;
if (ns)
else
i=m_map.find(name);
- if (i!=m_map.end()) {
- ret.first=true;
- ret.second=strtol(i->second.first,NULL,10);
- }
- return ret;
+ if (i!=m_map.end())
+ return pair<bool,unsigned int>(true,strtol(i->second.first,NULL,10));
+ else if (m_parent)
+ return m_parent->getUnsignedInt(name,ns);
+ return pair<bool,unsigned int>(false,0);
}
pair<bool,int> DOMPropertySet::getInt(const char* name, const char* ns) const
{
- pair<bool,int> ret(false,0);
map<string,pair<char*,const XMLCh*> >::const_iterator i;
if (ns)
else
i=m_map.find(name);
- if (i!=m_map.end()) {
- ret.first=true;
- ret.second=atoi(i->second.first);
- }
- return ret;
+ if (i!=m_map.end())
+ return pair<bool,int>(true,atoi(i->second.first));
+ else if (m_parent)
+ return m_parent->getInt(name,ns);
+ return pair<bool,int>(false,0);
}
const PropertySet* DOMPropertySet::getPropertySet(const char* name, const char* ns) const
else
i=m_nested.find(name);
- return (i!=m_nested.end()) ? i->second : NULL;
+ return (i!=m_nested.end()) ? i->second : (m_parent ? m_parent->getPropertySet(name,ns) : NULL);
}
class SHIBSP_API DOMPropertySet : public virtual PropertySet
{
public:
- DOMPropertySet() : m_root(NULL) {}
+ DOMPropertySet() : m_parent(NULL), m_root(NULL) {}
virtual ~DOMPropertySet();
+ void setParent(const PropertySet* parent) {
+ m_parent = parent;
+ }
+
std::pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
std::pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
std::pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
);
private:
+ const PropertySet* m_parent;
const xercesc::DOMElement* m_root;
std::map<std::string,std::pair<char*,const XMLCh*> > m_map;
std::map<std::string,DOMPropertySet*> m_nested;
virtual ~PropertySet() {}
/**
+ * Establishes a "parent" PropertySet to supply inherited settings.
+ *
+ * @param parent the parent PropertySet to use
+ */
+ virtual void setParent(const PropertySet* parent)=0;
+
+ /**
* Returns a boolean-valued property.
*
* @param name property name
auto_ptr_XMLCh domain(q_param);\r
auto_ptr_XMLCh name(n_param);\r
auto_ptr_XMLCh format(f_param);\r
- auto_ptr_XMLCh issuer(app->getString("providerId").second);\r
+ auto_ptr_XMLCh issuer(app->getString("entityID").second);\r
\r
MetadataProvider* m=app->getMetadataProvider();\r
xmltooling::Locker mlocker(m);\r