2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * XMLAttributeFilter.cpp
20 * AttributeFilter based on an XML policy language.
24 #include "exceptions.h"
25 #include "Application.h"
26 #include "ServiceProvider.h"
27 #include "attribute/Attribute.h"
28 #include "attribute/filtering/AttributeFilter.h"
29 #include "attribute/filtering/FilteringContext.h"
30 #include "attribute/filtering/FilterPolicyContext.h"
31 #include "attribute/filtering/MatchFunctor.h"
32 #include "util/SPConstants.h"
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/ReloadableXMLFile.h>
36 #include <xmltooling/util/Threads.h>
37 #include <xmltooling/util/XMLHelper.h>
38 #include <xercesc/util/XMLUniDefs.hpp>
40 using shibspconstants::SHIB2ATTRIBUTEFILTER_NS;
41 using namespace shibsp;
42 using namespace opensaml::saml2md;
43 using namespace opensaml;
44 using namespace xmltooling;
49 #if defined (_MSC_VER)
50 #pragma warning( push )
51 #pragma warning( disable : 4250 )
54 // Each Policy has a functor for determining applicability and a map of
55 // attribute IDs to Accept/Deny functor pairs (which can include NULLs).
56 struct SHIBSP_DLLLOCAL Policy
58 Policy() : m_applies(NULL) {}
59 const MatchFunctor* m_applies;
60 typedef multimap< string,pair<const MatchFunctor*,const MatchFunctor*> > rules_t;
64 class SHIBSP_DLLLOCAL XMLFilterImpl
67 XMLFilterImpl(const DOMElement* e, Category& log);
70 m_document->release();
71 for_each(m_policyReqRules.begin(), m_policyReqRules.end(), cleanup_pair<string,MatchFunctor>());
72 for_each(m_permitValRules.begin(), m_permitValRules.end(), cleanup_pair<string,MatchFunctor>());
73 for_each(m_denyValRules.begin(), m_denyValRules.end(), cleanup_pair<string,MatchFunctor>());
76 void setDocument(DOMDocument* doc) {
80 void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const;
83 MatchFunctor* buildFunctor(
84 const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
86 pair< string,pair<const MatchFunctor*,const MatchFunctor*> > buildAttributeRule(
87 const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
91 DOMDocument* m_document;
92 vector<Policy> m_policies;
93 map< string,pair<string,pair<const MatchFunctor*,const MatchFunctor*> > > m_attrRules;
94 multimap<string,MatchFunctor*> m_policyReqRules;
95 multimap<string,MatchFunctor*> m_permitValRules;
96 multimap<string,MatchFunctor*> m_denyValRules;
99 class SHIBSP_DLLLOCAL XMLFilter : public AttributeFilter, public ReloadableXMLFile
102 XMLFilter(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeFilter")), m_impl(NULL) {
109 void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const {
110 m_impl->filterAttributes(context, attributes);
114 pair<bool,DOMElement*> background_load();
117 XMLFilterImpl* m_impl;
120 #if defined (_MSC_VER)
121 #pragma warning( pop )
124 AttributeFilter* SHIBSP_DLLLOCAL XMLAttributeFilterFactory(const DOMElement* const & e)
126 return new XMLFilter(e);
129 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);
130 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);
131 static const XMLCh AttributeRule[] = UNICODE_LITERAL_13(A,t,t,r,i,b,u,t,e,R,u,l,e);
132 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);
133 static const XMLCh DenyValueRule[] = UNICODE_LITERAL_13(D,e,n,y,V,a,l,u,e,R,u,l,e);
134 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);
135 static const XMLCh PermitValueRule[] = UNICODE_LITERAL_15(P,e,r,m,i,t,V,a,l,u,e,R,u,l,e);
136 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);
137 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);
138 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);
139 static const XMLCh attributeID[] = UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,D);
140 static const XMLCh _id[] = UNICODE_LITERAL_2(i,d);
141 static const XMLCh _ref[] = UNICODE_LITERAL_3(r,e,f);
144 XMLFilterImpl::XMLFilterImpl(const DOMElement* e, Category& log) : m_log(log), m_document(NULL)
147 xmltooling::NDC ndc("XMLFilterImpl");
150 if (!XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicyGroup))
151 throw ConfigurationException("XML AttributeFilter requires afp:AttributeFilterPolicyGroup at root of configuration.");
153 FilterPolicyContext reqFunctors(m_policyReqRules);
154 FilterPolicyContext permFunctors(m_permitValRules);
155 FilterPolicyContext denyFunctors(m_denyValRules);
157 DOMElement* child = XMLHelper::getFirstChildElement(e);
159 if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
160 buildFunctor(child, reqFunctors, "PolicyRequirementRule", true);
162 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
163 buildFunctor(child, permFunctors, "PermitValueRule", true);
165 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
166 buildFunctor(child, denyFunctors, "DenyValueRule", true);
168 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
169 buildAttributeRule(child, permFunctors, denyFunctors, true);
171 else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicy)) {
172 e = XMLHelper::getFirstChildElement(child);
173 MatchFunctor* func = NULL;
174 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
175 func = buildFunctor(e, reqFunctors, "PolicyRequirementRule", false);
177 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRuleReference)) {
178 auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
179 if (ref.get() && *ref.get()) {
180 multimap<string,MatchFunctor*>::const_iterator prr = m_policyReqRules.find(ref.get());
181 func = (prr!=m_policyReqRules.end()) ? prr->second : NULL;
185 m_policies.push_back(Policy());
186 m_policies.back().m_applies = func;
187 e = XMLHelper::getNextSiblingElement(e);
189 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
190 pair< string,pair<const MatchFunctor*,const MatchFunctor*> > rule = buildAttributeRule(e, permFunctors, denyFunctors, false);
191 if (rule.second.first || rule.second.second)
192 m_policies.back().m_rules.insert(Policy::rules_t::value_type(rule.first, rule.second));
194 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRuleReference)) {
195 auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
196 if (ref.get() && *ref.get()) {
197 map< string,pair< string,pair< const MatchFunctor*,const MatchFunctor*> > >::const_iterator ar = m_attrRules.find(ref.get());
198 if (ar != m_attrRules.end())
199 m_policies.back().m_rules.insert(Policy::rules_t::value_type(ar->second.first, ar->second.second));
201 m_log.warn("skipping invalid AttributeRuleReference (%s)", ref.get());
204 e = XMLHelper::getNextSiblingElement(e);
208 m_log.warn("skipping AttributeFilterPolicy, PolicyRequirementRule invalid or missing");
211 child = XMLHelper::getNextSiblingElement(child);
215 MatchFunctor* XMLFilterImpl::buildFunctor(
216 const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
219 auto_ptr_char temp(e->getAttributeNS(NULL,_id));
220 const char* id = (temp.get() && *temp.get()) ? temp.get() : "";
222 if (standalone && !*id) {
223 m_log.warn("skipping stand-alone %s with no id", logname);
226 else if (*id && functorMap.getMatchFunctors().count(id)) {
228 m_log.warn("skipping duplicate stand-alone %s with id (%s)", logname, id);
235 auto_ptr<xmltooling::QName> type(XMLHelper::getXSIType(e));
238 MatchFunctor* func = SPConfig::getConfig().MatchFunctorManager.newPlugin(*type.get(), make_pair(&functorMap,e));
239 functorMap.getMatchFunctors().insert(multimap<string,MatchFunctor*>::value_type(id, func));
242 catch (exception& ex) {
243 m_log.error("error building %s with type (%s): %s", logname, type->toString().c_str(), ex.what());
247 m_log.warn("skipping stand-alone %s with no xsi:type", logname);
249 m_log.error("%s with no xsi:type", logname);
254 pair< string,pair<const MatchFunctor*,const MatchFunctor*> > XMLFilterImpl::buildAttributeRule(
255 const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
258 auto_ptr_char temp(e->getAttributeNS(NULL,_id));
259 const char* id = (temp.get() && *temp.get()) ? temp.get() : "";
261 if (standalone && !*id) {
262 m_log.warn("skipping stand-alone AttributeRule with no id");
263 return make_pair(string(),pair<const MatchFunctor*,const MatchFunctor*>(NULL,NULL));
265 else if (*id && m_attrRules.count(id)) {
267 m_log.warn("skipping duplicate stand-alone AttributeRule with id (%s)", id);
268 return make_pair(string(),pair<const MatchFunctor*,const MatchFunctor*>(NULL,NULL));
274 auto_ptr_char attrID(e->getAttributeNS(NULL,attributeID));
275 if (!attrID.get() || !*attrID.get())
276 m_log.warn("skipping AttributeRule with no attributeID");
278 MatchFunctor* perm=NULL;
279 MatchFunctor* deny=NULL;
281 e = XMLHelper::getFirstChildElement(e);
282 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
283 perm = buildFunctor(e, permMap, "PermitValueRule", false);
284 e = XMLHelper::getNextSiblingElement(e);
286 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRuleReference)) {
287 auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
288 if (ref.get() && *ref.get()) {
289 multimap<string,MatchFunctor*>::const_iterator pvr = m_permitValRules.find(ref.get());
290 perm = (pvr!=m_permitValRules.end()) ? pvr->second : NULL;
292 e = XMLHelper::getNextSiblingElement(e);
295 if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
296 deny = buildFunctor(e, denyMap, "DenyValueRule", false);
298 else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRuleReference)) {
299 auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
300 if (ref.get() && *ref.get()) {
301 multimap<string,MatchFunctor*>::const_iterator pvr = m_denyValRules.find(ref.get());
302 deny = (pvr!=m_denyValRules.end()) ? pvr->second : NULL;
308 m_attrRules[id] = pair< string,pair<const MatchFunctor*,const MatchFunctor*> >(attrID.get(), pair<const MatchFunctor*,const MatchFunctor*>(perm,deny));
309 return m_attrRules[id];
312 return pair< string,pair<const MatchFunctor*,const MatchFunctor*> >(attrID.get(), pair<const MatchFunctor*,const MatchFunctor*>(perm,deny));
317 m_log.warn("skipping AttributeRule (%s), permit and denial rule(s) invalid or missing", id);
319 m_log.warn("skipping AttributeRule, permit and denial rule(s) invalid or missing");
320 return pair< string,pair<const MatchFunctor*,const MatchFunctor*> >(string(),pair<const MatchFunctor*,const MatchFunctor*>(NULL,NULL));
323 void XMLFilterImpl::filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const
325 auto_ptr_char issuer(context.getAttributeIssuer());
327 m_log.debug("filtering %lu attribute(s) from (%s)", attributes.size(), issuer.get() ? issuer.get() : "unknown source");
329 if (m_policies.empty()) {
330 m_log.warn("no filter policies were loaded, filtering out all attributes from (%s)", issuer.get() ? issuer.get() : "unknown source");
331 for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
336 // We have to evaluate every policy that applies against each attribute before deciding what to keep.
338 // For efficiency, we build an array of the policies that apply in advance.
339 vector<const Policy*> applicablePolicies;
340 for (vector<Policy>::const_iterator p=m_policies.begin(); p!=m_policies.end(); ++p) {
341 if (p->m_applies->evaluatePolicyRequirement(context))
342 applicablePolicies.push_back(&(*p));
345 // For further efficiency, we declare arrays to store the applicable rules for an Attribute.
346 vector< pair<const MatchFunctor*,const MatchFunctor*> > applicableRules;
347 vector< pair<const MatchFunctor*,const MatchFunctor*> > wildcardRules;
349 // Store off the wildcards ahead of time.
350 for (vector<const Policy*>::const_iterator pol=applicablePolicies.begin(); pol!=applicablePolicies.end(); ++pol) {
351 pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = (*pol)->m_rules.equal_range("*");
352 for (; rules.first!=rules.second; ++rules.first)
353 wildcardRules.push_back(rules.first->second);
356 // To track what to keep without removing anything from the original set until the end, we maintain
357 // a map of each Attribute object to a boolean array with true flags indicating what to delete.
358 // A single dimension array tracks attributes being removed entirely.
359 vector<bool> deletedAttributes(attributes.size(), false);
360 map< Attribute*, vector<bool> > deletedPositions;
362 // Loop over each attribute to filter them.
363 for (vector<Attribute*>::size_type a=0; a<attributes.size(); ++a) {
364 Attribute* attr = attributes[a];
366 // Clear the rule store.
367 applicableRules.clear();
369 // Look for rules to run in each policy.
370 for (vector<const Policy*>::const_iterator pol=applicablePolicies.begin(); pol!=applicablePolicies.end(); ++pol) {
371 pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = (*pol)->m_rules.equal_range(attr->getId());
372 for (; rules.first!=rules.second; ++rules.first)
373 applicableRules.push_back(rules.first->second);
376 // If no rules found, apply wildcards.
377 const vector< pair<const MatchFunctor*,const MatchFunctor*> >& rulesToRun = applicableRules.empty() ? wildcardRules : applicableRules;
379 // If no rules apply, remove the attribute entirely.
380 if (rulesToRun.empty()) {
382 "no rule found, removing attribute (%s) from (%s)",
383 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
385 deletedAttributes[a] = true;
389 // Run each permit/deny rule.
391 "applying filtering rule(s) for attribute (%s) from (%s)",
392 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
397 // Examine each value.
398 for (size_t count = attr->valueCount(), index = 0; index < count; ++index) {
400 // Assume we're kicking it out.
403 for (vector< pair<const MatchFunctor*,const MatchFunctor*> >::const_iterator r=rulesToRun.begin(); r!=rulesToRun.end(); ++r) {
404 // If there's a permit rule that passes, don't kick it.
405 if (r->first && r->first->evaluatePermitValue(context, *attr, index))
407 if (!kickit && r->second && r->second->evaluatePermitValue(context, *attr, index))
411 // If we're kicking it, record that in the tracker.
414 "removed value at position (%lu) of attribute (%s) from (%s)",
415 index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
417 deletedPositions[attr].resize(index+1);
418 deletedPositions[attr][index] = true;
423 // Final step: go over the deletedPositions matrix and apply the actual changes. In order to delete
424 // any attributes that end up with no values, we have to do it by looping over the originals.
425 for (vector<Attribute*>::size_type a=0; a<attributes.size();) {
426 Attribute* attr = attributes[a];
428 if (deletedAttributes[a]) {
430 deletedAttributes.erase(deletedAttributes.begin() + a);
431 attributes.erase(attributes.begin() + a);
434 else if (deletedPositions.count(attr) > 0) {
435 // To do the removal, we loop over the bits backwards so that the
436 // underlying value sequence doesn't get distorted by any removals.
437 // Index has to be offset by one because size_type is unsigned.
438 const vector<bool>& row = deletedPositions[attr];
439 for (vector<bool>::size_type index = row.size(); index > 0; --index) {
441 attr->removeValue(index-1);
445 // Check for no values.
446 if (attr->valueCount() == 0) {
448 "no values left, removing attribute (%s) from (%s)",
449 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
452 attributes.erase(attributes.begin() + a);
460 pair<bool,DOMElement*> XMLFilter::background_load()
462 // Load from source using base class.
463 pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
465 // If we own it, wrap it.
466 XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
468 XMLFilterImpl* impl = new XMLFilterImpl(raw.second, m_log);
470 // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
471 impl->setDocument(docjanitor.release());
473 // Perform the swap inside a lock.
476 SharedLock locker(m_lock, false);
480 return make_pair(false,(DOMElement*)NULL);