d1656e589d74f268a6954c7ddb427dcba80b960f
[shibboleth/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 namespace shibsp;
37 using namespace opensaml::saml2md;
38 using namespace opensaml;
39 using namespace xmltooling;
40 using namespace std;
41
42 namespace shibsp {
43
44 #if defined (_MSC_VER)
45     #pragma warning( push )
46     #pragma warning( disable : 4250 )
47 #endif
48
49     struct SHIBSP_DLLLOCAL Policy
50     {
51         Policy() : m_applies(NULL) {}
52         const MatchFunctor* m_applies;
53         typedef multimap<string,const MatchFunctor*> rules_t;
54         rules_t m_rules;
55     };
56
57     class SHIBSP_DLLLOCAL XMLFilterImpl
58     {
59     public:
60         XMLFilterImpl(const DOMElement* e, Category& log);
61         ~XMLFilterImpl() {
62             if (m_document)
63                 m_document->release();
64             for_each(m_policyReqRules.begin(), m_policyReqRules.end(), cleanup_pair<string,MatchFunctor>());
65             for_each(m_permitValRules.begin(), m_permitValRules.end(), cleanup_pair<string,MatchFunctor>());
66         }
67
68         void setDocument(DOMDocument* doc) {
69             m_document = doc;
70         }
71
72         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const;
73
74     private:
75         MatchFunctor* buildFunctor(
76             const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
77             );
78         pair<string,const MatchFunctor*> buildAttributeRule(const DOMElement* e, const FilterPolicyContext& functorMap, bool standalone);
79
80         Category& m_log;
81         DOMDocument* m_document;
82         vector<Policy> m_policies;
83         map< string,pair<string,const MatchFunctor*> > m_attrRules;
84         multimap<string,MatchFunctor*> m_policyReqRules;
85         multimap<string,MatchFunctor*> m_permitValRules;
86     };
87     
88     class SHIBSP_DLLLOCAL XMLFilter : public AttributeFilter, public ReloadableXMLFile
89     {
90     public:
91         XMLFilter(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeFilter")), m_impl(NULL) {
92             load();
93         }
94         ~XMLFilter() {
95             delete m_impl;
96         }
97         
98         void filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const {
99             m_impl->filterAttributes(context, attributes);
100         }
101
102     protected:
103         pair<bool,DOMElement*> load();
104
105     private:
106         XMLFilterImpl* m_impl;
107     };
108
109 #if defined (_MSC_VER)
110     #pragma warning( pop )
111 #endif
112
113     AttributeFilter* SHIBSP_DLLLOCAL XMLAttributeFilterFactory(const DOMElement* const & e)
114     {
115         return new XMLFilter(e);
116     }
117     
118     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);
119     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);
120     static const XMLCh AttributeRule[] =                UNICODE_LITERAL_13(A,t,t,r,i,b,u,t,e,R,u,l,e);
121     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);
122     static const XMLCh PermitValueRule[] =              UNICODE_LITERAL_15(P,e,r,m,i,t,V,a,l,u,e,R,u,l,e);
123     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);
124     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);
125     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);
126     static const XMLCh attributeID[] =                  UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,D);
127     static const XMLCh _id[] =                          UNICODE_LITERAL_2(i,d);
128     static const XMLCh _ref[] =                         UNICODE_LITERAL_3(r,e,f);
129 };
130
131 XMLFilterImpl::XMLFilterImpl(const DOMElement* e, Category& log) : m_log(log), m_document(NULL)
132 {
133 #ifdef _DEBUG
134     xmltooling::NDC ndc("XMLFilterImpl");
135 #endif
136     
137     if (!XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicyGroup))
138         throw ConfigurationException("XML AttributeFilter requires afp:AttributeFilterPolicyGroup at root of configuration.");
139
140     FilterPolicyContext reqFunctors(m_policyReqRules);
141     FilterPolicyContext valFunctors(m_permitValRules);
142
143     DOMElement* child = XMLHelper::getFirstChildElement(e);
144     while (child) {
145         if (XMLHelper::isNodeNamed(child, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
146             buildFunctor(child, reqFunctors, "PolicyRequirementRule", true);
147         }
148         else if (XMLHelper::isNodeNamed(child, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
149             buildFunctor(child, valFunctors, "PermitValueRule", true);
150         }
151         else if (XMLHelper::isNodeNamed(child, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
152             buildAttributeRule(child, valFunctors, true);
153         }
154         else if (XMLHelper::isNodeNamed(child, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, AttributeFilterPolicy)) {
155             e = XMLHelper::getFirstChildElement(child);
156             MatchFunctor* func = NULL;
157             if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRule)) {
158                 func = buildFunctor(e, reqFunctors, "PolicyRequirementRule", false);
159             }
160             else if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PolicyRequirementRuleReference)) {
161                 auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
162                 if (ref.get() && *ref.get()) {
163                     multimap<string,MatchFunctor*>::const_iterator prr = m_policyReqRules.find(ref.get());
164                     func = (prr!=m_policyReqRules.end()) ? prr->second : NULL;
165                 }
166             }
167             if (func) {
168                 m_policies.push_back(Policy());
169                 m_policies.back().m_applies = func;
170                 e = XMLHelper::getNextSiblingElement(e);
171                 while (e) {
172                     if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, AttributeRule)) {
173                         pair<string,const MatchFunctor*> rule = buildAttributeRule(e, valFunctors, false);
174                         if (rule.second)
175                             m_policies.back().m_rules.insert(Policy::rules_t::value_type(rule.first, rule.second));
176                     }
177                     else if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, AttributeRuleReference)) {
178                         auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
179                         if (ref.get() && *ref.get()) {
180                             map< string,pair<string,const MatchFunctor*> >::const_iterator ar = m_attrRules.find(ref.get());
181                             if (ar != m_attrRules.end())
182                                 m_policies.back().m_rules.insert(Policy::rules_t::value_type(ar->second.first, ar->second.second));
183                             else
184                                 m_log.warn("skipping invalid AttributeRuleReference (%s)", ref.get());
185                         }
186                     }
187                     e = XMLHelper::getNextSiblingElement(e);
188                 }
189             }
190             else {
191                 m_log.warn("skipping AttributeFilterPolicy, PolicyRequirementRule invalid or missing");
192             }
193         }
194         child = XMLHelper::getNextSiblingElement(child);
195     }
196 }
197
198 MatchFunctor* XMLFilterImpl::buildFunctor(
199     const DOMElement* e, const FilterPolicyContext& functorMap, const char* logname, bool standalone
200     )
201 {
202     auto_ptr_char temp(e->getAttributeNS(NULL,_id));
203     const char* id = (temp.get() && *temp.get()) ? temp.get() : "";
204
205     if (standalone && !*id) {
206         m_log.warn("skipping stand-alone %s with no id", logname);
207         return NULL;
208     }
209     else if (*id && functorMap.getMatchFunctors().count(id)) {
210         if (standalone) {
211             m_log.warn("skipping duplicate stand-alone %s with id (%s)", logname, id);
212             return NULL;
213         }
214         else
215             id = "";
216     }
217
218     auto_ptr<QName> type(XMLHelper::getXSIType(e));
219     if (type.get()) {
220         try {
221             MatchFunctor* func = SPConfig::getConfig().MatchFunctorManager.newPlugin(*type.get(), make_pair(&functorMap,e));
222             functorMap.getMatchFunctors().insert(multimap<string,MatchFunctor*>::value_type(id, func));
223             return func;
224         }
225         catch (exception& ex) {
226             m_log.error("error building %s with type (%s): %s", logname, type->toString().c_str(), ex.what());
227         }
228     }
229     else if (standalone)
230         m_log.warn("skipping stand-alone %s with no xsi:type", logname);
231     else
232         m_log.error("%s with no xsi:type", logname);
233
234     return NULL;
235 }
236
237 pair<string,const MatchFunctor*> XMLFilterImpl::buildAttributeRule(const DOMElement* e, const FilterPolicyContext& functorMap, bool standalone)
238 {
239     auto_ptr_char temp(e->getAttributeNS(NULL,_id));
240     const char* id = (temp.get() && *temp.get()) ? temp.get() : "";
241
242     if (standalone && !*id) {
243         m_log.warn("skipping stand-alone AttributeRule with no id");
244         return make_pair(string(),(const MatchFunctor*)NULL);
245     }
246     else if (*id && m_attrRules.count(id)) {
247         if (standalone) {
248             m_log.warn("skipping duplicate stand-alone AttributeRule with id (%s)", id);
249             return make_pair(string(),(const MatchFunctor*)NULL);
250         }
251         else
252             id = "";
253     }
254
255     auto_ptr_char attrID(e->getAttributeNS(NULL,attributeID));
256     if (!attrID.get() || !*attrID.get())
257         m_log.warn("skipping AttributeRule with no attributeID");
258
259     e = XMLHelper::getFirstChildElement(e);
260     MatchFunctor* func=NULL;
261     if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PermitValueRule)) {
262         func = buildFunctor(e, functorMap, "PermitValueRule", false);
263     }
264     else if (e && XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEFILTER_NS, PermitValueRuleReference)) {
265         auto_ptr_char ref(e->getAttributeNS(NULL, _ref));
266         if (ref.get() && *ref.get()) {
267             multimap<string,MatchFunctor*>::const_iterator pvr = m_permitValRules.find(ref.get());
268             func = (pvr!=m_permitValRules.end()) ? pvr->second : NULL;
269         }
270     }
271
272     if (func) {
273         if (*id)
274             return m_attrRules[id] = pair<string,const MatchFunctor*>(attrID.get(), func);
275         else
276             return pair<string,const MatchFunctor*>(attrID.get(), func);
277     }
278
279     m_log.warn("skipping AttributeRule (%s), PermitValueRule invalid or missing", id);
280     return make_pair(string(),(const MatchFunctor*)NULL);
281 }
282
283 void XMLFilterImpl::filterAttributes(const FilteringContext& context, vector<Attribute*>& attributes) const
284 {
285     auto_ptr_char issuer(context.getAttributeIssuer());
286
287     m_log.debug("filtering %lu attribute(s) from (%s)", attributes.size(), issuer.get() ? issuer.get() : "unknown source");
288
289     if (m_policies.empty()) {
290         m_log.warn("no filter policies were loaded, filtering out all attributes from (%s)", issuer.get() ? issuer.get() : "unknown source");
291         for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
292         attributes.clear();
293         return;
294     }
295
296     size_t count,index;
297
298     // Test each Policy.
299     for (vector<Policy>::const_iterator p=m_policies.begin(); p!=m_policies.end(); ++p) {
300         if (p->m_applies->evaluatePolicyRequirement(context)) {
301             // Loop over the attributes and look for possible rules to run.
302             for (vector<Attribute*>::size_type a=0; a<attributes.size();) {
303                 bool ruleFound = false;
304                 Attribute* attr = attributes[a];
305                 pair<Policy::rules_t::const_iterator,Policy::rules_t::const_iterator> rules = p->m_rules.equal_range(attr->getId());
306                 if (rules.first != rules.second) {
307                     ruleFound = true;
308                     // Run each rule in sequence.
309                     m_log.debug(
310                         "applying filtering rule(s) for attribute (%s) from (%s)",
311                         attr->getId(), issuer.get() ? issuer.get() : "unknown source"
312                         );
313                     for (; rules.first!=rules.second; ++rules.first) {
314                         count = attr->valueCount();
315                         for (index=0; index < count;) {
316                             // The return value tells us whether to index past the accepted value, or stay put and decrement the count.
317                             if (rules.first->second->evaluatePermitValue(context, *attr, index)) {
318                                 index++;
319                             }
320                             else {
321                                 m_log.warn(
322                                     "filtered value at position (%lu) of attribute (%s) from (%s)",
323                                     index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
324                                     );
325                                 attr->removeValue(index);
326                                 count--;
327                             }
328                         }
329                     }
330                 }
331
332                 rules = p->m_rules.equal_range("*");
333                 if (rules.first != rules.second) {
334                     // Run each rule in sequence.
335                     if (!ruleFound) {
336                         m_log.debug(
337                             "applying wildcard rule(s) for attribute (%s) from (%s)",
338                             attr->getId(), issuer.get() ? issuer.get() : "unknown source"
339                             );
340                         ruleFound = true;
341                     }
342                     for (; rules.first!=rules.second; ++rules.first) {
343                         count = attr->valueCount();
344                         for (index=0; index < count;) {
345                             // The return value tells us whether to index past the accepted value, or stay put and decrement the count.
346                             if (rules.first->second->evaluatePermitValue(context, *attr, index)) {
347                                 index++;
348                             }
349                             else {
350                                 m_log.warn(
351                                     "filtered value at position (%lu) of attribute (%s) from (%s)",
352                                     index, attr->getId(), issuer.get() ? issuer.get() : "unknown source"
353                                     );
354                                 attr->removeValue(index);
355                                 count--;
356                             }
357                         }
358                     }
359                 }
360
361                 if (!ruleFound || attr->valueCount() == 0) {
362                     if (!ruleFound) {
363                         // No rule found, so we're filtering it out.
364                         m_log.warn(
365                             "no rule found, filtering out values of attribute (%s) from (%s)",
366                             attr->getId(), issuer.get() ? issuer.get() : "unknown source"
367                             );
368                     }
369                     delete attr;
370                     attributes.erase(attributes.begin() + a);
371                 }
372                 else {
373                     ++a;
374                 }
375             }
376         }
377     }
378 }
379
380 pair<bool,DOMElement*> XMLFilter::load()
381 {
382     // Load from source using base class.
383     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
384     
385     // If we own it, wrap it.
386     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
387
388     XMLFilterImpl* impl = new XMLFilterImpl(raw.second, m_log);
389     
390     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
391     impl->setDocument(docjanitor.release());
392
393     delete m_impl;
394     m_impl = impl;
395
396     return make_pair(false,(DOMElement*)NULL);
397 }