e2e703c8ae8439ca07f9c1fd44fabd34fa5b0fe3
[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/iterator/indirect_iterator.hpp>
39 #include <boost/tuple/tuple.hpp>
40 #include <xmltooling/util/NDC.h>
41 #include <xmltooling/util/ReloadableXMLFile.h>
42 #include <xmltooling/util/Threads.h>
43 #include <xmltooling/util/XMLHelper.h>
44 #include <xercesc/util/XMLUniDefs.hpp>
45
46 using shibspconstants::SHIB2ATTRIBUTEFILTER_NS;
47 using namespace shibsp;
48 using namespace opensaml::saml2md;
49 using namespace opensaml;
50 using namespace xmltooling;
51 using namespace boost;
52 using namespace std;
53
54 namespace shibsp {
55
56 #if defined (_MSC_VER)
57     #pragma warning( push )
58     #pragma warning( disable : 4250 )
59 #endif
60
61     // Each Policy has a functor for determining applicability and a map of
62     // attribute IDs to Accept/Deny functor pairs (which can include nullptrs).
63     struct SHIBSP_DLLLOCAL Policy
64     {
65         Policy() : m_applies(nullptr) {}
66         const MatchFunctor* m_applies;
67         typedef multimap< string,pair<const MatchFunctor*,const MatchFunctor*> > rules_t;
68         rules_t m_rules;
69     };
70
71     class SHIBSP_DLLLOCAL XMLFilterImpl
72     {
73     public:
74         XMLFilterImpl(const DOMElement* e, Category& log);
75         ~XMLFilterImpl() {
76             if (m_document)
77                 m_document->release();
78             for_each(m_policyReqRules.begin(), m_policyReqRules.end(), cleanup_pair<string,MatchFunctor>());
79             for_each(m_permitValRules.begin(), m_permitValRules.end(), cleanup_pair<string,MatchFunctor>());
80             for_each(m_denyValRules.begin(), m_denyValRules.end(), cleanup_pair<string,MatchFunctor>());
81         }
82
83         void setDocument(DOMDocument* doc) {
84             m_document = doc;
85         }
86
87         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const;
88
89     private:
90         MatchFunctor* buildFunctor(
91             const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
92             );
93         tuple<string,const MatchFunctor*,const MatchFunctor*> buildAttributeRule(
94             const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
95             );
96
97         Category& m_log;
98         DOMDocument* m_document;
99         vector<Policy> m_policies;
100         map< string,tuple<string,const MatchFunctor*,const MatchFunctor*> > m_attrRules;
101         multimap<string,MatchFunctor*> m_policyReqRules;
102         multimap<string,MatchFunctor*> m_permitValRules;
103         multimap<string,MatchFunctor*> m_denyValRules;
104     };
105
106     class SHIBSP_DLLLOCAL XMLFilter : public AttributeFilter, public ReloadableXMLFile
107     {
108     public:
109         XMLFilter(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeFilter")) {
110             background_load();
111         }
112         ~XMLFilter() {
113             shutdown();
114         }
115
116         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const {
117             m_impl->filterAttributes(context, attributes);
118         }
119
120     protected:
121         pair<bool,DOMElement*> background_load();
122
123     private:
124         scoped_ptr<XMLFilterImpl> m_impl;
125     };
126
127 #if defined (_MSC_VER)
128     #pragma warning( pop )
129 #endif
130
131     AttributeFilter* SHIBSP_DLLLOCAL XMLAttributeFilterFactory(const DOMElement* const & e)
132     {
133         return new XMLFilter(e);
134     }
135
136     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);
137     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);
138     static const XMLCh AttributeRule[] =                UNICODE_LITERAL_13(A,t,t,r,i,b,u,t,e,R,u,l,e);
139     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);
140     static const XMLCh DenyValueRule[] =                UNICODE_LITERAL_13(D,e,n,y,V,a,l,u,e,R,u,l,e);
141     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);
142     static const XMLCh PermitValueRule[] =              UNICODE_LITERAL_15(P,e,r,m,i,t,V,a,l,u,e,R,u,l,e);
143     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);
144     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);
145     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);
146     static const XMLCh attributeID[] =                  UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,D);
147     static const XMLCh _id[] =                          UNICODE_LITERAL_2(i,d);
148     static const XMLCh _ref[] =                         UNICODE_LITERAL_3(r,e,f);
149 };
150
151 XMLFilterImpl::XMLFilterImpl(const DOMElement* e, Category& log) : m_log(log), m_document(nullptr)
152 {
153 #ifdef _DEBUG
154     xmltooling::NDC ndc("XMLFilterImpl");
155 #endif
156
157     if (!XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicyGroup))
158         throw ConfigurationException("XML AttributeFilter requires afp:AttributeFilterPolicyGroup at root of configuration.");
159
160     FilterPolicyContext reqFunctors(m_policyReqRules);
161     FilterPolicyContext permFunctors(m_permitValRules);
162     FilterPolicyContext denyFunctors(m_denyValRules);
163
164     DOMElement* child = XMLHelper::getFirstChildElement(e);
165     while (child) {
166         if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
167             buildFunctor(child, reqFunctors, "PolicyRequirementRule", true);
168         }
169         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
170             buildFunctor(child, permFunctors, "PermitValueRule", true);
171         }
172         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
173             buildFunctor(child, denyFunctors, "DenyValueRule", true);
174         }
175         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
176             buildAttributeRule(child, permFunctors, denyFunctors, true);
177         }
178         else if (XMLHelper::isNodeNamed(child, SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicy)) {
179             e = XMLHelper::getFirstChildElement(child);
180             MatchFunctor* func = nullptr;
181             if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
182                 func = buildFunctor(e, reqFunctors, "PolicyRequirementRule", false);
183             }
184             else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRuleReference)) {
185                 string ref(XMLHelper::getAttrString(e, nullptr, _ref));
186                 if (!ref.empty()) {
187                     multimap<string,MatchFunctor*>::const_iterator prr = m_policyReqRules.find(ref);
188                     func = (prr!=m_policyReqRules.end()) ? prr->second : nullptr;
189                 }
190             }
191             if (func) {
192                 m_policies.push_back(Policy());
193                 m_policies.back().m_applies = func;
194                 e = XMLHelper::getNextSiblingElement(e);
195                 while (e) {
196                     if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
197                         tuple<string,const MatchFunctor*,const MatchFunctor*> rule = buildAttributeRule(e, permFunctors, denyFunctors, false);
198                         if (rule.get<1>() || rule.get<2>())
199                             m_policies.back().m_rules.insert(Policy::rules_t::value_type(rule.get<0>(), make_pair(rule.get<1>(), rule.get<2>())));
200                     }
201                     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, AttributeRuleReference)) {
202                         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
203                         if (!ref.empty()) {
204                             map< string,tuple<string,const MatchFunctor*,const MatchFunctor*> >::const_iterator ar = m_attrRules.find(ref);
205                             if (ar != m_attrRules.end()) {
206                                 m_policies.back().m_rules.insert(
207                                     Policy::rules_t::value_type(ar->second.get<0>(), make_pair(ar->second.get<1>(), ar->second.get<2>()))
208                                     );
209                             }
210                             else {
211                                 m_log.warn("skipping invalid AttributeRuleReference (%s)", ref.c_str());
212                             }
213                         }
214                     }
215                     e = XMLHelper::getNextSiblingElement(e);
216                 }
217             }
218             else {
219                 m_log.warn("skipping AttributeFilterPolicy, PolicyRequirementRule invalid or missing");
220             }
221         }
222         child = XMLHelper::getNextSiblingElement(child);
223     }
224 }
225
226 MatchFunctor* XMLFilterImpl::buildFunctor(
227     const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
228     )
229 {
230     string id(XMLHelper::getAttrString(e, nullptr, _id));
231
232     if (standalone && id.empty()) {
233         m_log.warn("skipping stand-alone %s with no id", logname);
234         return nullptr;
235     }
236     else if (!id.empty() && functorMap.getMatchFunctors().count(id)) {
237         if (standalone) {
238             m_log.warn("skipping duplicate stand-alone %s with id (%s)", logname, id.c_str());
239             return nullptr;
240         }
241         else
242             id.clear();
243     }
244
245     scoped_ptr<xmltooling::QName> type(XMLHelper::getXSIType(e));
246     if (type) {
247         try {
248             auto_ptr<MatchFunctor> func(SPConfig::getConfig().MatchFunctorManager.newPlugin(*type, make_pair(&functorMap,e)));
249             functorMap.getMatchFunctors().insert(multimap<string,MatchFunctor*>::value_type(id, func.get()));
250             return func.release();
251         }
252         catch (exception& ex) {
253             m_log.error("error building %s with type (%s): %s", logname, type->toString().c_str(), ex.what());
254         }
255     }
256     else if (standalone)
257         m_log.warn("skipping stand-alone %s with no xsi:type", logname);
258     else
259         m_log.error("%s with no xsi:type", logname);
260
261     return nullptr;
262 }
263
264 tuple<string,const MatchFunctor*,const MatchFunctor*> XMLFilterImpl::buildAttributeRule(
265     const DOMElement* e, const FilterPolicyContext& permMap, const FilterPolicyContext& denyMap, bool standalone
266     )
267 {
268     string id(XMLHelper::getAttrString(e, nullptr, _id));
269
270     if (standalone && id.empty()) {
271         m_log.warn("skipping stand-alone AttributeRule with no id");
272         return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
273     }
274     else if (!id.empty() && m_attrRules.count(id)) {
275         if (standalone) {
276             m_log.warn("skipping duplicate stand-alone AttributeRule with id (%s)", id.c_str());
277             return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
278         }
279         else
280             id.clear();
281     }
282
283     string attrID(XMLHelper::getAttrString(e, nullptr, attributeID));
284     if (attrID.empty())
285         m_log.warn("skipping AttributeRule with no attributeID");
286
287     MatchFunctor* perm = nullptr;
288     MatchFunctor* deny = nullptr;
289
290     e = XMLHelper::getFirstChildElement(e);
291     if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
292         perm = buildFunctor(e, permMap, "PermitValueRule", false);
293         e = XMLHelper::getNextSiblingElement(e);
294     }
295     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, PermitValueRuleReference)) {
296         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
297         if (!ref.empty()) {
298             multimap<string,MatchFunctor*>::const_iterator pvr = m_permitValRules.find(ref);
299             perm = (pvr!=m_permitValRules.end()) ? pvr->second : nullptr;
300         }
301         e = XMLHelper::getNextSiblingElement(e);
302     }
303
304     if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRule)) {
305         deny = buildFunctor(e, denyMap, "DenyValueRule", false);
306     }
307     else if (e && XMLHelper::isNodeNamed(e, SHIB2ATTRIBUTEFILTER_NS, DenyValueRuleReference)) {
308         string ref(XMLHelper::getAttrString(e, nullptr, _ref));
309         if (!ref.empty()) {
310             multimap<string,MatchFunctor*>::const_iterator pvr = m_denyValRules.find(ref);
311             deny = (pvr!=m_denyValRules.end()) ? pvr->second : nullptr;
312         }
313     }
314
315     if (perm || deny) {
316         if (!id.empty()) {
317             m_attrRules[id] = make_tuple(attrID, perm, deny);
318             return m_attrRules[id];
319         }
320         else {
321             return make_tuple(attrID, perm, deny);
322         }
323     }
324
325     if (!id.empty())
326         m_log.warn("skipping AttributeRule (%s), permit and denial rule(s) invalid or missing", id.c_str());
327     else
328         m_log.warn("skipping AttributeRule, permit and denial rule(s) invalid or missing");
329     return tuple<string,const MatchFunctor*,const MatchFunctor*>(string(),nullptr,nullptr);
330 }
331
332 void XMLFilterImpl::filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const
333 {
334     auto_ptr_char issuer(context.getAttributeIssuer());
335
336     m_log.debug("filtering %lu attribute(s) from (%s)", attributes.size(), issuer.get() ? issuer.get() : "unknown source");
337
338     if (m_policies.empty()) {
339         m_log.warn("no filter policies were loaded, filtering out all attributes from (%s)", issuer.get() ? issuer.get() : "unknown source");
340         for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
341         attributes.clear();
342         return;
343     }
344
345     // We have to evaluate every policy that applies against each attribute before deciding what to keep.
346
347     // For efficiency, we build an array of the policies that apply in advance.
348     vector<const Policy*> applicablePolicies;
349     for (vector<Policy>::const_iterator p = m_policies.begin(); p != m_policies.end(); ++p) {
350         if (p->m_applies->evaluatePolicyRequirement(context))
351             applicablePolicies.push_back(&(*p));
352     }
353
354     // For further efficiency, we declare arrays to store the applicable rules for an Attribute.
355     vector< pair<const MatchFunctor*,const MatchFunctor*> > applicableRules;
356     vector< pair<const MatchFunctor*,const MatchFunctor*> > wildcardRules;
357
358     // Store off the wildcards ahead of time.
359     for (indirect_iterator<vector<const Policy*>::const_iterator> pol = make_indirect_iterator(applicablePolicies.begin());
360             pol != make_indirect_iterator(applicablePolicies.end()); ++pol) {
361         pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = pol->m_rules.equal_range("*");
362         for (; rules.first!=rules.second; ++rules.first)
363             wildcardRules.push_back(rules.first->second);
364     }
365
366     // To track what to keep without removing anything from the original set until the end, we maintain
367     // a map of each Attribute object to a boolean array with true flags indicating what to delete.
368     // A single dimension array tracks attributes being removed entirely.
369     vector<bool> deletedAttributes(attributes.size(), false);
370     map< Attribute*, vector<bool> > deletedPositions;
371
372     // Loop over each attribute to filter them.
373     for (vector<Attribute*>::size_type a = 0; a < attributes.size(); ++a) {
374         Attribute* attr = attributes[a];
375
376         // Clear the rule store.
377         applicableRules.clear();
378
379         // Look for rules to run in each policy.
380         for (indirect_iterator<vector<const Policy*>::const_iterator> pol = make_indirect_iterator(applicablePolicies.begin());
381                 pol != make_indirect_iterator(applicablePolicies.end()); ++pol) {
382             pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = pol->m_rules.equal_range(attr->getId());
383             for (; rules.first!=rules.second; ++rules.first)
384                 applicableRules.push_back(rules.first->second);
385         }
386
387         // If no rules found, apply wildcards.
388         const vector< pair<const MatchFunctor*,const MatchFunctor*> >& rulesToRun =
389             applicableRules.empty() ? wildcardRules : applicableRules;
390
391         // If no rules apply, remove the attribute entirely.
392         if (rulesToRun.empty()) {
393             m_log.warn(
394                 "no rule found, will remove attribute (%s) from (%s)",
395                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
396                 );
397             deletedAttributes[a] = true;
398             continue;
399         }
400
401         // Run each permit/deny rule.
402         m_log.debug(
403             "applying filtering rule(s) for attribute (%s) from (%s)",
404             attr->getId(), issuer.get() ? issuer.get() : "unknown source"
405             );
406
407         bool kickit;
408
409         // Examine each value.
410         for (size_t count = attr->valueCount(), index = 0; index < count; ++index) {
411
412             // Assume we're kicking it out.
413             kickit=true;
414
415             for (vector< pair<const MatchFunctor*,const MatchFunctor*> >::const_iterator r = rulesToRun.begin(); r != rulesToRun.end(); ++r) {
416                 // If there's a permit rule that passes, don't kick it.
417                 if (r->first && r->first->evaluatePermitValue(context, *attr, index))
418                     kickit = false;
419                 if (!kickit && r->second && r->second->evaluatePermitValue(context, *attr, index))
420                     kickit = true;
421             }
422
423             // If we're kicking it, record that in the tracker.
424             if (kickit) {
425                 m_log.warn(
426                     "removed value at position (%lu) of attribute (%s) from (%s)",
427                     index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
428                     );
429                 deletedPositions[attr].resize(index+1);
430                 deletedPositions[attr][index] = true;
431             }
432         }
433     }
434
435     // Final step: go over the deletedPositions matrix and apply the actual changes. In order to delete
436     // any attributes that end up with no values, we have to do it by looping over the originals.
437     for (vector<Attribute*>::size_type a = 0; a < attributes.size();) {
438         Attribute* attr = attributes[a];
439
440         if (deletedAttributes[a]) {
441             m_log.warn(
442                 "removing filtered attribute (%s) from (%s)",
443                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
444                 );
445             delete attr;
446             deletedAttributes.erase(deletedAttributes.begin() + a);
447             attributes.erase(attributes.begin() + a);
448             continue;
449         }
450         else if (deletedPositions.count(attr) > 0) {
451             // To do the removal, we loop over the bits backwards so that the
452             // underlying value sequence doesn't get distorted by any removals.
453             // Index has to be offset by one because size_type is unsigned.
454             const vector<bool>& row = deletedPositions[attr];
455             for (vector<bool>::size_type index = row.size(); index > 0; --index) {
456                 if (row[index-1])
457                     attr->removeValue(index-1);
458             }
459         }
460
461         // Check for no values.
462         if (attr->valueCount() == 0) {
463             m_log.warn(
464                 "no values left, removing attribute (%s) from (%s)",
465                 attr->getId(), issuer.get() ? issuer.get() : "unknown source"
466                 );
467             delete attr;
468             deletedAttributes.erase(deletedAttributes.begin() + a);
469             attributes.erase(attributes.begin() + a);
470             continue;
471         }
472
473         ++a;
474     }
475 }
476
477 pair<bool,DOMElement*> XMLFilter::background_load()
478 {
479     // Load from source using base class.
480     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
481
482     // If we own it, wrap it.
483     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
484
485     scoped_ptr<XMLFilterImpl> impl(new XMLFilterImpl(raw.second, m_log));
486
487     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
488     impl->setDocument(docjanitor.release());
489
490     // Perform the swap inside a lock.
491     if (m_lock)
492         m_lock->wrlock();
493     SharedLock locker(m_lock, false);
494     m_impl.swap(impl);
495
496     return make_pair(false,(DOMElement*)nullptr);
497 }