#include <algorithm>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
-#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/tuple/tuple.hpp>
# include <saml/saml1/core/Assertions.h>
# include <saml/saml2/core/Assertions.h>
# include <saml/saml2/binding/SAML2ArtifactType0004.h>
+# include <saml/saml2/metadata/EntityMatcher.h>
# include <saml/saml2/metadata/Metadata.h>
# include <saml/saml2/metadata/MetadataProvider.h>
# include <saml/util/SAMLConstants.h>
}
const PropertySet* getRelyingParty(const EntityDescriptor* provider) const;
const PropertySet* getRelyingParty(const XMLCh* entityID) const;
+
const vector<const XMLCh*>* getAudiences() const {
return (m_audiences.empty() && m_base) ? m_base->getAudiences() : &m_audiences;
}
vector<const XMLCh*> m_audiences;
// RelyingParty properties
- map< xstring,boost::shared_ptr<PropertySet> > m_partyMap;
+ map< xstring,boost::shared_ptr<PropertySet> > m_partyMap; // name-based matching
+ vector< pair< boost::shared_ptr<EntityMatcher>,boost::shared_ptr<PropertySet> > > m_partyVec; // plugin-based matching
#endif
vector<string> m_remoteUsers,m_frontLogout,m_backLogout;
#ifndef SHIBSP_LITE
scoped_ptr<TransactionLog> m_tranLog;
scoped_ptr<SecurityPolicyProvider> m_policy;
- vector< tuple<string,string,string> > m_transportOptions;
+ vector< boost::tuple<string,string,string> > m_transportOptions;
#endif
scoped_ptr<RequestMapper> m_requestMapper;
map< string,boost::shared_ptr<Application> > m_appmap;
#endif
{
public:
- XMLConfig(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".Config")) {}
+ XMLConfig(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT ".Config")) {}
void init() {
background_load();
bool setTransportOptions(SOAPTransport& transport) const {
bool ret = true;
- for (vector< tuple<string,string,string> >::const_iterator opt = m_impl->m_transportOptions.begin();
+ for (vector< boost::tuple<string,string,string> >::const_iterator opt = m_impl->m_transportOptions.begin();
opt != m_impl->m_transportOptions.end(); ++opt) {
if (!transport.setProviderOption(opt->get<0>().c_str(), opt->get<1>().c_str(), opt->get<2>().c_str())) {
m_log.error("failed to set SOAPTransport option (%s)", opt->get<1>().c_str());
static const XMLCh _option[] = UNICODE_LITERAL_6(o,p,t,i,o,n);
static const XMLCh OutOfProcess[] = UNICODE_LITERAL_12(O,u,t,O,f,P,r,o,c,e,s,s);
static const XMLCh _path[] = UNICODE_LITERAL_4(p,a,t,h);
+ static const XMLCh _policyId[] = UNICODE_LITERAL_8(p,o,l,i,c,y,I,d);
static const XMLCh _ProtocolProvider[] = UNICODE_LITERAL_16(P,r,o,t,o,c,o,l,P,r,o,v,i,d,e,r);
static const XMLCh _provider[] = UNICODE_LITERAL_8(p,r,o,v,i,d,e,r);
static const XMLCh RelyingParty[] = UNICODE_LITERAL_12(R,e,l,y,i,n,g,P,a,r,t,y);
#ifdef _DEBUG
xmltooling::NDC ndc("XMLApplication");
#endif
- Category& log = Category::getInstance(SHIBSP_LOGCAT".Application");
+ Category& log = Category::getInstance(SHIBSP_LOGCAT ".Application");
// First load any property sets.
map<string,string> remapper;
// to ensure we get only our Sessions element.
const PropertySet* sessionProps = getPropertySet("Sessions");
if (sessionProps) {
- pair<bool,const char*> redirectLimit = sessionProps->getString("redirectLimit");
- if (redirectLimit.first) {
- if (!strcmp(redirectLimit.second, "none"))
+ pair<bool,const char*> prop = sessionProps->getString("redirectLimit");
+ if (prop.first) {
+ if (!strcmp(prop.second, "none"))
m_redirectLimit = REDIRECT_LIMIT_NONE;
- else if (!strcmp(redirectLimit.second, "exact"))
+ else if (!strcmp(prop.second, "exact"))
m_redirectLimit = REDIRECT_LIMIT_EXACT;
- else if (!strcmp(redirectLimit.second, "host"))
+ else if (!strcmp(prop.second, "host"))
m_redirectLimit = REDIRECT_LIMIT_HOST;
else {
- if (!strcmp(redirectLimit.second, "exact+whitelist"))
+ if (!strcmp(prop.second, "exact+whitelist"))
m_redirectLimit = REDIRECT_LIMIT_EXACT_WHITELIST;
- else if (!strcmp(redirectLimit.second, "exact+host"))
+ else if (!strcmp(prop.second, "host+whitelist"))
m_redirectLimit = REDIRECT_LIMIT_HOST_WHITELIST;
- else if (!strcmp(redirectLimit.second, "exact+host"))
+ else if (!strcmp(prop.second, "whitelist"))
m_redirectLimit = REDIRECT_LIMIT_WHITELIST;
else
- throw ConfigurationException("Unrecognized redirectLimit setting ($1)", params(1, redirectLimit.second));
- redirectLimit = sessionProps->getString("redirectWhitelist");
- if (redirectLimit.first) {
- string dup(redirectLimit.second);
+ throw ConfigurationException("Unrecognized redirectLimit setting ($1)", params(1, prop.second));
+ prop = sessionProps->getString("redirectWhitelist");
+ if (prop.first) {
+ string dup(prop.second);
+ trim(dup);
split(m_redirectWhitelist, dup, is_space(), algorithm::token_compress_on);
}
}
else {
m_redirectLimit = base ? REDIRECT_LIMIT_INHERIT : REDIRECT_LIMIT_NONE;
}
+
+ // Audit some additional settings for logging purposes.
+ prop = sessionProps->getString("cookieProps");
+ if (!prop.first) {
+ log.warn("empty/missing cookieProps setting, set to \"https\" for SSL/TLS-only usage");
+ }
+ else if (!strcmp(prop.second, "http")) {
+ log.warn("insecure cookieProps setting, set to \"https\" for SSL/TLS-only usage");
+ }
+ else if (strcmp(prop.second, "https")) {
+ if (!strstr(prop.second, ";secure") && !strstr(prop.second, "; secure"))
+ log.warn("custom cookieProps setting should include \"; secure\" for SSL/TLS-only usage");
+ else if (!strstr(prop.second, ";HttpOnly") && !strstr(prop.second, "; HttpOnly"))
+ log.warn("custom cookieProps setting should include \"; HttpOnly\", site is vulnerable to client-side cookie theft");
+ }
+
+ pair<bool,bool> handlerSSL = sessionProps->getBool("handlerSSL");
+ if (handlerSSL.first && !handlerSSL.second)
+ log.warn("handlerSSL should be enabled for SSL/TLS-enabled web sites");
}
else {
m_redirectLimit = base ? REDIRECT_LIMIT_INHERIT : REDIRECT_LIMIT_NONE;
if (m_metadata)
m_metadata->init();
else if (!m_base)
- log.crit("no MetadataProvider available, configuration is probably unusable");
+ log.warn("no MetadataProvider available, configure at least one for standard SSO usage");
}
catch (std::exception& ex) {
log.crit("error initializing MetadataProvider: %s", ex.what());
rp->setParent(this);
m_partyMap[child->getAttributeNS(nullptr, saml2::Attribute::NAME_ATTRIB_NAME)] = rp;
}
+ else if (child->hasAttributeNS(nullptr, _type)) {
+ string emtype(XMLHelper::getAttrString(child, nullptr, _type));
+ boost::shared_ptr<EntityMatcher> em(SAMLConfig::getConfig().EntityMatcherManager.newPlugin(emtype, child));
+ boost::shared_ptr<DOMPropertySet> rp(new DOMPropertySet());
+ rp->load(child, nullptr, this);
+ rp->setParent(this);
+ m_partyVec.push_back(make_pair(em, rp));
+ }
child = XMLHelper::getNextSiblingElement(child, RelyingParty);
}
- if (base && m_partyMap.empty() && !base->m_partyMap.empty()) {
+ if (base && m_partyMap.empty() && m_partyVec.empty() && (!base->m_partyMap.empty() || !base->m_partyVec.empty())) {
// For inheritance of RPs to work, we have to pull them in to the override by cloning the DOM.
child = XMLHelper::getFirstChildElement(base->getElement(), RelyingParty);
while (child) {
rp->setParent(this);
m_partyMap[rpclone->getAttributeNS(nullptr, saml2::Attribute::NAME_ATTRIB_NAME)] = rp;
}
+ else if (child->hasAttributeNS(nullptr, _type)) {
+ DOMElement* rpclone = static_cast<DOMElement*>(child->cloneNode(true));
+ string emtype(XMLHelper::getAttrString(rpclone, nullptr, _type));
+ boost::shared_ptr<EntityMatcher> em(SAMLConfig::getConfig().EntityMatcherManager.newPlugin(emtype, rpclone));
+ boost::shared_ptr<DOMPropertySet> rp(new DOMPropertySet());
+ rp->load(rpclone, nullptr, this);
+ rp->setParent(this);
+ m_partyVec.push_back(make_pair(em, rp));
+ }
child = XMLHelper::getNextSiblingElement(child, RelyingParty);
}
}
pair<bool,const char*> attributes = getString("REMOTE_USER");
if (attributes.first) {
string dup(attributes.second);
+ trim(dup);
split(m_remoteUsers, dup, is_space(), algorithm::token_compress_on);
}
}
string dup(attributes.second);
+ trim(dup);
vector<string> headerNames;
split(headerNames, dup, is_space(), algorithm::token_compress_on);
for (vector<string>::const_iterator h = headerNames.begin(); h != headerNames.end(); ++h) {
{
if (!e->hasChildNodes())
return;
+ DOMNamedNodeMap* ssoprops = e->getAttributes();
+ XMLSize_t ssopropslen = ssoprops ? ssoprops->getLength() : 0;
SPConfig& conf = SPConfig::getConfig();
+ int index = 0; // track ACS indexes globally across all protocols
+
// Tokenize the protocol list inside the element.
XMLStringTokenizer prottokens(e->getTextContent());
while (prottokens.hasMoreTokens()) {
const vector<const PropertySet*>& bindings = pp.getBindings(prot.get(), "SSO");
if (!bindings.empty()) {
log.info("auto-configuring SSO endpoints for protocol (%s)", prot.get());
- int index = 0;
pair<bool,const XMLCh*> idprop,pathprop;
for (vector<const PropertySet*>::const_iterator b = bindings.begin(); b != bindings.end(); ++b, ++index) {
idprop = (*b)->getXMLString("id");
pathprop = (*b)->getXMLString("path");
if (idprop.first && pathprop.first) {
DOMElement* acsdom = e->getOwnerDocument()->createElementNS(samlconstants::SAML20MD_NS, _AssertionConsumerService);
+
+ // Copy in any attributes from the <SSO> element so they can be accessed as properties in the ACS handler.
+ for (XMLSize_t p = 0; p < ssopropslen; ++p) {
+ DOMNode* ssoprop = ssoprops->item(p);
+ if (ssoprop->getNodeType() == DOMNode::ATTRIBUTE_NODE) {
+ acsdom->setAttributeNS(
+ ((DOMAttr*)ssoprop)->getNamespaceURI(),
+ ((DOMAttr*)ssoprop)->getLocalName(),
+ ((DOMAttr*)ssoprop)->getValue()
+ );
+ }
+ }
+
+ // Set necessary properties based on context.
acsdom->setAttributeNS(nullptr, Binding, idprop.second);
acsdom->setAttributeNS(nullptr, Location, pathprop.second);
- xstring indexbuf(chDigit_1 + (index % 10), 1);
+ xstring indexbuf(1, chDigit_1 + (index % 10));
if (index / 10)
indexbuf = (XMLCh)(chDigit_1 + (index / 10)) + indexbuf;
acsdom->setAttributeNS(nullptr, _index, indexbuf.c_str());
{
if (!e->hasChildNodes())
return;
+ DOMNamedNodeMap* sloprops = e->getAttributes();
+ XMLSize_t slopropslen = sloprops ? sloprops->getLength() : 0;
SPConfig& conf = SPConfig::getConfig();
pathprop = (*b)->getXMLString("path");
if (idprop.first && pathprop.first) {
DOMElement* slodom = e->getOwnerDocument()->createElementNS(samlconstants::SAML20MD_NS, _SingleLogoutService);
+
+ // Copy in any attributes from the <Logout> element so they can be accessed as properties in the SLO handler.
+ for (XMLSize_t p = 0; p < slopropslen; ++p) {
+ DOMNode* sloprop = sloprops->item(p);
+ if (sloprop->getNodeType() == DOMNode::ATTRIBUTE_NODE) {
+ slodom->setAttributeNS(
+ ((DOMAttr*)sloprop)->getNamespaceURI(),
+ ((DOMAttr*)sloprop)->getLocalName(),
+ ((DOMAttr*)sloprop)->getValue()
+ );
+ }
+ }
+
+ // Set necessary properties based on context.
slodom->setAttributeNS(nullptr, Binding, idprop.second);
slodom->setAttributeNS(nullptr, Location, pathprop.second);
+ if (e->hasAttributeNS(nullptr, _policyId))
+ slodom->setAttributeNS(shibspconstants::SHIB2SPCONFIG_NS, _policyId, e->getAttributeNS(nullptr, _policyId));
log.info("adding SingleLogoutService for Binding (%s) at (%s)", (*b)->getString("id").second, (*b)->getString("path").second);
boost::shared_ptr<Handler> handler(
{
if (!e->hasChildNodes())
return;
+ DOMNamedNodeMap* nimprops = e->getAttributes();
+ XMLSize_t nimpropslen = nimprops ? nimprops->getLength() : 0;
SPConfig& conf = SPConfig::getConfig();
pathprop = (*b)->getXMLString("path");
if (idprop.first && pathprop.first) {
DOMElement* nimdom = e->getOwnerDocument()->createElementNS(samlconstants::SAML20MD_NS, _ManageNameIDService);
+
+ // Copy in any attributes from the <NameIDMgmt> element so they can be accessed as properties in the NIM handler.
+ for (XMLSize_t p = 0; p < nimpropslen; ++p) {
+ DOMNode* nimprop = nimprops->item(p);
+ if (nimprop->getNodeType() == DOMNode::ATTRIBUTE_NODE) {
+ nimdom->setAttributeNS(
+ ((DOMAttr*)nimprop)->getNamespaceURI(),
+ ((DOMAttr*)nimprop)->getLocalName(),
+ ((DOMAttr*)nimprop)->getValue()
+ );
+ }
+ }
+
+ // Set necessary properties based on context.
nimdom->setAttributeNS(nullptr, Binding, idprop.second);
nimdom->setAttributeNS(nullptr, Location, pathprop.second);
+ if (e->hasAttributeNS(nullptr, _policyId))
+ nimdom->setAttributeNS(shibspconstants::SHIB2SPCONFIG_NS, _policyId, e->getAttributeNS(nullptr, _policyId));
log.info("adding ManageNameIDService for Binding (%s) at (%s)", (*b)->getString("id").second, (*b)->getString("path").second);
boost::shared_ptr<Handler> handler(
{
SPConfig& conf = SPConfig::getConfig();
+ int index = 0; // track indexes globally across all protocols
+
// Look for incoming bindings.
const vector<const PropertySet*>& bindings = pp.getBindings(protocol, "ArtifactResolution");
if (!bindings.empty()) {
log.info("auto-configuring ArtifactResolution endpoints for protocol (%s)", protocol);
- int index = 0;
pair<bool,const XMLCh*> idprop,pathprop;
- for (vector<const PropertySet*>::const_iterator b = bindings.begin(); b != bindings.end(); ++b) {
+ for (vector<const PropertySet*>::const_iterator b = bindings.begin(); b != bindings.end(); ++b, ++index) {
idprop = (*b)->getXMLString("id");
pathprop = (*b)->getXMLString("path");
if (idprop.first && pathprop.first) {
DOMElement* artdom = e->getOwnerDocument()->createElementNS(samlconstants::SAML20MD_NS, _ArtifactResolutionService);
artdom->setAttributeNS(nullptr, Binding, idprop.second);
artdom->setAttributeNS(nullptr, Location, pathprop.second);
- xstring indexbuf(chDigit_1 + (index % 10), 1);
+ xstring indexbuf(1, chDigit_1 + (index % 10));
if (index / 10)
indexbuf = (XMLCh)(chDigit_1 + (index / 10)) + indexbuf;
artdom->setAttributeNS(nullptr, _index, indexbuf.c_str());
if (!provider)
return this;
+ // Check for exact match on name.
map< xstring,boost::shared_ptr<PropertySet> >::const_iterator i = m_partyMap.find(provider->getEntityID());
if (i != m_partyMap.end())
return i->second.get();
+
+ // Check for extensible matching.
+ vector < pair< boost::shared_ptr<EntityMatcher>,boost::shared_ptr<PropertySet> > >::const_iterator j;
+ for (j = m_partyVec.begin(); j != m_partyVec.end(); ++j) {
+ if (j->first->matches(*provider))
+ return j->second.get();
+ }
+
+ // Check for group match.
const EntitiesDescriptor* group = dynamic_cast<const EntitiesDescriptor*>(provider->getParent());
while (group) {
if (group->getName()) {
boost::bind(startsWithI, url, boost::bind(&string::c_str, _1))) != m_redirectWhitelist.end()) {
return;
}
- Category::getInstance(SHIBSP_LOGCAT".Application").warn("redirectLimit policy enforced, blocked redirect to (%s)", url);
+ Category::getInstance(SHIBSP_LOGCAT ".Application").warn("redirectLimit policy enforced, blocked redirect to (%s)", url);
throw opensaml::SecurityPolicyException("Blocked unacceptable redirect location.");
}
}
const DOMElement* SHIRE=XMLHelper::getFirstChildElement(e, InProcess);
// Initialize logging manually in order to redirect log messages as soon as possible.
+ // If no explicit config is supplied, we now assume the caller has done this, so that
+ // setuid processes can potentially do this as root.
if (conf.isEnabled(SPConfig::Logging)) {
string logconf;
if (conf.isEnabled(SPConfig::OutOfProcess))
logconf = XMLHelper::getAttrString(SHIRE, nullptr, logger);
if (logconf.empty())
logconf = XMLHelper::getAttrString(e, nullptr, logger);
- if (logconf.empty() && !getenv("SHIBSP_LOGGING")) {
- // No properties found, so default them.
- if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess))
- logconf = "shibd.logger";
- else if (!conf.isEnabled(SPConfig::OutOfProcess) && conf.isEnabled(SPConfig::InProcess))
- logconf = "native.logger";
- else
- logconf = "shibboleth.logger";
- }
if (!logconf.empty()) {
log.debug("loading new logging configuration from (%s), check log destination for status of configuration", logconf.c_str());
if (!XMLToolingConfig::getConfig().log_config(logconf.c_str()))
if (unsafe.first) {
HTTPResponse::getAllowedSchemes().clear();
string schemes(unsafe.second);
+ trim(schemes);
split(HTTPResponse::getAllowedSchemes(), schemes, is_space(), algorithm::token_compress_on);
}
// For backward compatibility, wrap in a plugin element.
DOMElement* polwrapper = e->getOwnerDocument()->createElementNS(nullptr, _SecurityPolicyProvider);
polwrapper->appendChild(child);
- log.info("building SecurityPolicyProvider of type %s...", XML_SECURITYPOLICY_PROVIDER);
+ log.warn("deprecated/legacy SecurityPolicy configuration, consider externalizing with <SecurityPolicyProvider>");
m_policy.reset(conf.SecurityPolicyProviderManager.newPlugin(XML_SECURITYPOLICY_PROVIDER, polwrapper));
}
else {
}
if (first) {
- if (!m_policy->getAlgorithmBlacklist().empty()) {
+ if (!m_policy->getAlgorithmWhitelist().empty()) {
#ifdef SHIBSP_XMLSEC_WHITELISTING
- for_each(
- m_policy->getAlgorithmBlacklist().begin(), m_policy->getAlgorithmBlacklist().end(),
- boost::bind(&XSECPlatformUtils::blacklistAlgorithm, boost::bind(&xstring::c_str, _1))
- );
+ for (vector<xstring>::const_iterator white = m_policy->getAlgorithmWhitelist().begin();
+ white != m_policy->getAlgorithmWhitelist().end(); ++white) {
+ XSECPlatformUtils::whitelistAlgorithm(white->c_str());
+ auto_ptr_char whitelog(white->c_str());
+ log.info("explicitly whitelisting security algorithm (%s)", whitelog.get());
+ }
#else
log.crit("XML-Security-C library prior to 1.6.0 does not support algorithm white/blacklists");
#endif
}
- else if (!m_policy->getAlgorithmWhitelist().empty()) {
+ else if (!m_policy->getDefaultAlgorithmBlacklist().empty() || !m_policy->getAlgorithmBlacklist().empty()) {
#ifdef SHIBSP_XMLSEC_WHITELISTING
- for_each(
- m_policy->getAlgorithmWhitelist().begin(), m_policy->getAlgorithmWhitelist().end(),
- boost::bind(&XSECPlatformUtils::whitelistAlgorithm, boost::bind(&xstring::c_str, _1))
- );
+ for (vector<xstring>::const_iterator black = m_policy->getDefaultAlgorithmBlacklist().begin();
+ black != m_policy->getDefaultAlgorithmBlacklist().end(); ++black) {
+ XSECPlatformUtils::blacklistAlgorithm(black->c_str());
+ auto_ptr_char blacklog(black->c_str());
+ log.info("automatically blacklisting security algorithm (%s)", blacklog.get());
+ }
+ for (vector<xstring>::const_iterator black = m_policy->getAlgorithmBlacklist().begin();
+ black != m_policy->getAlgorithmBlacklist().end(); ++black) {
+ XSECPlatformUtils::blacklistAlgorithm(black->c_str());
+ auto_ptr_char blacklog(black->c_str());
+ log.info("explicitly blacklisting security algorithm (%s)", blacklog.get());
+ }
#else
log.crit("XML-Security-C library prior to 1.6.0 does not support algorithm white/blacklists");
#endif
string option(XMLHelper::getAttrString(child, nullptr, _option));
auto_ptr_char value(child->getFirstChild()->getNodeValue());
if (!provider.empty() && !option.empty() && value.get() && *value.get()) {
- m_transportOptions.push_back(make_tuple(provider, option, string(value.get())));
+ m_transportOptions.push_back(boost::make_tuple(provider, option, string(value.get())));
}
}
child = XMLHelper::getPreviousSiblingElement(child, TransportOption);
pair<bool,const char*> extraAuthTypes = inprocs->getString("extraAuthTypes");
if (extraAuthTypes.first) {
string types(extraAuthTypes.second);
+ trim(types);
split(outer->m_authTypes, types, is_space(), algorithm::token_compress_on);
+ outer->m_authTypes.insert("shibboleth");
}
}
}
}
}
else {
- Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+ Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").error(
"Storage-backed RelayState with invalid StorageService ID (%s)", id
);
}
storage->createText("RelayState", rsKey.c_str(), value, time(nullptr) + 600);
}
else {
- Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+ Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").error(
"Storage-backed RelayState with invalid StorageService ID (%s)", id
);
}
}
}
else {
- Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+ Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").error(
"Storage-backed PostData with invalid StorageService ID (%s)", id
);
}
storage->createText("PostData", rsKey.c_str(), params.str().c_str(), time(nullptr) + 600);
}
else {
- Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+ Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").error(
"Storage-backed PostData with invalid StorageService ID (%s)", id
);
}