Fixed attribute-based functors.
[shibboleth/sp.git] / shibsp / attribute / resolver / impl / XMLAttributeExtractor.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  * XMLAttributeExtractor.cpp
19  * 
20  * AttributeExtractor based on an XML mapping file.
21  */
22
23 #include "internal.h"
24 #include "Application.h"
25 #include "ServiceProvider.h"
26 #include "SPRequest.h"
27 #include "attribute/AttributeDecoder.h"
28 #include "attribute/resolver/AttributeExtractor.h"
29 #include "util/SPConstants.h"
30
31 #include <saml/saml1/core/Assertions.h>
32 #include <saml/saml2/core/Assertions.h>
33 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/ReloadableXMLFile.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/XMLUniDefs.hpp>
38
39 using namespace shibsp;
40 using namespace opensaml::saml2md;
41 using namespace opensaml;
42 using namespace xmltooling;
43 using namespace log4cpp;
44 using namespace std;
45 using saml1::NameIdentifier;
46 using saml2::NameID;
47 using saml2::EncryptedAttribute;
48
49 namespace shibsp {
50
51 #if defined (_MSC_VER)
52     #pragma warning( push )
53     #pragma warning( disable : 4250 )
54 #endif
55
56     class XMLExtractorImpl
57     {
58     public:
59         XMLExtractorImpl(const DOMElement* e, Category& log);
60         ~XMLExtractorImpl() {
61             for (attrmap_t::iterator i = m_attrMap.begin(); i!=m_attrMap.end(); ++i)
62                 delete i->second.first;
63             if (m_document)
64                 m_document->release();
65         }
66
67         void setDocument(DOMDocument* doc) {
68             m_document = doc;
69         }
70
71         void extractAttributes(
72             const Application& application, const char* assertingParty, const NameIdentifier& nameid, multimap<string,Attribute*>& attributes
73             ) const;
74         void extractAttributes(
75             const Application& application, const char* assertingParty, const NameID& nameid, multimap<string,Attribute*>& attributes
76             ) const;
77         void extractAttributes(
78             const Application& application, const char* assertingParty, const saml1::Attribute& attr, multimap<string,Attribute*>& attributes
79             ) const;
80         void extractAttributes(
81             const Application& application, const char* assertingParty, const saml2::Attribute& attr, multimap<string,Attribute*>& attributes
82             ) const;
83
84         void clearHeaders(SPRequest& request) const {
85             for (vector<string>::const_iterator i = m_attributeIds.begin(); i!=m_attributeIds.end(); ++i)
86                 request.clearHeader(i->c_str());
87         }
88
89     private:
90         Category& m_log;
91         DOMDocument* m_document;
92 #ifdef HAVE_GOOD_STL
93         typedef map< pair<xstring,xstring>,pair<AttributeDecoder*,string> > attrmap_t;
94 #else
95         typedef map< pair<string,string>,pair<AttributeDecoder*,string> > attrmap_t;
96 #endif
97         attrmap_t m_attrMap;
98         vector<string> m_attributeIds;
99     };
100     
101     class XMLExtractor : public AttributeExtractor, public ReloadableXMLFile
102     {
103     public:
104         XMLExtractor(const DOMElement* e) : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AttributeExtractor")), m_impl(NULL) {
105             load();
106         }
107         ~XMLExtractor() {
108             delete m_impl;
109         }
110         
111         void extractAttributes(
112             const Application& application, const RoleDescriptor* issuer, const XMLObject& xmlObject, multimap<string,Attribute*>& attributes
113             ) const;
114
115         void clearHeaders(SPRequest& request) const {
116             if (m_impl)
117                 m_impl->clearHeaders(request);
118         }
119
120     protected:
121         pair<bool,DOMElement*> load();
122
123     private:
124         XMLExtractorImpl* m_impl;
125     };
126
127 #if defined (_MSC_VER)
128     #pragma warning( pop )
129 #endif
130
131     AttributeExtractor* SHIBSP_DLLLOCAL XMLAttributeExtractorFactory(const DOMElement* const & e)
132     {
133         return new XMLExtractor(e);
134     }
135     
136     static const XMLCh _AttributeDecoder[] =    UNICODE_LITERAL_16(A,t,t,r,i,b,u,t,e,D,e,c,o,d,e,r);
137     static const XMLCh Attributes[] =           UNICODE_LITERAL_10(A,t,t,r,i,b,u,t,e,s);
138     static const XMLCh _id[] =                  UNICODE_LITERAL_2(i,d);
139     static const XMLCh _name[] =                UNICODE_LITERAL_4(n,a,m,e);
140     static const XMLCh nameFormat[] =           UNICODE_LITERAL_10(n,a,m,e,F,o,r,m,a,t);
141 };
142
143 void SHIBSP_API shibsp::registerAttributeExtractors()
144 {
145     SPConfig::getConfig().AttributeExtractorManager.registerFactory(XML_ATTRIBUTE_EXTRACTOR, XMLAttributeExtractorFactory);
146 }
147
148 XMLExtractorImpl::XMLExtractorImpl(const DOMElement* e, Category& log) : m_log(log), m_document(NULL)
149 {
150 #ifdef _DEBUG
151     xmltooling::NDC ndc("XMLExtractorImpl");
152 #endif
153     
154     if (!XMLHelper::isNodeNamed(e, shibspconstants::SHIB2ATTRIBUTEMAP_NS, Attributes))
155         throw ConfigurationException("XML AttributeExtractor requires am:Attributes at root of configuration.");
156
157     DOMElement* child = XMLHelper::getFirstChildElement(e, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
158     while (child) {
159         // Check for missing name or id.
160         const XMLCh* name = child->getAttributeNS(NULL, _name);
161         if (!name || !*name) {
162             m_log.warn("skipping Attribute with no name");
163             child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
164             continue;
165         }
166
167         auto_ptr_char id(child->getAttributeNS(NULL, _id));
168         if (!id.get() || !*id.get()) {
169             m_log.warn("skipping Attribute with no id");
170             child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
171             continue;
172         }
173         else if (!strcmp(id.get(), "REMOTE_USER")) {
174             m_log.warn("skipping Attribute, id of REMOTE_USER is a reserved name");
175             child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
176             continue;
177         }
178
179         AttributeDecoder* decoder=NULL;
180         try {
181             DOMElement* dchild = XMLHelper::getFirstChildElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, _AttributeDecoder);
182             if (dchild) {
183                 auto_ptr<QName> q(XMLHelper::getXSIType(dchild));
184                 if (q.get())
185                     decoder = SPConfig::getConfig().AttributeDecoderManager.newPlugin(*q.get(), dchild);
186             }
187             if (!decoder)
188                 decoder = SPConfig::getConfig().AttributeDecoderManager.newPlugin(StringAttributeDecoderType, NULL);
189         }
190         catch (exception& ex) {
191             m_log.error("skipping Attribute (%s), error building AttributeDecoder: %s", id.get(), ex.what());
192         }
193
194         if (!decoder) {
195             child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
196             continue;
197         }
198
199         // Empty NameFormat implies the usual Shib URI naming defaults.
200         const XMLCh* format = child->getAttributeNS(NULL, nameFormat);
201         if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) ||
202                 XMLString::equals(format, saml2::Attribute::URI_REFERENCE))
203             format = &chNull;  // ignore default Format/Namespace values
204
205         // Fetch/create the map entry and see if it's a duplicate rule.
206 #ifdef HAVE_GOOD_STL
207         pair<AttributeDecoder*,string>& decl = m_attrMap[make_pair(name,format)];
208 #else
209         auto_ptr_char n(name);
210         auto_ptr_char f(format);
211         pair<AttributeDecoder*,string>& decl = m_attrMap[make_pair(n.get(),f.get())];
212 #endif
213         if (decl.first) {
214             m_log.warn("skipping duplicate Attribute mapping (same name and nameFormat)");
215             delete decoder;
216             child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
217             continue;
218         }
219
220         if (m_log.isInfoEnabled()) {
221 #ifdef HAVE_GOOD_STL
222             auto_ptr_char n(name);
223             auto_ptr_char f(format);
224 #endif
225             m_log.info("creating mapping for Attribute %s%s%s", n.get(), *f.get() ? ", Format/Namespace:" : "", f.get());
226         }
227         
228         decl.first = decoder;
229         decl.second = id.get();
230         m_attributeIds.push_back(id.get());
231         
232         child = XMLHelper::getNextSiblingElement(child, shibspconstants::SHIB2ATTRIBUTEMAP_NS, saml1::Attribute::LOCAL_NAME);
233     }
234 }
235
236 void XMLExtractorImpl::extractAttributes(
237     const Application& application, const char* assertingParty, const NameIdentifier& nameid, multimap<string,Attribute*>& attributes
238     ) const
239 {
240 #ifdef HAVE_GOOD_STL
241     map< pair<xstring,xstring>,pair<AttributeDecoder*,string> >::const_iterator rule;
242 #else
243     map< pair<string,string>,pair<AttributeDecoder*,string> >::const_iterator rule;
244 #endif
245
246     const XMLCh* format = nameid.getFormat();
247     if (!format || !*format)
248         format = NameIdentifier::UNSPECIFIED;
249 #ifdef HAVE_GOOD_STL
250     if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {
251 #else
252     auto_ptr_char temp(format);
253     if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {
254 #endif
255         attributes.insert(
256             make_pair(
257                 rule->second.second,
258                 rule->second.first->decode(rule->second.second.c_str(), &nameid, assertingParty, application.getString("entityID").second)
259                 )
260             );
261     }
262 }
263
264 void XMLExtractorImpl::extractAttributes(
265     const Application& application, const char* assertingParty, const NameID& nameid, multimap<string,Attribute*>& attributes
266     ) const
267 {
268 #ifdef HAVE_GOOD_STL
269     map< pair<xstring,xstring>,pair<AttributeDecoder*,string> >::const_iterator rule;
270 #else
271     map< pair<string,string>,pair<AttributeDecoder*,string> >::const_iterator rule;
272 #endif
273
274     const XMLCh* format = nameid.getFormat();
275     if (!format || !*format)
276         format = NameID::UNSPECIFIED;
277 #ifdef HAVE_GOOD_STL
278     if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {
279 #else
280     auto_ptr_char temp(format);
281     if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {
282 #endif
283         attributes.insert(
284             make_pair(
285                 rule->second.second,
286                 rule->second.first->decode(rule->second.second.c_str(), &nameid, assertingParty, application.getString("entityID").second)
287                 )
288             );
289     }
290 }
291
292 void XMLExtractorImpl::extractAttributes(
293     const Application& application, const char* assertingParty, const saml1::Attribute& attr, multimap<string,Attribute*>& attributes
294     ) const
295 {
296 #ifdef HAVE_GOOD_STL
297     map< pair<xstring,xstring>,pair<AttributeDecoder*,string> >::const_iterator rule;
298 #else
299     map< pair<string,string>,pair<AttributeDecoder*,string> >::const_iterator rule;
300 #endif
301
302     const XMLCh* name = attr.getAttributeName();
303     const XMLCh* format = attr.getAttributeNamespace();
304     if (!name || !*name)
305         return;
306     if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI))
307         format = &chNull;
308 #ifdef HAVE_GOOD_STL
309     if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {
310 #else
311     auto_ptr_char temp1(name);
312     auto_ptr_char temp2(format);
313     if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {
314 #endif
315         attributes.insert(
316             make_pair(
317                 rule->second.second,
318                 rule->second.first->decode(rule->second.second.c_str(), &attr, assertingParty, application.getString("entityID").second)
319                 )
320             );
321     }
322 }
323
324 void XMLExtractorImpl::extractAttributes(
325     const Application& application, const char* assertingParty, const saml2::Attribute& attr, multimap<string,Attribute*>& attributes
326     ) const
327 {
328 #ifdef HAVE_GOOD_STL
329     map< pair<xstring,xstring>,pair<AttributeDecoder*,string> >::const_iterator rule;
330 #else
331     map< pair<string,string>,pair<AttributeDecoder*,string> >::const_iterator rule;
332 #endif
333
334     const XMLCh* name = attr.getName();
335     const XMLCh* format = attr.getNameFormat();
336     if (!name || !*name)
337         return;
338     if (!format || !*format)
339         format = saml2::Attribute::UNSPECIFIED;
340     else if (XMLString::equals(format, saml2::Attribute::URI_REFERENCE))
341         format = &chNull;
342 #ifdef HAVE_GOOD_STL
343     if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {
344 #else
345     auto_ptr_char temp1(name);
346     auto_ptr_char temp2(format);
347     if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {
348 #endif
349         attributes.insert(
350             make_pair(
351                 rule->second.second,
352                 rule->second.first->decode(rule->second.second.c_str(), &attr, assertingParty, application.getString("entityID").second)
353                 )
354             );
355     }
356 }
357
358 void XMLExtractor::extractAttributes(
359     const Application& application, const RoleDescriptor* issuer, const XMLObject& xmlObject, multimap<string,Attribute*>& attributes
360     ) const
361 {
362     if (!m_impl)
363         return;
364
365     // Check for assertions.
366     if (XMLString::equals(xmlObject.getElementQName().getLocalPart(), saml1::Assertion::LOCAL_NAME)) {
367         const saml2::Assertion* token2 = dynamic_cast<const saml2::Assertion*>(&xmlObject);
368         if (token2) {
369             auto_ptr_char assertingParty(issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID() : NULL);
370             const vector<saml2::AttributeStatement*>& statements = token2->getAttributeStatements();
371             for (vector<saml2::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {
372                 const vector<saml2::Attribute*>& attrs = const_cast<const saml2::AttributeStatement*>(*s)->getAttributes();
373                 for (vector<saml2::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a)
374                     m_impl->extractAttributes(application, assertingParty.get(), *(*a), attributes);
375
376                 const vector<saml2::EncryptedAttribute*>& encattrs = const_cast<const saml2::AttributeStatement*>(*s)->getEncryptedAttributes();
377                 for (vector<saml2::EncryptedAttribute*>::const_iterator ea = encattrs.begin(); ea!=encattrs.end(); ++ea)
378                     extractAttributes(application, issuer, *(*ea), attributes);
379             }
380             return;
381         }
382
383         const saml1::Assertion* token1 = dynamic_cast<const saml1::Assertion*>(&xmlObject);
384         if (token1) {
385             auto_ptr_char assertingParty(issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID() : NULL);
386             const vector<saml1::AttributeStatement*>& statements = token1->getAttributeStatements();
387             for (vector<saml1::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {
388                 const vector<saml1::Attribute*>& attrs = const_cast<const saml1::AttributeStatement*>(*s)->getAttributes();
389                 for (vector<saml1::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a)
390                     m_impl->extractAttributes(application, assertingParty.get(), *(*a), attributes);
391             }
392             return;
393         }
394
395         throw AttributeExtractionException("Unable to extract attributes, unknown object type.");
396     }
397
398     // Check for attributes.
399     if (XMLString::equals(xmlObject.getElementQName().getLocalPart(), saml1::Attribute::LOCAL_NAME)) {
400         auto_ptr_char assertingParty(issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID() : NULL);
401
402         const saml2::Attribute* attr2 = dynamic_cast<const saml2::Attribute*>(&xmlObject);
403         if (attr2)
404             return m_impl->extractAttributes(application, assertingParty.get(), *attr2, attributes);
405
406         const saml1::Attribute* attr1 = dynamic_cast<const saml1::Attribute*>(&xmlObject);
407         if (attr1)
408             return m_impl->extractAttributes(application, assertingParty.get(), *attr1, attributes);
409
410         throw AttributeExtractionException("Unable to extract attributes, unknown object type.");
411     }
412
413     if (XMLString::equals(xmlObject.getElementQName().getLocalPart(), EncryptedAttribute::LOCAL_NAME)) {
414         const EncryptedAttribute* encattr = dynamic_cast<const EncryptedAttribute*>(&xmlObject);
415         if (encattr) {
416             const XMLCh* recipient = application.getXMLString("entityID").second;
417             CredentialResolver* cr = application.getCredentialResolver();
418             if (!cr) {
419                 m_log.warn("found encrypted attribute, but no CredentialResolver was available");
420                 return;
421             }
422
423             try {
424                 Locker credlocker(cr);
425                 if (issuer) {
426                     MetadataCredentialCriteria mcc(*issuer);
427                     auto_ptr<XMLObject> decrypted(encattr->decrypt(*cr, recipient, &mcc));
428                     return extractAttributes(application, issuer, *(decrypted.get()), attributes);
429                 }
430                 else {
431                     auto_ptr<XMLObject> decrypted(encattr->decrypt(*cr, recipient));
432                     return extractAttributes(application, issuer, *(decrypted.get()), attributes);
433                 }
434             }
435             catch (exception& ex) {
436                 m_log.error("caught exception decrypting Attribute: %s", ex.what());
437                 return;
438             }
439         }
440     }
441
442     // Check for NameIDs.
443     const NameID* name2 = dynamic_cast<const NameID*>(&xmlObject);
444     if (name2) {
445         auto_ptr_char assertingParty(issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID() : NULL);
446         return m_impl->extractAttributes(application, assertingParty.get(), *name2, attributes);
447     }
448
449     const NameIdentifier* name1 = dynamic_cast<const NameIdentifier*>(&xmlObject);
450     if (name1) {
451         auto_ptr_char assertingParty(issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent())->getEntityID() : NULL);
452         return m_impl->extractAttributes(application, assertingParty.get(), *name1, attributes);
453     }
454
455     throw AttributeExtractionException("Unable to extract attributes, unknown object type.");
456 }
457
458 pair<bool,DOMElement*> XMLExtractor::load()
459 {
460     // Load from source using base class.
461     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
462     
463     // If we own it, wrap it.
464     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
465
466     XMLExtractorImpl* impl = new XMLExtractorImpl(raw.second, m_log);
467     
468     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
469     impl->setDocument(docjanitor.release());
470
471     delete m_impl;
472     m_impl = impl;
473
474     return make_pair(false,(DOMElement*)NULL);
475 }