Add Logo support to metadata extractor.
[shibboleth/cpp-sp.git] / shibsp / attribute / resolver / impl / MetadataAttributeExtractor.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  * MetadataAttributeExtractor.cpp
23  *
24  * AttributeExtractor for SAML metadata content.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "ServiceProvider.h"
30 #include "attribute/SimpleAttribute.h"
31 #include "attribute/AttributeDecoder.h"
32 #include "attribute/resolver/AttributeExtractor.h"
33
34 #include <boost/bind.hpp>
35 #include <boost/shared_ptr.hpp>
36 #include <boost/iterator/indirect_iterator.hpp>
37 #include <boost/tuple/tuple.hpp>
38 #include <saml/saml2/metadata/Metadata.h>
39 #include <xmltooling/util/XMLHelper.h>
40 #include <xercesc/util/XMLStringTokenizer.hpp>
41 #include <xercesc/util/XMLUniDefs.hpp>
42
43 using namespace shibsp;
44 using namespace opensaml::saml2md;
45 using namespace opensaml;
46 using namespace xmltooling;
47 using namespace boost;
48 using namespace std;
49
50 namespace shibsp {
51
52 #if defined (_MSC_VER)
53     #pragma warning( push )
54     #pragma warning( disable : 4250 )
55 #endif
56
57     class MetadataExtractor : public AttributeExtractor
58     {
59     public:
60         MetadataExtractor(const DOMElement* e);
61         ~MetadataExtractor() {}
62
63         Lockable* lock() {
64             return this;
65         }
66
67         void unlock() {
68         }
69
70         // deprecated
71         void extractAttributes(
72             const Application& application,
73             const RoleDescriptor* issuer,
74             const XMLObject& xmlObject,
75             vector<shibsp::Attribute*>& attributes
76             ) const {
77             extractAttributes(application, nullptr, issuer, xmlObject, attributes);
78         }
79
80         void extractAttributes(
81             const Application& application,
82             const GenericRequest* request,
83             const RoleDescriptor* issuer,
84             const XMLObject& xmlObject,
85             vector<shibsp::Attribute*>& attributes
86             ) const;
87         void getAttributeIds(vector<string>& attributes) const;
88
89     private:
90         string m_attributeProfiles,
91             m_errorURL,
92             m_displayName,
93             m_description,
94             m_informationURL,
95             m_privacyURL,
96             m_orgName,
97             m_orgDisplayName,
98             m_orgURL;
99         typedef tuple< string,xstring,boost::shared_ptr<AttributeDecoder> > contact_tuple_t;
100         typedef tuple< string,int,int,boost::shared_ptr<AttributeDecoder> > logo_tuple_t;
101         vector<contact_tuple_t> m_contacts; // tuple is attributeID, contact type, decoder
102         vector<logo_tuple_t> m_logos;       // tuple is attributeID, height, width, decoder
103
104         template <class T> void doLangSensitive(const GenericRequest*, const vector<T*>&, const string&, vector<shibsp::Attribute*>&) const;
105         void doContactPerson(const RoleDescriptor*, const contact_tuple_t&, vector<shibsp::Attribute*>&) const;
106         void doLogo(const GenericRequest*, const vector<Logo*>&,const logo_tuple_t&, vector<shibsp::Attribute*>&) const;
107     };
108
109 #if defined (_MSC_VER)
110     #pragma warning( pop )
111 #endif
112
113     AttributeExtractor* SHIBSP_DLLLOCAL MetadataAttributeExtractorFactory(const DOMElement* const & e)
114     {
115         return new MetadataExtractor(e);
116     }
117
118     static const XMLCh _id[] = UNICODE_LITERAL_2(i,d);
119     static const XMLCh _formatter[] = UNICODE_LITERAL_9(f,o,r,m,a,t,t,e,r);
120 };
121
122 MetadataExtractor::MetadataExtractor(const DOMElement* e)
123     : m_attributeProfiles(XMLHelper::getAttrString(e, nullptr, AttributeProfile::LOCAL_NAME)),
124         m_errorURL(XMLHelper::getAttrString(e, nullptr, RoleDescriptor::ERRORURL_ATTRIB_NAME)),
125         m_displayName(XMLHelper::getAttrString(e, nullptr, DisplayName::LOCAL_NAME)),
126         m_description(XMLHelper::getAttrString(e, nullptr, Description::LOCAL_NAME)),
127         m_informationURL(XMLHelper::getAttrString(e, nullptr, InformationURL::LOCAL_NAME)),
128         m_privacyURL(XMLHelper::getAttrString(e, nullptr, PrivacyStatementURL::LOCAL_NAME)),
129         m_orgName(XMLHelper::getAttrString(e, nullptr, OrganizationName::LOCAL_NAME)),
130         m_orgDisplayName(XMLHelper::getAttrString(e, nullptr, OrganizationDisplayName::LOCAL_NAME)),
131         m_orgURL(XMLHelper::getAttrString(e, nullptr, OrganizationURL::LOCAL_NAME))
132 {
133     e = e ? XMLHelper::getFirstChildElement(e) : nullptr;
134     while (e) {
135         if (XMLHelper::isNodeNamed(e, shibspconstants::SHIB2SPCONFIG_NS, ContactPerson::LOCAL_NAME)) {
136             string id(XMLHelper::getAttrString(e, nullptr, _id));
137             const XMLCh* type = e->getAttributeNS(nullptr, ContactPerson::CONTACTTYPE_ATTRIB_NAME);
138             if (!id.empty() && type && *type) {
139                 boost::shared_ptr<AttributeDecoder> decoder(SPConfig::getConfig().AttributeDecoderManager.newPlugin(DOMAttributeDecoderType, e));
140                 m_contacts.push_back(contact_tuple_t(id, type, decoder));
141             }
142         }
143         else if (XMLHelper::isNodeNamed(e, shibspconstants::SHIB2SPCONFIG_NS, Logo::LOCAL_NAME)) {
144             string id(XMLHelper::getAttrString(e, nullptr, _id));
145             int h(XMLHelper::getAttrInt(e, 0, Logo::HEIGHT_ATTRIB_NAME));
146             int w(XMLHelper::getAttrInt(e, 0, Logo::WIDTH_ATTRIB_NAME));
147             if (!id.empty()) {
148                 boost::shared_ptr<AttributeDecoder> decoder(SPConfig::getConfig().AttributeDecoderManager.newPlugin(DOMAttributeDecoderType, e));
149                 m_logos.push_back(logo_tuple_t(id, h, w, decoder));
150             }
151         }
152         e = XMLHelper::getNextSiblingElement(e);
153     }
154 }
155
156 void MetadataExtractor::getAttributeIds(vector<string>& attributes) const
157 {
158     if (!m_attributeProfiles.empty())
159         attributes.push_back(m_attributeProfiles);
160     if (!m_errorURL.empty())
161         attributes.push_back(m_errorURL);
162     if (!m_displayName.empty())
163         attributes.push_back(m_displayName);
164     if (!m_description.empty())
165         attributes.push_back(m_description);
166     if (!m_informationURL.empty())
167         attributes.push_back(m_informationURL);
168     if (!m_privacyURL.empty())
169         attributes.push_back(m_privacyURL);
170     if (!m_orgName.empty())
171         attributes.push_back(m_orgName);
172     if (!m_orgDisplayName.empty())
173         attributes.push_back(m_orgDisplayName);
174     if (!m_orgURL.empty())
175         attributes.push_back(m_orgURL);
176     static void (vector<string>::* push_back)(const string&) = &vector<string>::push_back;
177     static const string& (contact_tuple_t::* tget)() const = &contact_tuple_t::get<0>;
178     static const string& (logo_tuple_t::* tget2)() const = &logo_tuple_t::get<0>;
179     for_each(m_contacts.begin(), m_contacts.end(), boost::bind(push_back, boost::ref(attributes), boost::bind(tget, _1)));
180     for_each(m_logos.begin(), m_logos.end(), boost::bind(push_back, boost::ref(attributes), boost::bind(tget2, _1)));
181 }
182
183 void MetadataExtractor::extractAttributes(
184     const Application& application,
185     const GenericRequest* request,
186     const RoleDescriptor* issuer,
187     const XMLObject& xmlObject,
188     vector<shibsp::Attribute*>& attributes
189     ) const
190 {
191     const RoleDescriptor* roleToExtract = dynamic_cast<const RoleDescriptor*>(&xmlObject);
192     if (!roleToExtract)
193         return;
194
195     if (!m_attributeProfiles.empty()) {
196         const vector<AttributeProfile*>* profiles = nullptr;
197         const IDPSSODescriptor* idpRole = dynamic_cast<const IDPSSODescriptor*>(roleToExtract);
198         if (idpRole) {
199             profiles = &(idpRole->getAttributeProfiles());
200         }
201         else {
202             const AttributeAuthorityDescriptor* aaRole = dynamic_cast<const AttributeAuthorityDescriptor*>(roleToExtract);
203             if (aaRole) {
204                 profiles = &(aaRole->getAttributeProfiles());
205             }
206         }
207         if (profiles && !profiles->empty()) {
208             auto_ptr<SimpleAttribute> attr(new SimpleAttribute(vector<string>(1, m_attributeProfiles)));
209             for (indirect_iterator<vector<AttributeProfile*>::const_iterator> i = make_indirect_iterator(profiles->begin());
210                     i != make_indirect_iterator(profiles->end()); ++i) {
211                 auto_ptr_char temp(i->getProfileURI());
212                 if (temp.get())
213                     attr->getValues().push_back(temp.get());
214             }
215             if (attr->valueCount() > 0) {
216                 attributes.push_back(attr.get());
217                 attr.release();
218             }
219         }
220     }
221
222     if (!m_errorURL.empty() && roleToExtract->getErrorURL()) {
223         auto_ptr_char temp(roleToExtract->getErrorURL());
224         if (temp.get() && *temp.get()) {
225             auto_ptr<SimpleAttribute> attr(new SimpleAttribute(vector<string>(1, m_errorURL)));
226             attr->getValues().push_back(temp.get());
227             attributes.push_back(attr.get());
228             attr.release();
229         }
230     }
231
232     if (!m_displayName.empty() || !m_description.empty() || !m_informationURL.empty() || !m_privacyURL.empty()) {
233         const Extensions* exts = roleToExtract->getExtensions();
234         if (exts) {
235             const UIInfo* ui;
236             for (vector<XMLObject*>::const_iterator ext = exts->getUnknownXMLObjects().begin(); ext != exts->getUnknownXMLObjects().end(); ++ext) {
237                 ui = dynamic_cast<const UIInfo*>(*ext);
238                 if (ui) {
239                     doLangSensitive(request, ui->getDisplayNames(), m_displayName, attributes);
240                     doLangSensitive(request, ui->getDescriptions(), m_description, attributes);
241                     doLangSensitive(request, ui->getInformationURLs(), m_informationURL, attributes);
242                     doLangSensitive(request, ui->getPrivacyStatementURLs(), m_privacyURL, attributes);
243                     const vector<Logo*>& logos = ui->getLogos();
244                     if (!logos.empty()) {
245                         for_each(
246                             m_logos.begin(), m_logos.end(),
247                             boost::bind(&MetadataExtractor::doLogo, this, request, boost::ref(logos), _1, boost::ref(attributes))
248                             );
249                     }
250                     break;
251                 }
252             }
253         }
254     }
255
256     if (!m_orgName.empty() || !m_orgDisplayName.empty() || !m_orgURL.empty()) {
257         const Organization* org = roleToExtract->getOrganization();
258         if (!org)
259             org = dynamic_cast<EntityDescriptor*>(roleToExtract->getParent())->getOrganization();
260         if (org) {
261             doLangSensitive(request, org->getOrganizationNames(), m_orgName, attributes);
262             doLangSensitive(request, org->getOrganizationDisplayNames(), m_orgDisplayName, attributes);
263             doLangSensitive(request, org->getOrganizationURLs(), m_orgURL, attributes);
264         }
265     }
266
267     for_each(
268         m_contacts.begin(), m_contacts.end(),
269         boost::bind(&MetadataExtractor::doContactPerson, this, roleToExtract, _1, boost::ref(attributes))
270         );
271 }
272
273 template <class T> void MetadataExtractor::doLangSensitive(
274     const GenericRequest* request, const vector<T*>& objects, const string& id, vector<shibsp::Attribute*>& attributes
275     ) const
276 {
277     if (objects.empty() || id.empty())
278         return;
279
280     T* match = nullptr;
281     if (request && request->startLangMatching()) {
282         do {
283             for (typename vector<T*>::const_iterator i = objects.begin(); !match && i != objects.end(); ++i) {
284                 if (request->matchLang((*i)->getLang()))
285                     match = *i;
286             }
287         } while (!match && request->continueLangMatching());
288     }
289     if (!match)
290         match = objects.front();
291
292     auto_ptr_char temp(match->getTextContent());
293     if (temp.get() && *temp.get()) {
294         auto_ptr<SimpleAttribute> attr(new SimpleAttribute(vector<string>(1, id)));
295         attr->getValues().push_back(temp.get());
296         attributes.push_back(attr.get());
297         attr.release();
298     }
299 }
300
301 void MetadataExtractor::doLogo(
302     const GenericRequest* request, const vector<Logo*>& logos, const logo_tuple_t& params, vector<shibsp::Attribute*>& attributes
303     ) const
304 {
305     if (logos.empty())
306         return;
307
308     pair<bool,int> dim;
309     Logo* match = nullptr;
310     int h = params.get<1>(), w = params.get<2>(), sizediff, bestdiff = INT_MAX;
311     if (request && request->startLangMatching()) {
312         do {
313             for (vector<Logo*>::const_iterator i = logos.begin(); i != logos.end(); ++i) {
314                 if (request->matchLang((*i)->getLang())) {
315                     sizediff = 0;
316                     if (h > 0) {
317                         dim = (*i)->getHeight();
318                         sizediff = abs(h - dim.second);
319                     }
320                     if (w > 0) {
321                         dim = (*i)->getWidth();
322                         sizediff += abs(w - dim.second);
323                     }
324                     if (sizediff > 0) {
325                         if (sizediff < bestdiff)
326                             match = *i;
327                     }
328                     else {
329                         match = *i;
330                     }
331                 }
332                 if (match && h == 0 && w == 0)
333                     break;
334             }
335             if (match && h == 0 && w == 0)
336                 break;
337         } while (request->continueLangMatching());
338     }
339     else if (h > 0 || w > 0) {
340         for (vector<Logo*>::const_iterator i = logos.begin(); i != logos.end(); ++i) {
341             sizediff = 0;
342             if (h > 0) {
343                 dim = (*i)->getHeight();
344                 sizediff = abs(h - dim.second);
345             }
346             if (w > 0) {
347                 dim = (*i)->getWidth();
348                 sizediff += abs(w - dim.second);
349             }
350             if (sizediff > 0) {
351                 if (sizediff < bestdiff)
352                     match = *i;
353             }
354             else {
355                 match = *i;
356             }
357         }
358     }
359     else {
360         match = logos.front();
361     }
362
363     if (!match->getDOM()) {
364         match->marshall();
365     }
366     vector<string> ids(1, params.get<0>());
367     auto_ptr<Attribute> attr(params.get<3>()->decode(ids, match));
368     if (attr.get()) {
369         attributes.push_back(attr.get());
370         attr.release();
371     }
372 }
373
374 void MetadataExtractor::doContactPerson(
375     const RoleDescriptor* role, const contact_tuple_t& params, vector<shibsp::Attribute*>& attributes
376     ) const
377 {
378     const XMLCh* ctype = params.get<1>().c_str();
379     static bool (*eq)(const XMLCh*, const XMLCh*) = &XMLString::equals;
380     const ContactPerson* cp = find_if(role->getContactPersons(),boost::bind(eq, ctype, boost::bind(&ContactPerson::getContactType, _1)));
381     if (!cp) {
382         cp = find_if(dynamic_cast<EntityDescriptor*>(role->getParent())->getContactPersons(),
383                 boost::bind(eq, ctype, boost::bind(&ContactPerson::getContactType, _1)));
384     }
385
386     if (cp) {
387         if (!cp->getDOM()) {
388             cp->marshall();
389         }
390         vector<string> ids(1, params.get<0>());
391         auto_ptr<Attribute> attr(params.get<2>()->decode(ids, cp));
392         if (attr.get()) {
393             attributes.push_back(attr.get());
394             attr.release();
395         }
396     }
397 }