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