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