2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
22 * XMLAttributeFilter.cpp
24 * AttributeFilter based on an XML policy language.
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "attribute/Attribute.h"
32 #include "attribute/filtering/AttributeFilter.h"
33 #include "attribute/filtering/FilteringContext.h"
34 #include "attribute/filtering/FilterPolicyContext.h"
35 #include "attribute/filtering/MatchFunctor.h"
36 #include "util/SPConstants.h"
38 #include <boost/scoped_ptr.hpp>
39 #include <boost/iterator/indirect_iterator.hpp>
40 #include <boost/tuple/tuple.hpp>
41 #include <xmltooling/util/NDC.h>
42 #include <xmltooling/util/ReloadableXMLFile.h>
43 #include <xmltooling/util/Threads.h>
44 #include <xmltooling/util/XMLHelper.h>
45 #include <xercesc/util/XMLUniDefs.hpp>
47 using shibspconstants::SHIB2ATTRIBUTEFILTER_NS;
48 using namespace shibsp;
49 using namespace opensaml::saml2md;
50 using namespace opensaml;
51 using namespace xmltooling;
52 using namespace boost;
57 #if defined (_MSC_VER)
58 #pragma warning( push )
59 #pragma warning( disable : 4250 )
62 // Each Policy has a functor for determining applicability and a map of
63 // attribute IDs to Accept/Deny functor pairs (which can include nullptrs).
64 struct SHIBSP_DLLLOCAL Policy
66 Policy() : m_applies(nullptr) {}
67 const MatchFunctor* m_applies;
68 typedef multimap< string,pair<const MatchFunctor*,const MatchFunctor*> > rules_t;
72 class SHIBSP_DLLLOCAL XMLFilterImpl
75 XMLFilterImpl(const DOMElement* e, Category& log);
78 m_document->release();
79 for_each(m_policyReqRules.begin(), m_policyReqRules.end(), cleanup_pair<string,MatchFunctor>());
80 for_each(m_permitValRules.begin(), m_permitValRules.end(), cleanup_pair<string,MatchFunctor>());
81 for_each(m_denyValRules.begin(), m_denyValRules.end(), cleanup_pair<string,MatchFunctor>());
84 void setDocument(DOMDocument* doc) {
88 void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const;
91 MatchFunctor* buildFunctor(
92 const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
94 tuple<string,const MatchFunctor*,const MatchFunctor*> buildAttributeRule(
95 const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
99 DOMDocument* m_document;
100 vector<Policy> m_policies;
101 map< string,tuple<string,const MatchFunctor*,const MatchFunctor*> > m_attrRules;
102 multimap<string,MatchFunctor*> m_policyReqRules;
103 multimap<string,MatchFunctor*> m_permitValRules;
104 multimap<string,MatchFunctor*> m_denyValRules;
107 class SHIBSP_DLLLOCAL XMLFilter : public AttributeFilter, public ReloadableXMLFile
110 XMLFilter(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeFilter")) {
117 void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const {
118 m_impl->filterAttributes(context, attributes);
122 pair<bool,DOMElement*> background_load();
125 scoped_ptr<XMLFilterImpl> m_impl;
128 #if defined (_MSC_VER)
129 #pragma warning( pop )
132 AttributeFilter* SHIBSP_DLLLOCAL XMLAttributeFilterFactory(const DOMElement* const & e)
134 return new XMLFilter(e);
137 static const XMLCh AttributeFilterPolicyGroup[] = UNICODE_LITERAL_26(A,t,t,r,i,b,u,t,e,F,i,l,t,e,r,P,o,l,i,c,y,G,r,o,u,p);
138 static const XMLCh AttributeFilterPolicy[] = UNICODE_LITERAL_21(A,t,t,r,i,b,u,t,e,F,i,l,t,e,r,P,o,l,i,c,y);
139 static const XMLCh AttributeRule[] = UNICODE_LITERAL_13(A,t,t,r,i,b,u,t,e,R,u,l,e);
140 static const XMLCh AttributeRuleReference[] = UNICODE_LITERAL_22(A,t,t,r,i,b,u,t,e,R,u,l,e,R,e,f,e,r,e,n,c,e);
141 static const XMLCh DenyValueRule[] = UNICODE_LITERAL_13(D,e,n,y,V,a,l,u,e,R,u,l,e);
142 static const XMLCh DenyValueRuleReference[] = UNICODE_LITERAL_22(D,e,n,y,V,a,l,u,e,R,u,l,e,R,e,f,e,r,e,n,c,e);
143 static const XMLCh PermitValueRule[] = UNICODE_LITERAL_15(P,e,r,m,i,t,V,a,l,u,e,R,u,l,e);
144 static const XMLCh PermitValueRuleReference[] = UNICODE_LITERAL_24(P,e,r,m,i,t,V,a,l,u,e,R,u,l,e,R,e,f,e,r,e,n,c,e);
145 static const XMLCh PolicyRequirementRule[] = UNICODE_LITERAL_21(P,o,l,i,c,y,R,e,q,u,i,r,e,m,e,n,t,R,u,l,e);
146 static const XMLCh PolicyRequirementRuleReference[]=UNICODE_LITERAL_30(P,o,l,i,c,y,R,e,q,u,i,r,e,m,e,n,t,R,u,l,e,R,e,f,e,r,e,n,c,e);
147 static const XMLCh attributeID[] = UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,D);
148 static const XMLCh _id[] = UNICODE_LITERAL_2(i,d);
149 static const XMLCh _ref[] = UNICODE_LITERAL_3(r,e,f);
152 XMLFilterImpl::XMLFilterImpl(const DOMElement* e, Category& log) : m_log(log), m_document(nullptr)
155 xmltooling::NDC ndc("XMLFilterImpl");
158 if (!XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicyGroup))
159 throw ConfigurationException("XML AttributeFilter requires afp:AttributeFilterPolicyGroup at root of configuration.");
161 FilterPolicyContext reqFunctors(m_policyReqRules);
162 FilterPolicyContext permFunctors(m_permitValRules);
163 FilterPolicyContext denyFunctors(m_denyValRules);
165 DOMElement* child = XMLHelper::getFirstChildElement(e);
167 if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
168 buildFunctor(child, reqFunctors, "PolicyRequirementRule", true);
170 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
171 buildFunctor(child, permFunctors, "PermitValueRule", true);
173 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
174 buildFunctor(child, denyFunctors, "DenyValueRule", true);
176 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
177 buildAttributeRule(child, permFunctors, denyFunctors, true);
179 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicy)) {
180 e = XMLHelper::getFirstChildElement(child);
181 MatchFunctor* func = nullptr;
182 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
183 func = buildFunctor(e, reqFunctors, "PolicyRequirementRule", false);
185 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRuleReference)) {
186 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
188 multimap<string,MatchFunctor*>::const_iterator prr = m_policyReqRules.find(ref);
189 func = (prr!=m_policyReqRules.end()) ? prr->second : nullptr;
193 m_policies.push_back(Policy());
194 m_policies.back().m_applies = func;
195 e = XMLHelper::getNextSiblingElement(e);
197 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
198 tuple<string,const MatchFunctor*,const MatchFunctor*> rule = buildAttributeRule(e, permFunctors, denyFunctors, false);
199 if (rule.get<1>() || rule.get<2>())
200 m_policies.back().m_rules.insert(Policy::rules_t::value_type(rule.get<0>(), make_pair(rule.get<1>(), rule.get<2>())));
202 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRuleReference)) {
203 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
205 map< string,tuple<string,const MatchFunctor*,const MatchFunctor*> >::const_iterator ar = m_attrRules.find(ref);
206 if (ar != m_attrRules.end()) {
207 m_policies.back().m_rules.insert(
208 Policy::rules_t::value_type(ar->second.get<0>(), make_pair(ar->second.get<1>(), ar->second.get<2>()))
212 m_log.warn("skipping invalid AttributeRuleReference (%s)", ref.c_str());
216 e = XMLHelper::getNextSiblingElement(e);
220 m_log.warn("skipping AttributeFilterPolicy, PolicyRequirementRule invalid or missing");
223 child = XMLHelper::getNextSiblingElement(child);
227 MatchFunctor* XMLFilterImpl::buildFunctor(
228 const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
231 string id(XMLHelper::getAttrString(e, nullptr, _id));
233 if (standalone && id.empty()) {
234 m_log.warn("skipping stand-alone %s with no id", logname);
237 else if (!id.empty() && functorMap.getMatchFunctors().count(id)) {
239 m_log.warn("skipping duplicate stand-alone %s with id (%s)", logname, id.c_str());
246 scoped_ptr<xmltooling::QName> type(XMLHelper::getXSIType(e));
249 auto_ptr<MatchFunctor> func(SPConfig::getConfig().MatchFunctorManager.newPlugin(*type, make_pair(&functorMap,e)));
250 functorMap.getMatchFunctors().insert(multimap<string,MatchFunctor*>::value_type(id, func.get()));
251 return func.release();
253 catch (exception& ex) {
254 m_log.error("error building %s with type (%s): %s", logname, type->toString().c_str(), ex.what());
258 m_log.warn("skipping stand-alone %s with no xsi:type", logname);
260 m_log.error("%s with no xsi:type", logname);
265 tuple<string,const MatchFunctor*,const MatchFunctor*> XMLFilterImpl::buildAttributeRule(
266 const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
269 string id(XMLHelper::getAttrString(e, nullptr, _id));
271 if (standalone && id.empty()) {
272 m_log.warn("skipping stand-alone AttributeRule with no id");
273 return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
275 else if (!id.empty() && m_attrRules.count(id)) {
277 m_log.warn("skipping duplicate stand-alone AttributeRule with id (%s)", id.c_str());
278 return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
284 string attrID(XMLHelper::getAttrString(e, nullptr, attributeID));
286 m_log.warn("skipping AttributeRule with no attributeID");
288 MatchFunctor* perm = nullptr;
289 MatchFunctor* deny = nullptr;
291 e = XMLHelper::getFirstChildElement(e);
292 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
293 perm = buildFunctor(e, permMap, "PermitValueRule", false);
294 e = XMLHelper::getNextSiblingElement(e);
296 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRuleReference)) {
297 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
299 multimap<string,MatchFunctor*>::const_iterator pvr = m_permitValRules.find(ref);
300 perm = (pvr!=m_permitValRules.end()) ? pvr->second : nullptr;
302 e = XMLHelper::getNextSiblingElement(e);
305 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
306 deny = buildFunctor(e, denyMap, "DenyValueRule", false);
308 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRuleReference)) {
309 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
311 multimap<string,MatchFunctor*>::const_iterator pvr = m_denyValRules.find(ref);
312 deny = (pvr!=m_denyValRules.end()) ? pvr->second : nullptr;
318 m_attrRules[id] = make_tuple(attrID, perm, deny);
319 return m_attrRules[id];
322 return make_tuple(attrID, perm, deny);
327 m_log.warn("skipping AttributeRule (%s), permit and denial rule(s) invalid or missing", id.c_str());
329 m_log.warn("skipping AttributeRule, permit and denial rule(s) invalid or missing");
330 return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
333 void XMLFilterImpl::filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const
335 auto_ptr_char issuer(context.getAttributeIssuer());
337 m_log.debug("filtering %lu attribute(s) from (%s)", attributes.size(), issuer.get() ? issuer.get() : "unknown source");
339 if (m_policies.empty()) {
340 m_log.warn("no filter policies were loaded, filtering out all attributes from (%s)", issuer.get() ? issuer.get() : "unknown source");
341 for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
346 // We have to evaluate every policy that applies against each attribute before deciding what to keep.
348 // For efficiency, we build an array of the policies that apply in advance.
349 vector<const Policy*> applicablePolicies;
350 for (vector<Policy>::const_iterator p = m_policies.begin(); p != m_policies.end(); ++p) {
351 if (p->m_applies->evaluatePolicyRequirement(context))
352 applicablePolicies.push_back(&(*p));
355 // For further efficiency, we declare arrays to store the applicable rules for an Attribute.
356 vector< pair<const MatchFunctor*,const MatchFunctor*> > applicableRules;
357 vector< pair<const MatchFunctor*,const MatchFunctor*> > wildcardRules;
359 // Store off the wildcards ahead of time.
360 for (indirect_iterator<vector<const Policy*>::const_iterator> pol = make_indirect_iterator(applicablePolicies.begin());
361 pol != make_indirect_iterator(applicablePolicies.end()); ++pol) {
362 pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = pol->m_rules.equal_range("*");
363 for (; rules.first!=rules.second; ++rules.first)
364 wildcardRules.push_back(rules.first->second);
367 // To track what to keep without removing anything from the original set until the end, we maintain
368 // a map of each Attribute object to a boolean array with true flags indicating what to delete.
369 // A single dimension array tracks attributes being removed entirely.
370 vector<bool> deletedAttributes(attributes.size(), false);
371 map< Attribute*, vector<bool> > deletedPositions;
373 // Loop over each attribute to filter them.
374 for (vector<Attribute*>::size_type a = 0; a < attributes.size(); ++a) {
375 Attribute* attr = attributes[a];
377 // Clear the rule store.
378 applicableRules.clear();
380 // Look for rules to run in each policy.
381 for (indirect_iterator<vector<const Policy*>::const_iterator> pol = make_indirect_iterator(applicablePolicies.begin());
382 pol != make_indirect_iterator(applicablePolicies.end()); ++pol) {
383 pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = pol->m_rules.equal_range(attr->getId());
384 for (; rules.first!=rules.second; ++rules.first)
385 applicableRules.push_back(rules.first->second);
388 // If no rules found, apply wildcards.
389 const vector< pair<const MatchFunctor*,const MatchFunctor*> >& rulesToRun =
390 applicableRules.empty() ? wildcardRules : applicableRules;
392 // If no rules apply, remove the attribute entirely.
393 if (rulesToRun.empty()) {
395 "no rule found, will remove attribute (%s) from (%s)",
396 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
398 deletedAttributes[a] = true;
402 // Run each permit/deny rule.
404 "applying filtering rule(s) for attribute (%s) from (%s)",
405 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
410 // Examine each value.
411 for (size_t count = attr->valueCount(), index = 0; index < count; ++index) {
413 // Assume we're kicking it out.
416 for (vector< pair<const MatchFunctor*,const MatchFunctor*> >::const_iterator r = rulesToRun.begin(); r != rulesToRun.end(); ++r) {
417 // If there's a permit rule that passes, don't kick it.
418 if (r->first && r->first->evaluatePermitValue(context, *attr, index))
420 if (!kickit && r->second && r->second->evaluatePermitValue(context, *attr, index))
424 // If we're kicking it, record that in the tracker.
427 "removed value at position (%lu) of attribute (%s) from (%s)",
428 index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
430 deletedPositions[attr].resize(index+1);
431 deletedPositions[attr][index] = true;
436 // Final step: go over the deletedPositions matrix and apply the actual changes. In order to delete
437 // any attributes that end up with no values, we have to do it by looping over the originals.
438 for (vector<Attribute*>::size_type a = 0; a < attributes.size();) {
439 Attribute* attr = attributes[a];
441 if (deletedAttributes[a]) {
443 "removing filtered attribute (%s) from (%s)",
444 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
447 deletedAttributes.erase(deletedAttributes.begin() + a);
448 attributes.erase(attributes.begin() + a);
451 else if (deletedPositions.count(attr) > 0) {
452 // To do the removal, we loop over the bits backwards so that the
453 // underlying value sequence doesn't get distorted by any removals.
454 // Index has to be offset by one because size_type is unsigned.
455 const vector<bool>& row = deletedPositions[attr];
456 for (vector<bool>::size_type index = row.size(); index > 0; --index) {
458 attr->removeValue(index-1);
462 // Check for no values.
463 if (attr->valueCount() == 0) {
465 "no values left, removing attribute (%s) from (%s)",
466 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
469 deletedAttributes.erase(deletedAttributes.begin() + a);
470 attributes.erase(attributes.begin() + a);
478 pair<bool,DOMElement*> XMLFilter::background_load()
480 // Load from source using base class.
481 pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
483 // If we own it, wrap it.
484 XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
486 scoped_ptr<XMLFilterImpl> impl(new XMLFilterImpl(raw.second, m_log));
488 // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
489 impl->setDocument(docjanitor.release());
491 // Perform the swap inside a lock.
494 SharedLock locker(m_lock, false);
497 return make_pair(false,(DOMElement*)nullptr);