#include <shibsp/util/PropertySet.h>
#include <saml/saml2/metadata/MetadataProvider.h>
#include <xmltooling/security/TrustEngine.h>
-#include <xmltooling/validation/Validator.h>
namespace shibsp {
* @return set of audience values associated with the Application
*/
virtual const std::vector<const XMLCh*>& getAudiences() const=0;
-
- /**
- * Returns a validator for applying verification rules to incoming SAML tokens.
- *
- * <p>The validator must be freed by the caller.
- *
- * @param ts timestamp against which to evaluate the token's validity, or 0 to ignore
- * @param role metadata role of token issuer, if known
- * @return a validator
- */
- virtual xmltooling::Validator* getTokenValidator(time_t ts=0, const opensaml::saml2md::RoleDescriptor* role=NULL) const=0;
};
};
handinclude_HEADERS = \
handler/AbstractHandler.h \
+ handler/AssertionConsumerService.h \
handler/Handler.h \
handler/RemotedHandler.h
attribute/resolver/impl/AttributeResolver.cpp \
attribute/resolver/impl/SimpleAttributeResolver.cpp \
binding/impl/SOAPClient.cpp \
- handler/impl/RemotedHandler.cpp \
handler/impl/AbstractHandler.cpp \
+ handler/impl/AssertionConsumerService.cpp \
+ handler/impl/RemotedHandler.cpp \
+ handler/impl/SAML1Consumer.cpp \
impl/RemotedSessionCache.cpp \
impl/StorageServiceSessionCache.cpp \
impl/XMLAccessControl.cpp \
registerAttributeDecoders();
registerAttributeFactories();
registerAttributeResolvers();
+ registerHandlers();
registerListenerServices();
registerRequestMappers();
registerSessionCaches();
class SHIBSP_API Application;
class SHIBSP_API Session;
+ class SHIBSP_API Attribute;
/**
* A context for a resolution request.
\r
#include "internal.h"\r
#include "Application.h"\r
+#include "ServiceProvider.h"\r
#include "SessionCache.h"\r
#include "attribute/AttributeDecoder.h"\r
#include "attribute/resolver/AttributeResolver.h"\r
#include <saml/saml1/binding/SAML1SOAPClient.h>\r
#include <saml/saml1/core/Assertions.h>\r
#include <saml/saml1/core/Protocols.h>\r
+#include <saml/saml1/profile/AssertionValidator.h>\r
#include <saml/saml2/binding/SAML2SOAPClient.h>\r
#include <saml/saml2/core/Protocols.h>\r
#include <saml/saml2/metadata/Metadata.h>\r
#include <saml/saml2/metadata/MetadataProvider.h>\r
+#include <saml/saml2/profile/AssertionValidator.h>\r
#include <xmltooling/util/NDC.h>\r
#include <xmltooling/util/ReloadableXMLFile.h>\r
#include <xmltooling/util/XMLHelper.h>\r
\r
SecurityPolicy policy;\r
shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
+ const PropertySet* policySettings = ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second);\r
+ pair<bool,bool> signedAssertions = policySettings->getBool("signedAssertions");\r
\r
auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);\r
saml1p::Response* response=NULL;\r
return;\r
}\r
\r
- time_t now = time(NULL);\r
- const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
const vector<saml1::Assertion*>& assertions = const_cast<const saml1p::Response*>(response)->getAssertions();\r
- if (assertions.size()==1) {\r
- auto_ptr<saml1p::Response> wrapper(response);\r
- saml1::Assertion* newtoken = assertions.front();\r
- if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer())) {\r
- log.error("assertion issued by someone other than AA, rejecting it");\r
- return;\r
- }\r
- try {\r
- tokval->validate(newtoken);\r
- }\r
- catch (exception& ex) {\r
- log.error("assertion failed validation check: %s", ex.what());\r
- }\r
- newtoken->detach();\r
- wrapper.release();\r
- ctx.getResolvedAssertions().push_back(newtoken);\r
- resolve(ctx, newtoken, attributes);\r
+ if (assertions.size()>1)\r
+ log.warn("simple resolver only supports one assertion in the query response");\r
+\r
+ auto_ptr<saml1p::Response> wrapper(response);\r
+ saml1::Assertion* newtoken = assertions.front();\r
+\r
+ if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {\r
+ log.error("assertion unsigned, rejecting it based on signedAssertions policy");\r
+ return;\r
}\r
- else {\r
- auto_ptr<saml1p::Response> wrapper(response);\r
- for (vector<saml1::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
- if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer())) {\r
- log.error("assertion issued by someone other than AA, rejecting it");\r
- continue;\r
- }\r
- try {\r
- tokval->validate(*a);\r
- }\r
- catch (exception& ex) {\r
- log.error("assertion failed validation check: %s", ex.what());\r
- }\r
- resolve(ctx, *a, attributes);\r
- ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
- }\r
+\r
+ try {\r
+ policy.evaluate(*newtoken);\r
+ if (!policy.isSecure())\r
+ throw SecurityPolicyException("Security of SAML 1.x query result not established.");\r
+ saml1::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL));\r
+ tokval.validateAssertion(*newtoken);\r
+ }\r
+ catch (exception& ex) {\r
+ log.error("assertion failed policy/validation: %s", ex.what());\r
}\r
+ newtoken->detach();\r
+ wrapper.release();\r
+ ctx.getResolvedAssertions().push_back(newtoken);\r
+ resolve(ctx, newtoken, attributes);\r
}\r
\r
void SimpleResolverImpl::query(ResolutionContext& ctx, const NameID& nameid, const vector<const char*>* attributes) const\r
\r
SecurityPolicy policy;\r
shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
+ const PropertySet* policySettings = ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second);\r
+ pair<bool,bool> signedAssertions = policySettings->getBool("signedAssertions");\r
\r
auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);\r
saml2p::StatusResponseType* srt=NULL;\r
return;\r
}\r
\r
- time_t now = time(NULL);\r
- const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();\r
- if (assertions.size()==1) {\r
- auto_ptr<saml2p::Response> wrapper(response);\r
- saml2::Assertion* newtoken = assertions.front();\r
- if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer() ? newtoken->getIssuer()->getName() : NULL)) {\r
- log.error("assertion issued by someone other than AA, rejecting it");\r
- return;\r
- }\r
- try {\r
- tokval->validate(newtoken);\r
- }\r
- catch (exception& ex) {\r
- log.error("assertion failed validation check: %s", ex.what());\r
- }\r
- newtoken->detach();\r
- wrapper.release();\r
- ctx.getResolvedAssertions().push_back(newtoken);\r
- resolve(ctx, newtoken, attributes);\r
+ if (assertions.size()>1)\r
+ log.warn("simple resolver only supports one assertion in the query response");\r
+\r
+ auto_ptr<saml2p::Response> wrapper(response);\r
+ saml2::Assertion* newtoken = assertions.front();\r
+\r
+ if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {\r
+ log.error("assertion unsigned, rejecting it based on signedAssertions policy");\r
+ return;\r
}\r
- else {\r
- auto_ptr<saml2p::Response> wrapper(response);\r
- for (vector<saml2::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
- if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer() ? (*a)->getIssuer()->getName() : NULL)) {\r
- log.error("assertion issued by someone other than AA, rejecting it");\r
- return;\r
- }\r
- try {\r
- tokval->validate(*a);\r
- }\r
- catch (exception& ex) {\r
- log.error("assertion failed validation check: %s", ex.what());\r
- }\r
- resolve(ctx, *a, attributes);\r
- ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
- }\r
+\r
+ try {\r
+ policy.evaluate(*newtoken);\r
+ if (!policy.isSecure())\r
+ throw SecurityPolicyException("Security of SAML 2.0 query result not established.");\r
+ saml2::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL));\r
+ tokval.validateAssertion(*newtoken);\r
+ }\r
+ catch (exception& ex) {\r
+ log.error("assertion failed policy/validation: %s", ex.what());\r
}\r
+ newtoken->detach();\r
+ wrapper.release();\r
+ ctx.getResolvedAssertions().push_back(newtoken);\r
+ resolve(ctx, newtoken, attributes);\r
}\r
\r
void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes) const\r
#include <shibsp/handler/Handler.h>
#include <shibsp/util/DOMPropertySet.h>
+#include <log4cpp/Category.hh>
+#include <xmltooling/XMLObject.h>
namespace shibsp {
* @param remapper optional map of property rename rules for legacy property support
*/
AbstractHandler(
- const xercesc::DOMElement* e,
- xercesc::DOMNodeFilter* filter=NULL,
+ const DOMElement* e,
+ log4cpp::Category& log,
+ DOMNodeFilter* filter=NULL,
const std::map<std::string,std::string>* remapper=NULL
);
+
+ /**
+ * Examines a protocol response message for errors and raises an annotated exception
+ * if an error is found.
+ *
+ * <p>The base class version understands SAML 1.x and SAML 2.0 responses.
+ *
+ * @param response a response message of some known protocol
+ */
+ virtual void checkError(const xmltooling::XMLObject* response) const;
+ /** Logging object. */
+ log4cpp::Category& m_log;
+
public:
virtual ~AbstractHandler() {}
};
--- /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.
+ */
+
+/**
+ * @file shibsp/handler/AssertionConsumerService.h
+ *
+ * Base class for handlers that create sessions by consuming SSO protocol responses.
+ */
+
+#ifndef __shibsp_acshandler_h__
+#define __shibsp_acshandler_h__
+
+#include <shibsp/handler/AbstractHandler.h>
+#include <shibsp/handler/RemotedHandler.h>
+#include <saml/binding/MessageDecoder.h>
+#include <saml/saml2/metadata/Metadata.h>
+
+namespace shibsp {
+
+ class SHIBSP_API ResolutionContext;
+
+#if defined (_MSC_VER)
+ #pragma warning( push )
+ #pragma warning( disable : 4250 )
+#endif
+
+ /**
+ * Base class for handlers that create sessions by consuming SSO protocol responses.
+ */
+ class SHIBSP_API AssertionConsumerService : public AbstractHandler, public RemotedHandler
+ {
+ public:
+ virtual ~AssertionConsumerService();
+
+ std::pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+ void receive(DDF& in, std::ostream& out);
+
+ protected:
+ AssertionConsumerService(const DOMElement* e, log4cpp::Category& log);
+
+ /**
+ * Implement protocol-specific handling of the incoming decoded message.
+ *
+ * <p>The result of implementing the protocol should be an exception or
+ * the key to a newly created session.
+ *
+ * @param application reference to application receiving message
+ * @param httpRequest client request that included message
+ * @param policy the SecurityPolicy in effect, after having evaluated the message
+ * @param settings policy configuration settings in effect
+ * @param xmlObject a protocol-specific message object
+ * @return the key to the newly created session
+ */
+ virtual std::string implementProtocol(
+ const Application& application,
+ const opensaml::HTTPRequest& httpRequest,
+ opensaml::SecurityPolicy& policy,
+ const PropertySet* settings,
+ const xmltooling::XMLObject& xmlObject
+ ) const=0;
+
+ /**
+ * Enforce address checking requirements.
+ *
+ * @param application reference to application receiving message
+ * @param httpRequest client request that initiated session
+ * @param issuedTo address for which security assertion was issued
+ */
+ void checkAddress(
+ const Application& application, const opensaml::HTTPRequest& httpRequest, const char* issuedTo
+ ) const;
+
+ /**
+ * Attempt SSO-initiated attribute resolution using the supplied information.
+ *
+ * <p>The caller must free the returned context handle.
+ *
+ * @param application reference to application receiving message
+ * @param httpRequest client request that initiated session
+ * @param issuer source of SSO tokens
+ * @param nameid identifier of principal
+ * @param tokens tokens to resolve, if any
+ */
+ ResolutionContext* resolveAttributes(
+ const Application& application,
+ const opensaml::HTTPRequest& httpRequest,
+ const opensaml::saml2md::EntityDescriptor* issuer,
+ const opensaml::saml2::NameID& nameid,
+ const std::vector<const opensaml::Assertion*>* tokens=NULL
+ ) const;
+
+ private:
+ std::string processMessage(
+ const Application& application,
+ const opensaml::HTTPRequest& httpRequest,
+ std::string& providerId,
+ std::string& relayState
+ ) const;
+
+ std::pair<bool,long> sendRedirect(
+ SPRequest& request, const char* key, const char* providerId, const char* relayState
+ ) const;
+
+ void maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const;
+
+ opensaml::MessageDecoder* m_decoder;
+ xmltooling::auto_ptr_char m_configNS;
+ xmltooling::QName m_role;
+ };
+
+#if defined (_MSC_VER)
+ #pragma warning( pop )
+#endif
+
+};
+
+#endif /* __shibsp_acshandler_h__ */
*/
virtual std::pair<bool,long> run(SPRequest& request, bool isHandler=true) const=0;
};
+
+ /** Registers Handler implementations. */
+ void SHIBSP_API registerHandlers();
};
#endif /* __shibsp_handler_h__ */
class SHIBSP_API RemotedHandler : public virtual Handler, public Remoted
{
public:
- virtual ~RemotedHandler() {}
+ virtual ~RemotedHandler();
protected:
- RemotedHandler() {}
+ RemotedHandler();
/**
- * Wraps a request by annotating an outgoing data flow with the data needed
+ * Wraps a request by creating an outgoing data flow with the data needed
* to remote the request information.
*
* @param request an SPRequest to remote
- * @param in the dataflow object to annotate
* @param headers array of request headers to copy to remote request
* @param certs true iff client certificates should be available for the remote request
* @return the input dataflow object
*/
- const DDF& wrap(const SPRequest& request, DDF& in, const std::vector<std::string>& headers, bool certs=false) const;
+ DDF wrap(const SPRequest& request, const std::vector<std::string>* headers=NULL, bool certs=false) const;
/**
* Unwraps a response by examining an incoming data flow to determine
* @return a call-specific response object, to be freed by the caller
*/
opensaml::HTTPResponse* getResponse(DDF& out) const;
+
+ /** Message address for remote half. */
+ std::string m_address;
+
+ private:
+ static unsigned int m_counter;
};
};
*/
#include "internal.h"
+#include "exceptions.h"
#include "handler/AbstractHandler.h"
+#include <saml/saml1/core/Protocols.h>
+#include <saml/saml2/core/Protocols.h>
+#include <saml/util/SAMLConstants.h>
+
using namespace shibsp;
+using namespace samlconstants;
+using namespace opensaml;
+using namespace xmltooling;
using namespace xercesc;
using namespace std;
+namespace shibsp {
+ SHIBSP_DLLLOCAL PluginManager<Handler,const DOMElement*>::Factory SAML1ConsumerFactory;
+};
+
+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);
+}
+
AbstractHandler::AbstractHandler(
- const DOMElement* e, DOMNodeFilter* filter, const map<string,string>* remapper
- ) {
- load(e,log4cpp::Category::getInstance(SHIBSP_LOGCAT".Handler"),filter,remapper);
+ const DOMElement* e, log4cpp::Category& log, DOMNodeFilter* filter, const map<string,string>* remapper
+ ) : m_log(log) {
+ load(e,log,filter,remapper);
+}
+
+void AbstractHandler::checkError(const XMLObject* response) const
+{
+ const saml2p::StatusResponseType* r2 = dynamic_cast<const saml2p::StatusResponseType*>(response);
+ if (r2) {
+ const saml2p::Status* status = r2->getStatus();
+ if (status) {
+ const saml2p::StatusCode* sc = status->getStatusCode();
+ const XMLCh* code = sc ? sc->getValue() : NULL;
+ if (code && !XMLString::equals(code,saml2p::StatusCode::SUCCESS)) {
+ FatalProfileException ex("SAML Response message contained an error.");
+ auto_ptr_char c1(code);
+ ex.addProperty("code", c1.get());
+ if (sc->getStatusCode()) {
+ code = sc->getStatusCode()->getValue();
+ auto_ptr_char c2(code);
+ ex.addProperty("code2", c2.get());
+ }
+ if (status->getStatusMessage()) {
+ auto_ptr_char msg(status->getStatusMessage()->getMessage());
+ ex.addProperty("message", msg.get());
+ }
+ }
+ }
+ }
+
+ const saml1p::Response* r1 = dynamic_cast<const saml1p::Response*>(response);
+ if (r1) {
+ const saml1p::Status* status = r1->getStatus();
+ if (status) {
+ const saml1p::StatusCode* sc = status->getStatusCode();
+ const QName* code = sc ? sc->getValue() : NULL;
+ if (code && *code != saml1p::StatusCode::SUCCESS) {
+ FatalProfileException ex("SAML Response message contained an error.");
+ ex.addProperty("code", code->toString().c_str());
+ if (sc->getStatusCode()) {
+ code = sc->getStatusCode()->getValue();
+ if (code)
+ ex.addProperty("code2", code->toString().c_str());
+ }
+ if (status->getStatusMessage()) {
+ auto_ptr_char msg(status->getStatusMessage()->getMessage());
+ ex.addProperty("message", msg.get());
+ }
+ }
+ }
+ }
}
--- /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.
+ */
+
+/**
+ * AssertionConsumerService.cpp
+ *
+ * Base class for handlers that create sessions by consuming SSO protocol responses.
+ */
+
+#include "internal.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "attribute/resolver/AttributeResolver.h"
+#include "attribute/resolver/ResolutionContext.h"
+#include "handler/AssertionConsumerService.h"
+#include "util/SPConstants.h"
+
+#include <saml/SAMLConfig.h>
+#include <saml/binding/URLEncoder.h>
+#include <saml/saml1/core/Assertions.h>
+#include <saml/util/CommonDomainCookie.h>
+
+using namespace shibspconstants;
+using namespace samlconstants;
+using namespace shibsp;
+using namespace opensaml;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+AssertionConsumerService::AssertionConsumerService(const DOMElement* e, Category& log)
+ : AbstractHandler(e, log), m_configNS(SHIB2SPCONFIG_NS),
+ m_role(samlconstants::SAML20MD_NS, opensaml::saml2md::IDPSSODescriptor::LOCAL_NAME)
+{
+ if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
+ m_decoder = SAMLConfig::getConfig().MessageDecoderManager.newPlugin(getString("Binding").second,e);
+}
+
+AssertionConsumerService::~AssertionConsumerService()
+{
+ delete m_decoder;
+}
+
+pair<bool,long> AssertionConsumerService::run(SPRequest& request, bool isHandler) const
+{
+ SPConfig& conf = SPConfig::getConfig();
+ if (conf.isEnabled(SPConfig::OutOfProcess)) {
+ string relayState, providerId;
+ string key = processMessage(request.getApplication(), request, providerId, relayState);
+ return sendRedirect(request, key.c_str(), providerId.c_str(), relayState.c_str());
+ }
+ else {
+ DDF in = wrap(request);
+ DDFJanitor jin(in);
+ in.addmember("application_id").string(request.getApplication().getId());
+ DDF out=request.getServiceProvider().getListenerService()->send(in);
+ DDFJanitor jout(out);
+ if (!out["key"].isstring())
+ throw FatalProfileException("Remote processing of SAML 1.x Browser profile did not return a usable session key.");
+ return sendRedirect(request, out["key"].string(), out["provider_id"].string(), out["RelayState"].string());
+ }
+}
+
+void AssertionConsumerService::receive(DDF& in, ostream& out)
+{
+ // Find application.
+ const char* aid=in["application_id"].string();
+ const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
+ if (!app) {
+ // Something's horribly wrong.
+ m_log.error("couldn't find application (%s) for new session", aid ? aid : "(missing)");
+ throw ConfigurationException("Unable to locate application for new session, deleted?");
+ }
+
+ // Unpack the request.
+ auto_ptr<HTTPRequest> http(getRequest(in));
+
+ // Do the work.
+ string relayState, providerId;
+ string key = processMessage(*app, *http.get(), providerId, 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 (!relayState.empty())
+ ret.addmember("RelayState").string(relayState.c_str());
+ out << ret;
+}
+
+string AssertionConsumerService::processMessage(
+ const Application& application, const HTTPRequest& httpRequest, string& providerId, string& relayState
+ ) const
+{
+ // Locate policy key.
+ pair<bool,const char*> policyId = getString("policyId", m_configNS.get()); // namespace-qualified if inside handler element
+ if (!policyId.first)
+ policyId = application.getString("policyId"); // unqualified in Application(s) element
+
+ // Access policy properties.
+ const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId.second);
+ pair<bool,bool> validate = settings->getBool("validate");
+
+ // Lock metadata for use by policy.
+ Locker metadataLocker(application.getMetadataProvider());
+
+ // Create the policy.
+ SecurityPolicy policy(
+ application.getServiceProvider().getPolicyRules(policyId.second),
+ application.getMetadataProvider(),
+ &m_role,
+ application.getTrustEngine(),
+ validate.first && validate.second
+ );
+
+ // Decode the message and process it in a protocol-specific way.
+ auto_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, policy));
+ string key = implementProtocol(application, httpRequest, policy, settings, *msg.get());
+
+ auto_ptr_char issuer(policy.getIssuer() ? policy.getIssuer()->getName() : NULL);
+ if (issuer.get())
+ providerId = issuer.get();
+
+ return key;
+}
+
+pair<bool,long> AssertionConsumerService::sendRedirect(
+ SPRequest& request, const char* key, const char* providerId, const char* relayState
+ ) const
+{
+ string s,k(key);
+
+ if (relayState && !strcmp(relayState,"default")) {
+ pair<bool,const char*> homeURL=request.getApplication().getString("homeURL");
+ relayState=homeURL.first ? homeURL.second : "/";
+ }
+ else if (!relayState || !strcmp(relayState,"cookie")) {
+ // Pull the value from the "relay state" cookie.
+ pair<string,const char*> relay_cookie = request.getApplication().getCookieNameProps("_shibstate_");
+ relayState = request.getCookie(relay_cookie.first.c_str());
+ if (!relayState || !*relayState) {
+ // No apparent relay state value to use, so fall back on the default.
+ pair<bool,const char*> homeURL=request.getApplication().getString("homeURL");
+ relayState=homeURL.first ? homeURL.second : "/";
+ }
+ else {
+ char* rscopy=strdup(relayState);
+ SAMLConfig::getConfig().getURLEncoder()->decode(rscopy);
+ s=rscopy;
+ free(rscopy);
+ relayState=s.c_str();
+ }
+ request.setCookie(relay_cookie.first.c_str(),relay_cookie.second);
+ }
+
+ // We've got a good session, so set the session cookie.
+ pair<string,const char*> shib_cookie=request.getApplication().getCookieNameProps("_shibsession_");
+ k += shib_cookie.second;
+ request.setCookie(shib_cookie.first.c_str(), k.c_str());
+
+ // History cookie.
+ maintainHistory(request, providerId, shib_cookie.second);
+
+ // Now redirect to the target.
+ return make_pair(true, request.sendRedirect(relayState));
+}
+
+void AssertionConsumerService::checkAddress(
+ const Application& application, const HTTPRequest& httpRequest, const char* issuedTo
+ ) const
+{
+ const PropertySet* props=application.getPropertySet("Sessions");
+ pair<bool,bool> checkAddress = props ? props->getBool("checkAddress") : make_pair(false,true);
+ if (!checkAddress.first)
+ checkAddress.second=true;
+
+ if (checkAddress.second) {
+ m_log.debug("checking client address");
+ if (httpRequest.getRemoteAddr() != issuedTo) {
+ throw FatalProfileException(
+ "Your client's current address ($client_addr) differs from the one used when you authenticated "
+ "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
+ "Please contact your local support staff or help desk for assistance.",
+ namedparams(1,"client_addr",httpRequest.getRemoteAddr().c_str())
+ );
+ }
+ }
+}
+
+ResolutionContext* AssertionConsumerService::resolveAttributes(
+ const Application& application,
+ const HTTPRequest& httpRequest,
+ const saml2md::EntityDescriptor* issuer,
+ const saml2::NameID& nameid,
+ const vector<const Assertion*>* tokens
+ ) const
+{
+ AttributeResolver* resolver = application.getAttributeResolver();
+ if (!resolver) {
+ m_log.info("no AttributeResolver available, skipping resolution");
+ return NULL;
+ }
+
+ try {
+ m_log.debug("resolving attributes...");
+ auto_ptr<ResolutionContext> ctx(
+ resolver->createResolutionContext(application, httpRequest.getRemoteAddr().c_str(), issuer, nameid, tokens)
+ );
+ resolver->resolveAttributes(*ctx.get());
+ return ctx.release();
+ }
+ catch (exception& ex) {
+ m_log.error("attribute resolution failed: %s", ex.what());
+ }
+
+ return NULL;
+}
+
+void AssertionConsumerService::maintainHistory(SPRequest& request, const char* providerId, const char* cookieProps) const
+{
+ if (!providerId)
+ return;
+
+ const PropertySet* sessionProps=request.getApplication().getPropertySet("Sessions");
+ pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
+ if (!idpHistory.first || idpHistory.second) {
+ // Set an IdP history cookie locally (essentially just a CDC).
+ CommonDomainCookie cdc(request.getCookie(CommonDomainCookie::CDCName));
+
+ // 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;
+ request.setCookie(CommonDomainCookie::CDCName, c.c_str());
+ }
+ else {
+ time_t now=time(NULL) + (days.second * 24 * 60 * 60);
+#ifdef HAVE_GMTIME_R
+ struct tm res;
+ struct tm* ptime=gmtime_r(&now,&res);
+#else
+ struct tm* ptime=gmtime(&now);
+#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;
+ request.setCookie(CommonDomainCookie::CDCName, c.c_str());
+ }
+ }
+}
*/
#include "internal.h"
+#include "ServiceProvider.h"
#include "handler/RemotedHandler.h"
#include <algorithm>
}
-const DDF& RemotedHandler::wrap(const SPRequest& request, DDF& in, const vector<string>& headers, bool certs) const
+unsigned int RemotedHandler::m_counter = 0;
+
+RemotedHandler::RemotedHandler()
+{
+ m_address += ('A' + (m_counter++));
+ m_address += "::run::RemotedHandler";
+
+ SPConfig& conf = SPConfig::getConfig();
+ if (conf.isEnabled(SPConfig::OutOfProcess)) {
+ ListenerService* listener = conf.getServiceProvider()->getListenerService(false);
+ if (listener)
+ listener->regListener(m_address.c_str(),this);
+ else
+ Category::getInstance(SHIBSP_LOGCAT".Handler").info("no ListenerService available, handler remoting disabled");
+ }
+}
+
+RemotedHandler::~RemotedHandler()
{
- if (!in.isstruct())
- in.structure();
+ SPConfig& conf = SPConfig::getConfig();
+ ListenerService* listener=conf.getServiceProvider()->getListenerService(false);
+ if (listener && conf.isEnabled(SPConfig::OutOfProcess))
+ listener->unregListener(m_address.c_str(),this);
+ m_counter--;
+}
+
+DDF RemotedHandler::wrap(const SPRequest& request, const vector<string>* headers, bool certs) const
+{
+ DDF in = DDF(m_address.c_str()).structure();
in.addmember("scheme").string(request.getScheme());
in.addmember("hostname").string(request.getHostname());
in.addmember("port").integer(request.getPort());
in.addmember("url").string(request.getRequestURL());
in.addmember("query").string(request.getQueryString());
- string hdr;
- DDF hin = in.addmember("headers").structure();
- for (vector<string>::const_iterator h = headers.begin(); h!=headers.end(); ++h) {
- hdr = request.getHeader(h->c_str());
- if (!hdr.empty())
- hin.addmember(h->c_str()).string(hdr.c_str());
+ if (headers) {
+ string hdr;
+ DDF hin = in.addmember("headers").structure();
+ for (vector<string>::const_iterator h = headers->begin(); h!=headers->end(); ++h) {
+ hdr = request.getHeader(h->c_str());
+ if (!hdr.empty())
+ hin.addmember(h->c_str()).string(hdr.c_str());
+ }
}
if (certs) {
const vector<XSECCryptoX509*>& xvec = request.getClientCertificates();
if (!xvec.empty()) {
- hin = in.addmember("certificates").list();
+ DDF clist = in.addmember("certificates").list();
for (vector<XSECCryptoX509*>::const_iterator x = xvec.begin(); x!=xvec.end(); ++x) {
DDF x509 = DDF(NULL).string((*x)->getDEREncodingSB().rawCharBuffer());
- hin.add(x509);
+ clist.add(x509);
}
}
}
--- /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.
+ */
+
+/**
+ * SAML1Consumer.cpp
+ *
+ * SAML 1.x assertion consumer service
+ */
+
+#include "internal.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "SessionCache.h"
+#include "attribute/resolver/ResolutionContext.h"
+#include "handler/AssertionConsumerService.h"
+
+#include <saml/saml1/core/Assertions.h>
+#include <saml/saml1/core/Protocols.h>
+#include <saml/saml1/profile/BrowserSSOProfileValidator.h>
+#include <saml/saml2/metadata/Metadata.h>
+
+using namespace shibsp;
+using namespace opensaml::saml1;
+using namespace opensaml::saml1p;
+using namespace opensaml;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+using saml2::NameID;
+using saml2::NameIDBuilder;
+using saml2md::EntityDescriptor;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+ #pragma warning( push )
+ #pragma warning( disable : 4250 )
+#endif
+
+ class SHIBSP_DLLLOCAL SAML1Consumer : public AssertionConsumerService
+ {
+ public:
+ SAML1Consumer(const DOMElement* e) : AssertionConsumerService(e, Category::getInstance(SHIBSP_LOGCAT".SAML1")) {}
+ virtual ~SAML1Consumer() {}
+
+ private:
+ string implementProtocol(
+ const Application& application,
+ const HTTPRequest& httpRequest,
+ SecurityPolicy& policy,
+ const PropertySet* settings,
+ const XMLObject& xmlObject
+ ) const;
+ };
+
+#if defined (_MSC_VER)
+ #pragma warning( pop )
+#endif
+
+ Handler* SHIBSP_DLLLOCAL SAML1ConsumerFactory(const DOMElement* const & e)
+ {
+ return new SAML1Consumer(e);
+ }
+
+};
+
+string SAML1Consumer::implementProtocol(
+ const Application& application,
+ const HTTPRequest& httpRequest,
+ SecurityPolicy& policy,
+ const PropertySet* settings,
+ const XMLObject& xmlObject
+ ) const
+{
+ // Implementation of SAML 1.x SSO profile(s).
+ m_log.debug("processing message against SAML 1.x SSO profile");
+
+ // With the binding aspects now moved out to the MessageDecoder,
+ // the focus here is on the assertion content. For SAML 1.x,
+ // all the security comes from the protocol layer, and signing
+ // the assertion isn't sufficient. So we can check the policy
+ // object now and bail if it's not a secure message.
+ if (!policy.isSecure())
+ throw SecurityPolicyException("Security of SAML 1.x SSO response not established.");
+
+ // Check for errors...this will throw if it's not a successful message.
+ checkError(&xmlObject);
+
+ const Response* response = dynamic_cast<const Response*>(&xmlObject);
+ if (!response)
+ throw FatalProfileException("Incoming message was not a samlp:Response.");
+
+ const vector<saml1::Assertion*>& assertions = response->getAssertions();
+ if (assertions.empty())
+ throw FatalProfileException("Incoming message contained no SAML assertions.");
+
+ // Maintain list of "legit" tokens to feed to SP subsystems.
+ const AuthenticationStatement* ssoStatement=NULL;
+ vector<const opensaml::Assertion*> tokens;
+
+ // Profile validator.
+ time_t now = time(NULL);
+ BrowserSSOProfileValidator ssoValidator(application.getAudiences(), now);
+
+ // With this flag on, we ignore any unsigned assertions.
+ pair<bool,bool> flag = settings->getBool("signedAssertions");
+ for (vector<saml1::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {
+ // Skip unsigned assertion?
+ if (!(*a)->getSignature() && flag.first && flag.second) {
+ m_log.warn("found unsigned assertion in SAML response, ignoring it per signedAssertions policy");
+ continue;
+ }
+
+ try {
+ // Run the policy over the assertion. Handles issuer consistency, replay, freshness,
+ // and signature verification, assuming the relevant rules are configured.
+ policy.evaluate(*(*a));
+
+ // Now do profile and core semantic validation to ensure we can use it for SSO.
+ ssoValidator.validateAssertion(*(*a));
+
+ // Track it as a valid token.
+ tokens.push_back(*a);
+
+ // Save off the first valid SSO statement.
+ if (!ssoStatement && !(*a)->getAuthenticationStatements().empty())
+ ssoStatement = (*a)->getAuthenticationStatements().front();
+ }
+ catch (exception& ex) {
+ m_log.warn("profile validation error in assertion: %s", ex.what());
+ }
+ }
+
+ if (!ssoStatement)
+ throw FatalProfileException("A valid authentication statement was not found in the incoming message.");
+
+ // Address checking.
+ SubjectLocality* locality = ssoStatement->getSubjectLocality();
+ if (locality && locality->getIPAddress()) {
+ auto_ptr_char ip(locality->getIPAddress());
+ checkAddress(application, httpRequest, ip.get());
+ }
+
+ m_log.debug("SSO profile processing completed successfully");
+
+ // We've successfully "accepted" at least one SSO token, along with any additional valid tokens.
+ // To complete processing, we need to resolve attributes and then create the session.
+
+ // First, normalize the SAML 1.x NameIdentifier...
+ auto_ptr<NameID> nameid(NameIDBuilder::buildNameID());
+ NameIdentifier* n = ssoStatement->getSubject()->getNameIdentifier();
+ if (n) {
+ nameid->setName(n->getName());
+ nameid->setFormat(n->getFormat());
+ nameid->setNameQualifier(n->getNameQualifier());
+ }
+
+ const EntityDescriptor* issuerMetadata = dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent());
+ auto_ptr<ResolutionContext> ctx(
+ resolveAttributes(application, httpRequest, issuerMetadata, *nameid.get(), &tokens)
+ );
+
+ // Copy over any new tokens, but leave them in the context for cleanup.
+ tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
+
+ // Now we have to extract the authentication details for session setup.
+
+ // Session expiration for SAML 1.x is purely SP-driven, and the method is mapped to a ctx class.
+ const PropertySet* sessionProps = application.getPropertySet("Sessions");
+ pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : make_pair(true,28800);
+ if (!lifetime.first)
+ lifetime.second = 28800;
+ auto_ptr_char authnInstant(
+ ssoStatement->getAuthenticationInstant() ? ssoStatement->getAuthenticationInstant()->getRawData() : NULL
+ );
+ auto_ptr_char authnMethod(ssoStatement->getAuthenticationMethod());
+
+ vector<shibsp::Attribute*>& attrs = ctx->getResolvedAttributes();
+ string key = application.getServiceProvider().getSessionCache()->insert(
+ lifetime.second ? now + lifetime.second : 0,
+ application,
+ httpRequest.getRemoteAddr().c_str(),
+ issuerMetadata,
+ *nameid.get(),
+ authnInstant.get(),
+ NULL,
+ authnMethod.get(),
+ NULL,
+ &tokens,
+ &attrs
+ );
+ attrs.clear(); // Attributes are owned by cache now.
+ return key;
+}
#pragma warning( disable : 4250 )\r
#endif\r
\r
- class SHIBSP_DLLLOCAL TokenValidator : public Validator\r
- {\r
- public:\r
- TokenValidator(const Application& app, time_t ts=0, const RoleDescriptor* role=NULL) : m_app(app), m_ts(ts), m_role(role) {}\r
- void validate(const XMLObject*) const;\r
-\r
- private:\r
- const Application& m_app;\r
- time_t m_ts;\r
- const RoleDescriptor* m_role;\r
- };\r
-\r
static vector<const Handler*> g_noHandlers;\r
\r
// Application configuration wrapper\r
const vector<const XMLCh*>& getAudiences() const {\r
return (m_audiences.empty() && m_base) ? m_base->getAudiences() : m_audiences;\r
}\r
- Validator* getTokenValidator(time_t ts=0, const saml2md::RoleDescriptor* role=NULL) const {\r
- return new TokenValidator(*this, ts, role);\r
- }\r
\r
// Provides filter to exclude special config elements.\r
short acceptNode(const DOMNode* node) const;\r
}\r
};\r
\r
-void TokenValidator::validate(const XMLObject* xmlObject) const\r
-{\r
-#ifdef _DEBUG\r
- xmltooling::NDC ndc("validate");\r
-#endif\r
- Category& log=Category::getInstance(SHIBSP_LOGCAT".Application");\r
-\r
- const opensaml::Assertion* root = NULL;\r
- const saml2::Assertion* token2 = dynamic_cast<const saml2::Assertion*>(xmlObject);\r
- if (token2) {\r
- const saml2::Conditions* conds = token2->getConditions();\r
- // First verify the time conditions, using the specified timestamp, if non-zero.\r
- if (m_ts>0 && conds) {\r
- unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs;\r
- time_t t=conds->getNotBeforeEpoch();\r
- if (m_ts+skew < t)\r
- throw ValidationException("Assertion is not yet valid.");\r
- t=conds->getNotOnOrAfterEpoch();\r
- if (t <= m_ts-skew)\r
- throw ValidationException("Assertion is no longer valid.");\r
- }\r
-\r
- // Now we process conditions. Only audience restrictions at the moment.\r
- const vector<saml2::Condition*>& convec = conds->getConditions();\r
- for (vector<saml2::Condition*>::const_iterator c = convec.begin(); c!=convec.end(); ++c) {\r
- const saml2::AudienceRestriction* ac=dynamic_cast<const saml2::AudienceRestriction*>(*c);\r
- if (!ac) {\r
- log.error("unrecognized Condition in assertion (%s)",\r
- (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str());\r
- throw ValidationException("Assertion contains an unrecognized condition.");\r
- }\r
-\r
- bool found = false;\r
- const vector<saml2::Audience*>& auds1 = ac->getAudiences();\r
- const vector<const XMLCh*>& auds2 = m_app.getAudiences();\r
- for (vector<saml2::Audience*>::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) {\r
- for (vector<const XMLCh*>::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) {\r
- found = XMLString::equals((*a)->getAudienceURI(), *a2);\r
- }\r
- }\r
-\r
- if (!found) {\r
- ostringstream os;\r
- os << *ac;\r
- log.error("unacceptable AudienceRestriction in assertion (%s)", os.str().c_str());\r
- throw ValidationException("Assertion contains an unacceptable AudienceRestriction.");\r
- }\r
- }\r
-\r
- root = token2;\r
- }\r
- else {\r
- const saml1::Assertion* token1 = dynamic_cast<const saml1::Assertion*>(xmlObject);\r
- if (token1) {\r
- const saml1::Conditions* conds = token1->getConditions();\r
- // First verify the time conditions, using the specified timestamp, if non-zero.\r
- if (m_ts>0 && conds) {\r
- unsigned int skew = XMLToolingConfig::getConfig().clock_skew_secs;\r
- time_t t=conds->getNotBeforeEpoch();\r
- if (m_ts+skew < t)\r
- throw ValidationException("Assertion is not yet valid.");\r
- t=conds->getNotOnOrAfterEpoch();\r
- if (t <= m_ts-skew)\r
- throw ValidationException("Assertion is no longer valid.");\r
- }\r
-\r
- // Now we process conditions. Only audience restrictions at the moment.\r
- const vector<saml1::Condition*>& convec = conds->getConditions();\r
- for (vector<saml1::Condition*>::const_iterator c = convec.begin(); c!=convec.end(); ++c) {\r
- const saml1::AudienceRestrictionCondition* ac=dynamic_cast<const saml1::AudienceRestrictionCondition*>(*c);\r
- if (!ac) {\r
- log.error("unrecognized Condition in assertion (%s)",\r
- (*c)->getSchemaType() ? (*c)->getSchemaType()->toString().c_str() : (*c)->getElementQName().toString().c_str());\r
- throw ValidationException("Assertion contains an unrecognized condition.");\r
- }\r
-\r
- bool found = false;\r
- const vector<saml1::Audience*>& auds1 = ac->getAudiences();\r
- const vector<const XMLCh*>& auds2 = m_app.getAudiences();\r
- for (vector<saml1::Audience*>::const_iterator a = auds1.begin(); !found && a!=auds1.end(); ++a) {\r
- for (vector<const XMLCh*>::const_iterator a2 = auds2.begin(); !found && a2!=auds2.end(); ++a2) {\r
- found = XMLString::equals((*a)->getAudienceURI(), *a2);\r
- }\r
- }\r
-\r
- if (!found) {\r
- ostringstream os;\r
- os << *ac;\r
- log.error("unacceptable AudienceRestrictionCondition in assertion (%s)", os.str().c_str());\r
- throw ValidationException("Assertion contains an unacceptable AudienceRestrictionCondition.");\r
- }\r
- }\r
-\r
- root = token1;\r
- }\r
- else {\r
- throw ValidationException("Unknown object type passed to token validator.");\r
- }\r
- }\r
-\r
- if (!m_role || !m_app.getTrustEngine()) {\r
- log.warn("no issuer role or TrustEngine provided, so no signature validation performed");\r
- return;\r
- }\r
-\r
- const PropertySet* policy=m_app.getServiceProvider().getPolicySettings(m_app.getString("policyId").second);\r
- pair<bool,bool> signedAssertions=policy ? policy->getBool("signedAssertions") : make_pair(false,false);\r
-\r
- if (root->getSignature()) {\r
- if (!m_app.getTrustEngine()->validate(*(root->getSignature()),*m_role))\r
- throw ValidationException("Assertion signature did not validate.");\r
- }\r
- else if (signedAssertions.first && signedAssertions.second)\r
- throw ValidationException("Assertion was unsigned, violating policy.");\r
-}\r
-\r
XMLApplication::XMLApplication(\r
const ServiceProvider* sp,\r
const DOMElement* e,\r
>\r
</File>\r
<File\r
+ RelativePath=".\handler\impl\AssertionConsumerService.cpp"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath=".\handler\impl\RemotedHandler.cpp"\r
>\r
</File>\r
+ <File\r
+ RelativePath=".\handler\impl\SAML1Consumer.cpp"\r
+ >\r
+ </File>\r
</Filter>\r
</Filter>\r
</Filter>\r
>\r
</File>\r
<File\r
+ RelativePath=".\handler\AssertionConsumerService.h"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath=".\handler\Handler.h"\r
>\r
</File>\r