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