Boost changes
[shibboleth/cpp-sp.git] / shibsp / attribute / filtering / impl / XMLAttributeFilter.cpp
1 /**
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.
6  *
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
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
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.
19  */
20
21 /**
22  * XMLAttributeFilter.cpp
23  *
24  * AttributeFilter based on an XML policy language.
25  */
26
27 #include "internal.h"
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"
37
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>
46
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;
53 using namespace std;
54
55 namespace shibsp {
56
57 #if defined (_MSC_VER)
58     #pragma warning( push )
59     #pragma warning( disable : 4250 )
60 #endif
61
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
65     {
66         Policy() : m_applies(nullptr) {}
67         const MatchFunctor* m_applies;
68         typedef multimap< string,pair<const MatchFunctor*,const MatchFunctor*> > rules_t;
69         rules_t m_rules;
70     };
71
72     class SHIBSP_DLLLOCAL XMLFilterImpl
73     {
74     public:
75         XMLFilterImpl(const DOMElement* e, Category& log);
76         ~XMLFilterImpl() {
77             if (m_document)
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>());
82         }
83
84         void setDocument(DOMDocument* doc) {
85             m_document = doc;
86         }
87
88         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const;
89
90     private:
91         MatchFunctor* buildFunctor(
92             const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
93             );
94         tuple<string,const MatchFunctor*,const MatchFunctor*> buildAttributeRule(
95             const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
96             );
97
98         Category& m_log;
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;
105     };
106
107     class SHIBSP_DLLLOCAL XMLFilter : public AttributeFilter, public ReloadableXMLFile
108     {
109     public:
110         XMLFilter(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeFilter")) {
111             background_load();
112         }
113         ~XMLFilter() {
114             shutdown();
115         }
116
117         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const {
118             m_impl->filterAttributes(context, attributes);
119         }
120
121     protected:
122         pair<bool,DOMElement*> background_load();
123
124     private:
125         scoped_ptr<XMLFilterImpl> m_impl;
126     };
127
128 #if defined (_MSC_VER)
129     #pragma warning( pop )
130 #endif
131
132     AttributeFilter* SHIBSP_DLLLOCAL XMLAttributeFilterFactory(const DOMElement* const & e)
133     {
134         return new XMLFilter(e);
135     }
136
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);
150 };
151
152 XMLFilterImpl::XMLFilterImpl(const DOMElement* e, Category& log) : m_log(log), m_document(nullptr)
153 {
154 #ifdef _DEBUG
155     xmltooling::NDC ndc("XMLFilterImpl");
156 #endif
157
158     if (!XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicyGroup))
159         throw ConfigurationException("XML AttributeFilter requires afp:AttributeFilterPolicyGroup at root of configuration.");
160
161     FilterPolicyContext reqFunctors(m_policyReqRules);
162     FilterPolicyContext permFunctors(m_permitValRules);
163     FilterPolicyContext denyFunctors(m_denyValRules);
164
165     DOMElement* child = XMLHelper::getFirstChildElement(e);
166     while (child) {
167         if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
168             buildFunctor(child, reqFunctors, "PolicyRequirementRule", true);
169         }
170         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
171             buildFunctor(child, permFunctors, "PermitValueRule", true);
172         }
173         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
174             buildFunctor(child, denyFunctors, "DenyValueRule", true);
175         }
176         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
177             buildAttributeRule(child, permFunctors, denyFunctors, true);
178         }
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);
184             }
185             else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRuleReference)) {
186                 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
187                 if (!ref.empty()) {
188                     multimap<string,MatchFunctor*>::const_iterator prr = m_policyReqRules.find(ref);
189                     func = (prr!=m_policyReqRules.end()) ? prr->second : nullptr;
190                 }
191             }
192             if (func) {
193                 m_policies.push_back(Policy());
194                 m_policies.back().m_applies = func;
195                 e = XMLHelper::getNextSiblingElement(e);
196                 while (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>())));
201                     }
202                     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRuleReference)) {
203                         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
204                         if (!ref.empty()) {
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>()))
209                                     );
210                             }
211                             else {
212                                 m_log.warn("skipping invalid AttributeRuleReference (%s)", ref.c_str());
213                             }
214                         }
215                     }
216                     e = XMLHelper::getNextSiblingElement(e);
217                 }
218             }
219             else {
220                 m_log.warn("skipping AttributeFilterPolicy, PolicyRequirementRule invalid or missing");
221             }
222         }
223         child = XMLHelper::getNextSiblingElement(child);
224     }
225 }
226
227 MatchFunctor* XMLFilterImpl::buildFunctor(
228     const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
229     )
230 {
231     string id(XMLHelper::getAttrString(e, nullptr, _id));
232
233     if (standalone && id.empty()) {
234         m_log.warn("skipping stand-alone %s with no id", logname);
235         return nullptr;
236     }
237     else if (!id.empty() && functorMap.getMatchFunctors().count(id)) {
238         if (standalone) {
239             m_log.warn("skipping duplicate stand-alone %s with id (%s)", logname, id.c_str());
240             return nullptr;
241         }
242         else
243             id.clear();
244     }
245
246     scoped_ptr<xmltooling::QName> type(XMLHelper::getXSIType(e));
247     if (type) {
248         try {
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();
252         }
253         catch (exception& ex) {
254             m_log.error("error building %s with type (%s): %s", logname, type->toString().c_str(), ex.what());
255         }
256     }
257     else if (standalone)
258         m_log.warn("skipping stand-alone %s with no xsi:type", logname);
259     else
260         m_log.error("%s with no xsi:type", logname);
261
262     return nullptr;
263 }
264
265 tuple<string,const MatchFunctor*,const MatchFunctor*> XMLFilterImpl::buildAttributeRule(
266     const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
267     )
268 {
269     string id(XMLHelper::getAttrString(e, nullptr, _id));
270
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);
274     }
275     else if (!id.empty() && m_attrRules.count(id)) {
276         if (standalone) {
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);
279         }
280         else
281             id.clear();
282     }
283
284     string attrID(XMLHelper::getAttrString(e, nullptr, attributeID));
285     if (attrID.empty())
286         m_log.warn("skipping AttributeRule with no attributeID");
287
288     MatchFunctor* perm = nullptr;
289     MatchFunctor* deny = nullptr;
290
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);
295     }
296     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRuleReference)) {
297         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
298         if (!ref.empty()) {
299             multimap<string,MatchFunctor*>::const_iterator pvr = m_permitValRules.find(ref);
300             perm = (pvr!=m_permitValRules.end()) ? pvr->second : nullptr;
301         }
302         e = XMLHelper::getNextSiblingElement(e);
303     }
304
305     if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
306         deny = buildFunctor(e, denyMap, "DenyValueRule", false);
307     }
308     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRuleReference)) {
309         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
310         if (!ref.empty()) {
311             multimap<string,MatchFunctor*>::const_iterator pvr = m_denyValRules.find(ref);
312             deny = (pvr!=m_denyValRules.end()) ? pvr->second : nullptr;
313         }
314     }
315
316     if (perm || deny) {
317         if (!id.empty()) {
318             m_attrRules[id] = make_tuple(attrID, perm, deny);
319             return m_attrRules[id];
320         }
321         else {
322             return make_tuple(attrID, perm, deny);
323         }
324     }
325
326     if (!id.empty())
327         m_log.warn("skipping AttributeRule (%s), permit and denial rule(s) invalid or missing", id.c_str());
328     else
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);
331 }
332
333 void XMLFilterImpl::filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const
334 {
335     auto_ptr_char issuer(context.getAttributeIssuer());
336
337     m_log.debug("filtering %lu attribute(s) from (%s)", attributes.size(), issuer.get() ? issuer.get() : "unknown source");
338
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>());
342         attributes.clear();
343         return;
344     }
345
346     // We have to evaluate every policy that applies against each attribute before deciding what to keep.
347
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));
353     }
354
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;
358
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);
365     }
366
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;
372
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];
376
377         // Clear the rule store.
378         applicableRules.clear();
379
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);
386         }
387
388         // If no rules found, apply wildcards.
389         const vector< pair<const MatchFunctor*,const MatchFunctor*> >& rulesToRun =
390             applicableRules.empty() ? wildcardRules : applicableRules;
391
392         // If no rules apply, remove the attribute entirely.
393         if (rulesToRun.empty()) {
394             m_log.warn(
395                 "no rule found, will remove attribute (%s) from (%s)",
396                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
397                 );
398             deletedAttributes[a] = true;
399             continue;
400         }
401
402         // Run each permit/deny rule.
403         m_log.debug(
404             "applying filtering rule(s) for attribute (%s) from (%s)",
405             attr->getId(), issuer.get() ? issuer.get() : "unknown source"
406             );
407
408         bool kickit;
409
410         // Examine each value.
411         for (size_t count = attr->valueCount(), index = 0; index < count; ++index) {
412
413             // Assume we're kicking it out.
414             kickit=true;
415
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))
419                     kickit = false;
420                 if (!kickit && r->second && r->second->evaluatePermitValue(context, *attr, index))
421                     kickit = true;
422             }
423
424             // If we're kicking it, record that in the tracker.
425             if (kickit) {
426                 m_log.warn(
427                     "removed value at position (%lu) of attribute (%s) from (%s)",
428                     index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
429                     );
430                 deletedPositions[attr].resize(index+1);
431                 deletedPositions[attr][index] = true;
432             }
433         }
434     }
435
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];
440
441         if (deletedAttributes[a]) {
442             m_log.warn(
443                 "removing filtered attribute (%s) from (%s)",
444                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
445                 );
446             delete attr;
447             deletedAttributes.erase(deletedAttributes.begin() + a);
448             attributes.erase(attributes.begin() + a);
449             continue;
450         }
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) {
457                 if (row[index-1])
458                     attr->removeValue(index-1);
459             }
460         }
461
462         // Check for no values.
463         if (attr->valueCount() == 0) {
464             m_log.warn(
465                 "no values left, removing attribute (%s) from (%s)",
466                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
467                 );
468             delete attr;
469             deletedAttributes.erase(deletedAttributes.begin() + a);
470             attributes.erase(attributes.begin() + a);
471             continue;
472         }
473
474         ++a;
475     }
476 }
477
478 pair<bool,DOMElement*> XMLFilter::background_load()
479 {
480     // Load from source using base class.
481     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
482
483     // If we own it, wrap it.
484     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
485
486     scoped_ptr<XMLFilterImpl> impl(new XMLFilterImpl(raw.second, m_log));
487
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());
490
491     // Perform the swap inside a lock.
492     if (m_lock)
493         m_lock->wrlock();
494     SharedLock locker(m_lock, false);
495     m_impl.swap(impl);
496
497     return make_pair(false,(DOMElement*)nullptr);
498 }