native.logger \
shibd.logger \
attribute-map.xml \
+ attribute-policy.xml \
example-metadata.xml
# While BUILTCONFIGFILES are processed, these are not; so we should pull
attribute-map.xml: ${srcdir}/attribute-map.xml.in Makefile ${top_builddir}/config.status
$(MAKE) do-build-file FILE=$@
+attribute-policy.xml: ${srcdir}/attribute-policy.xml.in Makefile ${top_builddir}/config.status
+ $(MAKE) do-build-file FILE=$@
+
example-metadata.xml: ${srcdir}/example-metadata.xml.in Makefile ${top_builddir}/config.status
$(MAKE) do-build-file FILE=$@
native.logger \
shibboleth.xml \
attribute-map.xml \
+ attribute-policy.xml \
example-metadata.xml
EXTRA_DIST =
shibboleth.xml.in \
attribute-map.xml.in \
+ attribute-policy.xml.in \
example-metadata.xml.in \
native.logger.in \
shibd.logger.in \
<!-- First some useful eduPerson attributes that many sites might use. -->
- <Attribute name="urn:mace:dir:attribute-def:eduPersonPrincipalName" id="REMOTE_USER">
+ <Attribute name="urn:mace:dir:attribute-def:eduPersonPrincipalName" id="eppn">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
- <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="REMOTE_USER">
+ <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="eppn">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
<!-- A persistent id attribute that supports personalized anonymous access. -->
<!-- First, the deprecated version: -->
- <Attribute name="urn:mace:dir:attribute-def:eduPersonTargetedID" id="REMOTE_USER">
+ <Attribute name="urn:mace:dir:attribute-def:eduPersonTargetedID" id="targeted-id">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
<!-- Second, the new version (note the OID-style name): -->
- <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" id="REMOTE_USER">
+ <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" id="persistent-id">
<AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$Name!!$NameQualifier!!$SPNameQualifier"/>
</Attribute>
<!-- Third, the SAML 2.0 NameID Format: -->
- <Attribute name="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" id="REMOTE_USER">
+ <Attribute name="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" id="persistent-id">
<AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$Name!!$NameQualifier!!$SPNameQualifier"/>
</Attribute>
--- /dev/null
+<afp:AttributeFilterPolicyGroup
+ xmlns="urn:mace:shibboleth:2.0:afp:mf:basic"
+ xmlns:basic="urn:mace:shibboleth:2.0:afp:mf:basic"
+ xmlns:afp="urn:mace:shibboleth:2.0:afp"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:afp @-PKGXMLDIR-@/shibboleth-2.0-afp.xsd urn:mace:shibboleth:2.0:afp:mf:basic @-PKGXMLDIR-@/shibboleth-2.0-afp-mf-basic.xsd urn:mace:shibboleth:2.0:afp:mf:saml @-PKGXMLDIR-@/shibboleth-2.0-afp-mf-saml.xsd">
+
+ <!-- Shared rule for affiliation values. -->
+ <afp:PermitValueRule id="eduPersonAffiliationValues" xsi:type="OR">
+ <Rule xsi:type="AttributeValueString" value="faculty"/>
+ <Rule xsi:type="AttributeValueString" value="student"/>
+ <Rule xsi:type="AttributeValueString" value="staff"/>
+ <Rule xsi:type="AttributeValueString" value="alum"/>
+ <Rule xsi:type="AttributeValueString" value="member"/>
+ <Rule xsi:type="AttributeValueString" value="affiliate"/>
+ <Rule xsi:type="AttributeValueString" value="employee"/>
+ </afp:PermitValueRule>
+
+ <!--
+ Shared rule for all "scoped" attributes, but you'll have to manually apply it inside
+ an AttributeRule for each attribute you want to check.
+ -->
+ <afp:PermitValueRule id="ScopingRules" xsi:type="AND">
+ <Rule xsi:type="NOT">
+ <Rule xsi:type="AttributeValueRegex" regex="@"/>
+ </Rule>
+ <Rule xsi:type="saml:AttributeScopeMatchesShibMDScope" xmlns:saml="urn:mace:shibboleth:2.0:afp:mf:saml"/>
+ </afp:PermitValueRule>
+
+ <afp:AttributeFilterPolicy>
+ <!-- This policy is in effect in all cases. -->
+ <afp:PolicyRequirementRule xsi:type="ANY"/>
+
+ <!-- Filter out undefined affiliations and ensure only one primary. -->
+ <afp:AttributeRule attributeID="affiliation">
+ <afp:PermitValueRule xsi:type="AND">
+ <RuleReference ref="eduPersonAffiliationValues"/>
+ <RuleReference ref="ScopingRules"/>
+ </afp:PermitValueRule>
+ </afp:AttributeRule>
+ <afp:AttributeRule attributeID="unscoped-affiliation">
+ <afp:PermitValueRuleReference ref="eduPersonAffiliationValues"/>
+ </afp:AttributeRule>
+ <afp:AttributeRule attributeID="primary-affiliation">
+ <afp:PermitValueRuleReference ref="eduPersonAffiliationValues"/>
+ </afp:AttributeRule>
+
+ <afp:AttributeRule attributeID="eppn">
+ <afp:PermitValueRuleReference ref="ScopingRules"/>
+ </afp:AttributeRule>
+
+ <afp:AttributeRule attributeID="targeted-id">
+ <afp:PermitValueRuleReference ref="ScopingRules"/>
+ </afp:AttributeRule>
+
+ <!-- Catch-all that passes everything else through unmolested. -->
+ <afp:AttributeRule attributeID="*">
+ <afp:PermitValueRule xsi:type="ANY"/>
+ </afp:AttributeRule>
+
+ </afp:AttributeFilterPolicy>
+
+</afp:AttributeFilterPolicyGroup>
<!-- Use a SAML query if no attributes are supplied during SSO. -->
<AttributeResolver type="Query"/>
+ <!-- Default filtering policy for recognized attributes, lets other data pass. -->
+ <AttributeFilter type="XML" path="@-PKGSYSCONFDIR-@/attribute-policy.xml"/>
+
<!-- Simple file-based resolver for key/certificate information. -->
<CredentialResolver type="File">
<Key>
<?xml version="1.0" encoding="UTF-8"?>
-<schema targetNamespace="urn:mace:shibboleth:2.0:afp:mf:basic" xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:basic="urn:mace:shibboleth:2.0:afp:mf:basic" xmlns:afp="urn:mace:shibboleth:2.0:afp">
+<schema targetNamespace="urn:mace:shibboleth:2.0:afp:mf:basic"
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:basic="urn:mace:shibboleth:2.0:afp:mf:basic"
+ xmlns:afp="urn:mace:shibboleth:2.0:afp"
+ elementFormDefault="qualified">
<import namespace="urn:mace:shibboleth:2.0:afp" schemaLocation="classpath:/schema/shibboleth-2.0-afp.xsd" />
<?xml version="1.0" encoding="UTF-8"?>
-<schema targetNamespace="urn:mace:shibboleth:2.0:afp" xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:afp="urn:mace:shibboleth:2.0:afp" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">\r
+<schema targetNamespace="urn:mace:shibboleth:2.0:afp"
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:afp="urn:mace:shibboleth:2.0:afp"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ elementFormDefault="qualified">\r
\r
<import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="classpath:/schema/xmldsig-core-schema.xsd" />\r
\r
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="urn:mace:shibboleth:2.0:attribute-map"
xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:am="urn:mace:shibboleth:2.0:attribute-map">
+ xmlns:am="urn:mace:shibboleth:2.0:attribute-map"
+ elementFormDefault="qualified">
<annotation>
<documentation>
<attributeGroup ref="conf:ContentSettings"/>\r
</complexType>\r
</element>\r
-\r
+ \r
<element name="Applications">\r
<annotation>\r
<documentation>Container for global settings and application-specific overrides</documentation>\r
<attribute name="entityID" type="anyURI" use="required"/>\r
<attribute name="policyId" type="conf:string" use="required"/>\r
<attribute name="homeURL" type="anyURI" default="/"/>\r
+ <attribute name="REMOTE_USER" type="conf:listOfStrings"/>\r
<anyAttribute namespace="##other" processContents="lax"/>\r
</complexType>\r
</element>\r
<attribute name="entityID" type="anyURI"/>\r
<attribute name="policyId" type="conf:string"/>\r
<attribute name="homeURL" type="anyURI" default="/"/>\r
+ <attribute name="REMOTE_USER" type="conf:listOfStrings"/>\r
<anyAttribute namespace="##other" processContents="lax"/>\r
</complexType>\r
</element>\r
*/
virtual AttributeResolver* getAttributeResolver() const=0;
+ /**\r
+ * Returns a set of attribute IDs to use as a REMOTE_USER value.\r
+ * <p>The first attribute with a value (and only a single value) will be used.\r
+ *\r
+ * @return a set of attribute IDs, or an empty set\r
+ */\r
+ virtual const std::set<std::string>& getRemoteUserAttributeIds() const=0;
+
/**
* Returns the CredentialResolver instance associated with this Application.
*
#include "SessionCache.h"
#include "SPRequest.h"
#include "attribute/Attribute.h"
+#include "attribute/resolver/AttributeExtractor.h"
+#include "attribute/resolver/AttributeResolver.h"
#include "handler/SessionInitiator.h"
#include "util/TemplateParameters.h"
request.clearHeader("Shib-Attributes");
request.clearHeader("Shib-Application-ID");
- // Clear out the list of mapped attributes
- /* TODO: need some kind of master attribute list via the new resolver
- Iterator<IAAP*> provs=dynamic_cast<const IApplication&>(getApplication()).getAAPProviders();
- while (provs.hasNext()) {
- IAAP* aap=provs.next();
- xmltooling::Locker locker(aap);
- Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
- while (rules.hasNext()) {
- const char* header=rules.next()->getHeader();
- if (header)
- request.clearHeader(header);
- }
+ // Let plugins do the rest.
+ AttributeExtractor* extractor = request.getApplication().getAttributeExtractor();
+ if (extractor) {
+ Locker locker(extractor);
+ extractor->clearHeaders(request);
+ }
+ AttributeResolver* resolver = request.getApplication().getAttributeResolver();
+ if (resolver) {
+ Locker locker(resolver);
+ resolver->clearHeaders(request);
}
- */
}
-
- static const XMLCh SessionInitiator[] = UNICODE_LITERAL_16(S,e,s,s,i,o,n,I,n,i,t,i,a,t,o,r);
};
void SHIBSP_API shibsp::registerServiceProviders()
}
// Export the attributes.
+ bool remoteUserSet = false;
const multimap<string,Attribute*>& attributes = session->getAttributes();
for (multimap<string,Attribute*>::const_iterator a = attributes.begin(); a!=attributes.end(); ++a) {
const vector<string>& vals = a->second->getSerializedValues();
- if (a->first == "REMOTE_USER" && !vals.empty())
+
+ // See if this needs to be set as the REMOTE_USER value.
+ if (!remoteUserSet && !vals.empty() && app->getRemoteUserAttributeIds().count(a->first)) {
request.setRemoteUser(vals.front().c_str());
- else {
- string header(request.getSecureHeader(a->first.c_str()));
- for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
- if (!header.empty())
- header += ";";
- string::size_type pos = v->find_first_of(';',string::size_type(0));
- if (pos!=string::npos) {
- string value(*v);
- for (; pos != string::npos; pos = value.find_first_of(';',pos)) {
- value.insert(pos, "\\");
- pos += 2;
- }
- header += value;
- }
- else {
- header += (*v);
+ remoteUserSet = true;
+ }
+
+ // Handle the normal export case.
+ string header(request.getSecureHeader(a->first.c_str()));
+ for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
+ if (!header.empty())
+ header += ";";
+ string::size_type pos = v->find_first_of(';',string::size_type(0));
+ if (pos!=string::npos) {
+ string value(*v);
+ for (; pos != string::npos; pos = value.find_first_of(';',pos)) {
+ value.insert(pos, "\\");
+ pos += 2;
}
+ header += value;
+ }
+ else {
+ header += (*v);
}
- request.setHeader(a->first.c_str(), header.c_str());
}
+ request.setHeader(a->first.c_str(), header.c_str());
}
return make_pair(false,0);
{
public:
bool evaluatePolicyRequirement(const FilteringContext& filterContext) const {
- return false;
+ throw AttributeFilteringException("Metadata scope matching not usable as a PolicyRequirement.");
}
bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
}
bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
- if (!XMLString::equals(m_attributeID.get(), attribute.getId()))
- return hasScope(filterContext);
- return matches(attribute, index);
+ if (!m_attributeID.get() || !*m_attributeID.get() || XMLString::equals(m_attributeID.get(), attribute.getId()))
+ return matches(attribute, index);
+ return hasScope(filterContext);
}
};
}
bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
- if (!XMLString::equals(m_attributeID.get(), attribute.getId()))
- return hasScope(filterContext);
- return XMLString::equals(attribute.getScope(index), m_value.get());
+ if (!m_attributeID.get() || !*m_attributeID.get() || XMLString::equals(m_attributeID.get(), attribute.getId()))
+ return XMLString::equals(attribute.getScope(index), m_value.get());
+ return hasScope(filterContext);
}
};
try {
m_regex = new RegularExpression(r, e->getAttributeNS(NULL,options));
}
- catch (XMLException& ex) {\r
- xmltooling::auto_ptr_char temp(ex.getMessage());\r
- throw ConfigurationException(temp.get());\r
- }\r
+ catch (XMLException& ex) {
+ xmltooling::auto_ptr_char temp(ex.getMessage());
+ throw ConfigurationException(temp.get());
+ }
}
bool evaluatePolicyRequirement(const FilteringContext& filterContext) const {
}
bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
- if (!XMLString::equals(m_attributeID.get(), attribute.getId()))
- return hasValue(filterContext);
- return matches(attribute, index);
+ if (!m_attributeID.get() || !*m_attributeID.get() || XMLString::equals(m_attributeID.get(), attribute.getId()))
+ return matches(attribute, index);
+ return hasValue(filterContext);
}
};
}
bool evaluatePermitValue(const FilteringContext& filterContext, const Attribute& attribute, size_t index) const {
- if (!XMLString::equals(m_attributeID.get(), attribute.getId()))
- return hasValue(filterContext);
- return matches(attribute, index);
+ if (!m_attributeID.get() || !*m_attributeID.get() || XMLString::equals(m_attributeID.get(), attribute.getId()))
+ return matches(attribute, index);
+ return hasValue(filterContext);
}
};
class SHIBSP_API Application;
class SHIBSP_API Attribute;
+ class SHIBSP_API SPRequest;
/**
* A service that extracts and decodes attributes from XML objects.
const xmltooling::XMLObject& xmlObject,
std::multimap<std::string,Attribute*>& attributes
) const=0;
+
+ /**
+ * Clears possible HTTP request headers that might be populated
+ * during attribute export.
+ *
+ * @param request the SP request being processed
+ */
+ virtual void clearHeaders(SPRequest& request) const=0;
};
/**
class SHIBSP_API Application;
class SHIBSP_API Attribute;
- class SHIBSP_API Session;
class SHIBSP_API ResolutionContext;
+ class SHIBSP_API Session;
+ class SHIBSP_API SPRequest;
#if defined (_MSC_VER)
#pragma warning( push )
* @throws AttributeResolutionException thrown if there is a problem resolving the attributes for the subject
*/
virtual void resolveAttributes(ResolutionContext& ctx) const=0;
+
+ /**
+ * Clears possible HTTP request headers that might be populated
+ * during attribute export.
+ *
+ * @param request the SP request being processed
+ */
+ virtual void clearHeaders(SPRequest& request) const=0;
};
#if defined (_MSC_VER)
void resolveAttributes(ResolutionContext& ctx) const;
+ void clearHeaders(SPRequest& request) const {
+ for (vector<AttributeResolver*>::const_iterator i=m_resolvers.begin(); i!=m_resolvers.end(); ++i)
+ (*i)->clearHeaders(request);
+ }
+
private:
vector<AttributeResolver*> m_resolvers;
};
void resolveAttributes(ResolutionContext& ctx) const;
+ void clearHeaders(SPRequest& request) const {
+ // Doesn't have to do anything, the extractor is the only possibly source of attributes.
+ }
+
private:
bool SAML1Query(QueryContext& ctx) const;
bool SAML2Query(QueryContext& ctx) const;
if (qctx.getNameID() && qctx.getEntityDescriptor()) {
m_log.debug("attempting SAML 2.0 attribute query");
- if (!SAML2Query(qctx)) {
- m_log.debug("attempting SAML 1.x attribute query");
- SAML1Query(qctx);
- }
+ if (SAML2Query(qctx))
+ return;
+ m_log.debug("attempting SAML 1.x attribute query");
+ SAML1Query(qctx);
+ return;
}
m_log.warn("can't attempt attribute query, either no NameID or no metadata to use");
}
#include "internal.h"
#include "Application.h"
#include "ServiceProvider.h"
+#include "SPRequest.h"
#include "attribute/AttributeDecoder.h"
#include "attribute/resolver/AttributeExtractor.h"
#include "util/SPConstants.h"
const Application& application, const char* assertingParty, const saml2::Attribute& attr, multimap<string,Attribute*>& attributes
) const;
+ void clearHeaders(SPRequest& request) const {
+ for (vector<string>::const_iterator i = m_attributeIds.begin(); i!=m_attributeIds.end(); ++i)
+ request.clearHeader(i->c_str());
+ }
+
private:
Category& m_log;
DOMDocument* m_document;
typedef map< pair<string,string>,pair<AttributeDecoder*,string> > attrmap_t;
#endif
attrmap_t m_attrMap;
+ vector<string> m_attributeIds;
};
class XMLExtractor : public AttributeExtractor, public ReloadableXMLFile
const Application& application, const RoleDescriptor* issuer, const XMLObject& xmlObject, multimap<string,Attribute*>& attributes
) const;
+ void clearHeaders(SPRequest& request) const {
+ if (m_impl)
+ m_impl->clearHeaders(request);
+ }
+
protected:
pair<bool,DOMElement*> load();
child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
continue;
}
+ else if (!strcmp(id.get(), "REMOTE_USER")) {
+ m_log.warn("skipping Attribute, id of REMOTE_USER is a reserved name");
+ child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
+ continue;
+ }
AttributeDecoder* decoder=NULL;
try {
decl.first = decoder;
decl.second = id.get();
+ m_attributeIds.push_back(id.get());
child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
}
const Application& application, const RoleDescriptor* issuer, const XMLObject& xmlObject, multimap<string,Attribute*>& attributes
) const
{
+ if (!m_impl)
+ return;
+
// Check for assertions.
if (XMLString::equals(xmlObject.getElementQName().getLocalPart(), saml1::Assertion::LOCAL_NAME)) {
const saml2::Assertion* token2 = dynamic_cast<const saml2::Assertion*>(&xmlObject);
AttributeResolver* getAttributeResolver() const {\r
return (!m_attrResolver && m_base) ? m_base->getAttributeResolver() : m_attrResolver;\r
}\r
+ const set<string>& getRemoteUserAttributeIds() const {\r
+ return (m_attributeIds.empty() && m_base) ? m_base->getRemoteUserAttributeIds() : m_attributeIds;\r
+ }\r
CredentialResolver* getCredentialResolver() const {\r
return (!m_credResolver && m_base) ? m_base->getCredentialResolver() : m_credResolver;\r
}\r
AttributeResolver* m_attrResolver;\r
CredentialResolver* m_credResolver;\r
vector<const XMLCh*> m_audiences;\r
+ set<string> m_attributeIds;\r
\r
// manage handler objects\r
vector<Handler*> m_handlers;\r
m_hash+=getString("entityID").second;\r
m_hash=samlConf.hashSHA1(m_hash.c_str(), true);\r
\r
+ pair<bool,const char*> attributes = getString("REMOTE_USER");\r
+ if (attributes.first) {\r
+ char* dup = strdup(attributes.second);\r
+ char* pos;\r
+ char* start = dup;\r
+ while (start && *start) {\r
+ while (*start && isspace(*start))\r
+ start++;\r
+ if (!*start)\r
+ break;\r
+ pos = strchr(start,' ');\r
+ if (pos)\r
+ *pos=0;\r
+ m_attributeIds.insert(start);\r
+ start = pos ? pos+1 : NULL;\r
+ }\r
+ free(dup);\r
+ }\r
+\r
const PropertySet* sessions = getPropertySet("Sessions");\r
\r
// Process handlers.\r