+ attrmap_t::const_iterator rule;
+ if ((rule = m_attrMap.find(pair<xstring,xstring>(name,format))) != m_attrMap.end()) {
+ auto_ptr<Attribute> a(rule->second.first->decode(request, rule->second.second, &attr, assertingParty, relyingParty));
+ if (a.get()) {
+ attributes.push_back(a.get());
+ a.release();
+ return;
+ }
+ }
+ else if (XMLString::equals(format, saml2::Attribute::UNSPECIFIED)) {
+ // As a fallback, if the format is "unspecified", null out the value and re-map.
+ if ((rule = m_attrMap.find(pair<xstring,xstring>(name,xstring()))) != m_attrMap.end()) {
+ auto_ptr<Attribute> a(rule->second.first->decode(request, rule->second.second, &attr, assertingParty, relyingParty));
+ if (a.get()) {
+ attributes.push_back(a.get());
+ a.release();
+ return;
+ }
+ }
+ }
+
+ if (m_log.isInfoEnabled()) {
+ auto_ptr_char temp1(name);
+ auto_ptr_char temp2(format);
+ m_log.info("skipping unmapped SAML 2.0 Attribute with Name: %s%s%s", temp1.get(), *temp2.get() ? ", Format:" : "", temp2.get());
+ }
+}
+
+void XMLExtractorImpl::extractAttributes(
+ const Application& application,
+ const GenericRequest* request,
+ const char* assertingParty,
+ const char* relyingParty,
+ const saml1::AttributeStatement& statement,
+ ptr_vector<Attribute>& attributes
+ ) const
+{
+ static void (XMLExtractorImpl::* extract)(
+ const Application&, const GenericRequest*, const char*, const char*, const saml1::Attribute&, ptr_vector<Attribute>&
+ ) const = &XMLExtractorImpl::extractAttributes;
+ for_each(
+ make_indirect_iterator(statement.getAttributes().begin()), make_indirect_iterator(statement.getAttributes().end()),
+ boost::bind(extract, this, boost::cref(application), request, assertingParty, relyingParty, _1, boost::ref(attributes))
+ );
+}
+
+void XMLExtractorImpl::extractAttributes(
+ const Application& application,
+ const GenericRequest* request,
+ const char* assertingParty,
+ const char* relyingParty,
+ const saml2::AttributeStatement& statement,
+ ptr_vector<Attribute>& attributes
+ ) const
+{
+ static void (XMLExtractorImpl::* extract)(
+ const Application&, const GenericRequest*, const char*, const char*, const saml2::Attribute&, ptr_vector<Attribute>&
+ ) const = &XMLExtractorImpl::extractAttributes;
+ for_each(
+ make_indirect_iterator(statement.getAttributes().begin()), make_indirect_iterator(statement.getAttributes().end()),
+ boost::bind(extract, this, boost::cref(application), request, assertingParty, relyingParty, _1, boost::ref(attributes))
+ );
+}
+
+void XMLExtractorImpl::extractAttributes(
+ const Application& application,
+ const GenericRequest* request,
+ const ObservableMetadataProvider* observable,
+ const XMLCh* entityID,
+ const char* relyingParty,
+ const Extensions& ext,
+ ptr_vector<Attribute>& attributes
+ ) const
+{
+ const vector<XMLObject*>& exts = ext.getUnknownXMLObjects();
+ for (vector<XMLObject*>::const_iterator i = exts.begin(); i != exts.end(); ++i) {
+ const EntityAttributes* container = dynamic_cast<const EntityAttributes*>(*i);
+ if (!container)
+ continue;
+
+ bool useCache = false;
+ map<const ObservableMetadataProvider*,decoded_t>::iterator cacheEntry;
+
+ // Check for cached result.
+ if (observable && m_metaAttrCaching) {
+ m_attrLock->rdlock();
+ cacheEntry = m_decodedMap.find(observable);
+ if (cacheEntry == m_decodedMap.end()) {
+ // We need to elevate the lock and retry.
+ m_attrLock->unlock();
+ m_attrLock->wrlock();
+ cacheEntry = m_decodedMap.find(observable);
+ if (cacheEntry == m_decodedMap.end()) {
+ SharedLock locker(m_attrLock, false); // guard in case these throw
+
+ // It's still brand new, so hook it for cache activation.
+ observable->addObserver(this);
+
+ // Prime the map reference with an empty decoded map.
+ cacheEntry = m_decodedMap.insert(make_pair(observable,decoded_t())).first;
+
+ // Downgrade the lock.
+ // We don't have to recheck because we never erase the master map entry entirely, even on changes.
+ locker.release(); // unguard for lock downgrade
+ m_attrLock->unlock();
+ m_attrLock->rdlock();
+ }
+ }
+ useCache = true;
+ }
+
+ if (useCache) {
+ // We're holding the lock, so check the cache.
+ decoded_t::iterator d = cacheEntry->second.find(container);
+ if (d != cacheEntry->second.end()) {
+ SharedLock locker(m_attrLock, false); // pop the lock when we're done
+ for (vector<DDF>::iterator obj = d->second.begin(); obj != d->second.end(); ++obj) {
+ auto_ptr<Attribute> wrapper(Attribute::unmarshall(*obj));
+ m_log.debug("recovered cached metadata attribute (%s)", wrapper->getId());
+ attributes.push_back(wrapper.get());
+ wrapper.release();
+ }
+ break;
+ }
+ }
+
+ // Add a guard for the lock if we're caching.
+ SharedLock locker(useCache ? m_attrLock.get() : nullptr, false);
+
+ // Use a holding area to support caching.
+ ptr_vector<Attribute> holding;
+
+ // Extract attributes into holding area with no asserting party set.
+ static void (XMLExtractorImpl::* extractV2Attr)(
+ const Application&, const GenericRequest*, const char*, const char*, const saml2::Attribute&, ptr_vector<Attribute>&
+ ) const = &XMLExtractorImpl::extractAttributes;
+ for_each(
+ make_indirect_iterator(container->getAttributes().begin()), make_indirect_iterator(container->getAttributes().end()),
+ boost::bind(extractV2Attr, this, boost::ref(application), request, (const char*)nullptr, relyingParty, _1, boost::ref(holding))
+ );
+
+ if (entityID && m_entityAssertions) {
+ const vector<saml2::Assertion*>& asserts = container->getAssertions();
+ for (indirect_iterator<vector<saml2::Assertion*>::const_iterator> assert = make_indirect_iterator(asserts.begin());
+ assert != make_indirect_iterator(asserts.end()); ++assert) {
+ if (!(assert->getSignature())) {
+ if (m_log.isDebugEnabled()) {
+ auto_ptr_char eid(entityID);
+ m_log.debug("skipping unsigned assertion in metadata extension for entity (%s)", eid.get());
+ }
+ continue;
+ }
+ else if (assert->getAttributeStatements().empty()) {
+ if (m_log.isDebugEnabled()) {
+ auto_ptr_char eid(entityID);
+ m_log.debug("skipping assertion with no AttributeStatement in metadata extension for entity (%s)", eid.get());
+ }
+ continue;
+ }
+ else {
+ // Check subject.
+ const NameID* subject = assert->getSubject() ? assert->getSubject()->getNameID() : nullptr;
+ if (!subject ||
+ !XMLString::equals(subject->getFormat(), NameID::ENTITY) ||
+ !XMLString::equals(subject->getName(), entityID)) {
+ if (m_log.isDebugEnabled()) {
+ auto_ptr_char eid(entityID);
+ m_log.debug("skipping assertion with improper Subject in metadata extension for entity (%s)", eid.get());
+ }
+ continue;
+ }
+ }
+
+ try {
+ // Set up and evaluate a policy for an AA asserting attributes to us.
+ shibsp::SecurityPolicy policy(application, &AttributeAuthorityDescriptor::ELEMENT_QNAME, false, m_policyId.c_str());
+ Locker locker(m_metadata.get());
+ if (m_metadata)
+ policy.setMetadataProvider(m_metadata.get());
+ if (m_trust)
+ policy.setTrustEngine(m_trust.get());
+ // Populate recipient as audience.
+ const XMLCh* issuer = assert->getIssuer() ? assert->getIssuer()->getName() : nullptr;
+ policy.getAudiences().push_back(application.getRelyingParty(issuer)->getXMLString("entityID").second);
+
+ // Extract assertion information for policy.
+ policy.setMessageID(assert->getID());
+ policy.setIssueInstant(assert->getIssueInstantEpoch());
+ policy.setIssuer(assert->getIssuer());
+
+ // Look up metadata for issuer.
+ if (policy.getIssuer() && policy.getMetadataProvider()) {
+ if (policy.getIssuer()->getFormat() && !XMLString::equals(policy.getIssuer()->getFormat(), saml2::NameIDType::ENTITY)) {
+ m_log.debug("non-system entity issuer, skipping metadata lookup");
+ }
+ else {
+ m_log.debug("searching metadata for entity assertion issuer...");
+ pair<const EntityDescriptor*,const RoleDescriptor*> lookup;
+ MetadataProvider::Criteria& mc = policy.getMetadataProviderCriteria();
+ mc.entityID_unicode = policy.getIssuer()->getName();
+ mc.role = &AttributeAuthorityDescriptor::ELEMENT_QNAME;
+ mc.protocol = samlconstants::SAML20P_NS;
+ lookup = policy.getMetadataProvider()->getEntityDescriptor(mc);
+ if (!lookup.first) {
+ auto_ptr_char iname(policy.getIssuer()->getName());
+ m_log.debug("no metadata found, can't establish identity of issuer (%s)", iname.get());
+ }
+ else if (!lookup.second) {
+ m_log.debug("unable to find compatible AA role in metadata");
+ }
+ else {
+ policy.setIssuerMetadata(lookup.second);
+ }
+ }
+ }
+
+ // Authenticate the assertion. We have to clone and marshall it to establish the signature for verification.
+ scoped_ptr<saml2::Assertion> tokencopy(assert->cloneAssertion());
+ tokencopy->marshall();
+ policy.evaluate(*tokencopy);
+ if (!policy.isAuthenticated()) {
+ if (m_log.isDebugEnabled()) {
+ auto_ptr_char tempid(tokencopy->getID());
+ auto_ptr_char eid(entityID);
+ m_log.debug(
+ "failed to authenticate assertion (%s) in metadata extension for entity (%s)", tempid.get(), eid.get()
+ );
+ }
+ continue;
+ }
+
+ // Override the asserting/relying party names based on this new issuer.
+ const EntityDescriptor* inlineEntity =
+ policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : nullptr;
+ auto_ptr_char inlineAssertingParty(inlineEntity ? inlineEntity->getEntityID() : nullptr);
+ relyingParty = application.getRelyingParty(inlineEntity)->getString("entityID").second;
+
+ // Use a private holding area for filtering purposes.
+ ptr_vector<Attribute> holding2;
+ const vector<saml2::Attribute*>& attrs2 =
+ const_cast<const saml2::AttributeStatement*>(tokencopy->getAttributeStatements().front())->getAttributes();
+ for_each(
+ make_indirect_iterator(attrs2.begin()), make_indirect_iterator(attrs2.end()),
+ boost::bind(extractV2Attr, this, boost::ref(application), request, inlineAssertingParty.get(), relyingParty, _1, boost::ref(holding2))
+ );
+
+ // Now we locally filter the attributes so that the actual issuer can be properly set.
+ // If we relied on outside filtering, the attributes couldn't be distinguished from the
+ // ones that come from the user's IdP.
+ if (m_filter && !holding2.empty()) {
+
+ // The filter API uses an unsafe container, so we have to transfer everything into one and back.
+ vector<Attribute*> unsafe_holding2;
+
+ // Use a local exception context since the container is unsafe.
+ try {
+ while (!holding2.empty()) {
+ ptr_vector<Attribute>::auto_type ptr = holding2.pop_back();
+ unsafe_holding2.push_back(ptr.get());
+ ptr.release();
+ }
+ BasicFilteringContext fc(application, unsafe_holding2, policy.getIssuerMetadata());
+ Locker filtlocker(m_filter.get());
+ m_filter->filterAttributes(fc, unsafe_holding2);
+
+ // Transfer back to safe container
+ while (!unsafe_holding2.empty()) {
+ auto_ptr<Attribute> ptr(unsafe_holding2.back());
+ unsafe_holding2.pop_back();
+ holding2.push_back(ptr.get());
+ ptr.release();
+ }
+ }
+ catch (std::exception& ex) {
+ m_log.error("caught exception filtering attributes: %s", ex.what());
+ m_log.error("dumping extracted attributes due to filtering exception");
+ for_each(unsafe_holding2.begin(), unsafe_holding2.end(), xmltooling::cleanup<Attribute>());
+ holding2.clear(); // in case the exception was during transfer between containers
+ }
+ }
+
+ if (!holding2.empty()) {
+ // Copy them over to the main holding tank, which transfers ownership.
+ holding.transfer(holding.end(), holding2);
+ }
+ }
+ catch (std::exception& ex) {
+ // Known exceptions are handled gracefully by skipping the assertion.
+ if (m_log.isDebugEnabled()) {
+ auto_ptr_char tempid(assert->getID());
+ auto_ptr_char eid(entityID);
+ m_log.debug(
+ "exception authenticating assertion (%s) in metadata extension for entity (%s): %s",
+ tempid.get(),
+ eid.get(),
+ ex.what()
+ );
+ }
+ continue;
+ }
+ }
+ }
+
+ if (!holding.empty()) {
+ if (useCache) {
+ locker.release(); // unguard to upgrade lock
+ m_attrLock->unlock();
+ m_attrLock->wrlock();
+ SharedLock locker2(m_attrLock, false); // pop the lock when we're done
+ if (cacheEntry->second.count(container) == 0) {
+ static void (vector<DDF>::* push_back)(DDF const &) = &vector<DDF>::push_back;
+ vector<DDF>& marshalled = cacheEntry->second[container];
+ for_each(
+ holding.begin(), holding.end(),
+ boost::bind(push_back, boost::ref(marshalled), boost::bind(&Attribute::marshall, _1))
+ );
+ }
+ }
+
+ // Copy them to the output parameter, which transfers ownership.
+ attributes.transfer(attributes.end(), holding);
+ }
+
+ // If the lock is held, it's guarded.
+
+ break; // only process a single extension element