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