5c912aab9c809230fc9a5dc8b319fc3ddfd7bf06
[shibboleth/sp.git] / shibsp / handler / impl / MetadataGenerator.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  * MetadataGenerator.cpp
23  *
24  * Handler for generating "approximate" metadata based on SP configuration.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "exceptions.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/RemotedHandler.h"
33 #include "handler/SecuredHandler.h"
34
35 #include <boost/scoped_ptr.hpp>
36 #include <boost/iterator/indirect_iterator.hpp>
37
38 #ifndef SHIBSP_LITE
39 # include "attribute/resolver/AttributeExtractor.h"
40 # include "metadata/MetadataProviderCriteria.h"
41 # include <boost/ptr_container/ptr_vector.hpp>
42 # include <saml/exceptions.h>
43 # include <saml/SAMLConfig.h>
44 # include <saml/signature/ContentReference.h>
45 # include <saml/saml2/metadata/Metadata.h>
46 # include <saml/saml2/metadata/MetadataProvider.h>
47 # include <xmltooling/XMLToolingConfig.h>
48 # include <xmltooling/security/Credential.h>
49 # include <xmltooling/security/CredentialCriteria.h>
50 # include <xmltooling/security/SecurityHelper.h>
51 # include <xmltooling/signature/Signature.h>
52 # include <xmltooling/util/ParserPool.h>
53 # include <xmltooling/util/PathResolver.h>
54 # include <xercesc/framework/LocalFileInputSource.hpp>
55 # include <xercesc/framework/Wrapper4InputSource.hpp>
56 #endif
57
58
59 using namespace shibsp;
60 #ifndef SHIBSP_LITE
61 using namespace opensaml::saml2md;
62 using namespace opensaml;
63 using namespace xmlsignature;
64 #endif
65 using namespace xmltooling;
66 using namespace boost;
67 using namespace std;
68
69 namespace shibsp {
70
71 #if defined (_MSC_VER)
72     #pragma warning( push )
73     #pragma warning( disable : 4250 )
74 #endif
75
76     class SHIBSP_API MetadataGenerator : public SecuredHandler, public RemotedHandler
77     {
78     public:
79         MetadataGenerator(const DOMElement* e, const char* appId);
80         virtual ~MetadataGenerator() {}
81
82         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
83         void receive(DDF& in, ostream& out);
84
85     private:
86         pair<bool,long> processMessage(
87             const Application& application,
88             const char* handlerURL,
89             const char* entityID,
90             HTTPResponse& httpResponse
91             ) const;
92
93 #ifndef SHIBSP_LITE
94         string m_salt;
95         short m_http,m_https;
96         vector<string> m_bases;
97         scoped_ptr<UIInfo> m_uiinfo;
98         scoped_ptr<Organization> m_org;
99         scoped_ptr<EntityAttributes> m_entityAttrs;
100         ptr_vector<ContactPerson> m_contacts;
101         ptr_vector<NameIDFormat> m_formats;
102         ptr_vector<RequestedAttribute> m_reqAttrs;
103         ptr_vector<AttributeConsumingService> m_attrConsumers;
104 #endif
105     };
106
107 #if defined (_MSC_VER)
108     #pragma warning( pop )
109 #endif
110
111     Handler* SHIBSP_DLLLOCAL MetadataGeneratorFactory(const pair<const DOMElement*,const char*>& p)
112     {
113         return new MetadataGenerator(p.first, p.second);
114     }
115
116 };
117
118 MetadataGenerator::MetadataGenerator(const DOMElement* e, const char* appId)
119     : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".MetadataGenerator"))
120 #ifndef SHIBSP_LITE
121         ,m_http(0), m_https(0)
122 #endif
123 {
124     string address(appId);
125     address += getString("Location").second;
126     setAddress(address.c_str());
127
128 #ifndef SHIBSP_LITE
129     static XMLCh EndpointBase[] = UNICODE_LITERAL_12(E,n,d,p,o,i,n,t,B,a,s,e);
130
131     pair<bool,const char*> salt = getString("salt");
132     if (salt.first)
133         m_salt = salt.second;
134
135     pair<bool,bool> flag = getBool("http");
136     if (flag.first)
137         m_http = flag.second ? 1 : -1;
138     flag = getBool("https");
139     if (flag.first)
140         m_https = flag.second ? 1 : -1;
141
142     e = XMLHelper::getFirstChildElement(e);
143     while (e) {
144         if (XMLString::equals(e->getLocalName(), EndpointBase) && e->hasChildNodes()) {
145             auto_ptr_char base(e->getFirstChild()->getNodeValue());
146             if (base.get() && *base.get())
147                 m_bases.push_back(base.get());
148         }
149         else {
150             // Try and parse the object.
151             auto_ptr<XMLObject> child(XMLObjectBuilder::buildOneFromElement(const_cast<DOMElement*>(e)));
152             ContactPerson* cp = dynamic_cast<ContactPerson*>(child.get());
153             if (cp) {
154                 m_contacts.push_back(cp);
155                 child.release();
156             }
157             else {
158                 NameIDFormat* nif = dynamic_cast<NameIDFormat*>(child.get());
159                 if (nif) {
160                     m_formats.push_back(nif);
161                     child.release();
162                 }
163                 else {
164                     RequestedAttribute* req = dynamic_cast<RequestedAttribute*>(child.get());
165                     if (req) {
166                         m_reqAttrs.push_back(req);
167                         child.release();
168                     }
169                     else {
170                         AttributeConsumingService* acs = dynamic_cast<AttributeConsumingService*>(child.get());
171                         if (acs) {
172                             m_attrConsumers.push_back(acs);
173                             child.release();
174                         }
175                         else {
176                             UIInfo* info = dynamic_cast<UIInfo*>(child.get());
177                             if (info) {
178                                 if (!m_uiinfo) {
179                                     m_uiinfo.reset(info);
180                                     child.release();
181                                 }
182                                 else {
183                                     m_log.warn("skipping duplicate UIInfo element");
184                                 }
185                             }
186                             else {
187                                 Organization* org = dynamic_cast<Organization*>(child.get());
188                                 if (org) {
189                                     if (!m_org) {
190                                         m_org.reset(org);
191                                         child.release();
192                                     }
193                                     else {
194                                         m_log.warn("skipping duplicate Organization element");
195                                     }
196                                 }
197                                 else {
198                                     EntityAttributes* ea = dynamic_cast<EntityAttributes*>(child.get());
199                                     if (ea) {
200                                         if (!m_entityAttrs) {
201                                             m_entityAttrs.reset(ea);
202                                             child.release();
203                                         }
204                                         else {
205                                             m_log.warn("skipping duplicate EntityAttributes element");
206                                         }
207                                     }
208                                 }
209                             }
210                         }
211                     }
212                 }
213             }
214         }
215         e = XMLHelper::getNextSiblingElement(e);
216     }
217 #endif
218 }
219
220 pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
221 {
222     // Check ACL in base class.
223     pair<bool,long> ret = SecuredHandler::run(request, isHandler);
224     if (ret.first)
225         return ret;
226
227     try {
228         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
229             // When out of process, we run natively and directly process the message.
230             return processMessage(request.getApplication(), request.getHandlerURL(), request.getParameter("entityID"), request);
231         }
232         else {
233             // When not out of process, we remote all the message processing.
234             DDF out,in = DDF(m_address.c_str());
235             in.addmember("application_id").string(request.getApplication().getId());
236             in.addmember("handler_url").string(request.getHandlerURL());
237             if (request.getParameter("entityID"))
238                 in.addmember("entity_id").string(request.getParameter("entityID"));
239             DDFJanitor jin(in), jout(out);
240
241             out = request.getServiceProvider().getListenerService()->send(in);
242             return unwrap(request, out);
243         }
244     }
245     catch (std::exception& ex) {
246         m_log.error("error while processing request: %s", ex.what());
247         istringstream msg("Metadata Request Failed");
248         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
249     }
250 }
251
252 void MetadataGenerator::receive(DDF& in, ostream& out)
253 {
254     // Find application.
255     const char* aid = in["application_id"].string();
256     const char* hurl = in["handler_url"].string();
257     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
258     if (!app) {
259         // Something's horribly wrong.
260         m_log.error("couldn't find application (%s) for metadata request", aid ? aid : "(missing)");
261         throw ConfigurationException("Unable to locate application for metadata request, deleted?");
262     }
263     else if (!hurl) {
264         throw ConfigurationException("Missing handler_url parameter in remoted method call.");
265     }
266
267     // Wrap a response shim.
268     DDF ret(nullptr);
269     DDFJanitor jout(ret);
270     scoped_ptr<HTTPResponse> resp(getResponse(ret));
271
272     // Since we're remoted, the result should either be a throw, a false/0 return,
273     // which we just return as an empty structure, or a response/redirect,
274     // which we capture in the facade and send back.
275     processMessage(*app, hurl, in["entity_id"].string(), *resp);
276     out << ret;
277 }
278
279 pair<bool,long> MetadataGenerator::processMessage(
280     const Application& application, const char* handlerURL, const char* entityID, HTTPResponse& httpResponse
281     ) const
282 {
283 #ifndef SHIBSP_LITE
284     m_log.debug("processing metadata request");
285
286     const PropertySet* relyingParty = nullptr;
287     if (entityID) {
288         MetadataProvider* m = application.getMetadataProvider();
289         Locker locker(m);
290         MetadataProviderCriteria mc(application, entityID);
291         relyingParty = application.getRelyingParty(m->getEntityDescriptor(mc).first);
292     }
293     else {
294         relyingParty = &application;
295     }
296
297     scoped_ptr<EntityDescriptor> entity;
298     pair<bool,const char*> prop = getString("template");
299     if (prop.first) {
300         // Load a template to use for our metadata.
301         string templ(prop.second);
302         XMLToolingConfig::getConfig().getPathResolver()->resolve(templ, PathResolver::XMLTOOLING_CFG_FILE);
303         auto_ptr_XMLCh widenit(templ.c_str());
304         LocalFileInputSource src(widenit.get());
305         Wrapper4InputSource dsrc(&src,false);
306         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(dsrc);
307         XercesJanitor<DOMDocument> docjan(doc);
308         auto_ptr<XMLObject> xmlobj(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
309         docjan.release();
310         entity.reset(dynamic_cast<EntityDescriptor*>(xmlobj.get()));
311         xmlobj.release();
312         if (!entity)
313             throw ConfigurationException("Template file ($1) did not contain an EntityDescriptor", params(1, templ.c_str()));
314     }
315     else {
316         entity.reset(EntityDescriptorBuilder::buildEntityDescriptor());
317     }
318
319     if (!entity->getID()) {
320         string hashinput = m_salt + relyingParty->getString("entityID").second;
321         string hashed = '_' + SecurityHelper::doHash("SHA1", hashinput.c_str(), hashinput.length());
322         auto_ptr_XMLCh widenit(hashed.c_str());
323         entity->setID(widenit.get());
324     }
325
326     pair<bool,unsigned int> cache = getUnsignedInt("cacheDuration");
327     if (cache.first) {
328         entity->setCacheDuration(cache.second);
329     }
330     cache = getUnsignedInt("validUntil");
331     if (cache.first)
332         entity->setValidUntil(time(nullptr) + cache.second);
333     entity->setEntityID(relyingParty->getXMLString("entityID").second);
334
335     if (m_org && !entity->getOrganization())
336         entity->setOrganization(m_org->cloneOrganization());
337
338     for (ptr_vector<ContactPerson>::const_iterator cp = m_contacts.begin(); cp != m_contacts.end(); ++cp)
339         entity->getContactPersons().push_back(cp->cloneContactPerson());
340
341     if (m_entityAttrs) {
342         if (!entity->getExtensions())
343             entity->setExtensions(ExtensionsBuilder::buildExtensions());
344         entity->getExtensions()->getUnknownXMLObjects().push_back(m_entityAttrs->cloneEntityAttributes());
345     }
346
347     SPSSODescriptor* role;
348     if (entity->getSPSSODescriptors().empty()) {
349         role = SPSSODescriptorBuilder::buildSPSSODescriptor();
350         entity->getSPSSODescriptors().push_back(role);
351     }
352     else {
353         role = entity->getSPSSODescriptors().front();
354     }
355
356     for (ptr_vector<NameIDFormat>::const_iterator nif = m_formats.begin(); nif != m_formats.end(); ++nif)
357         role->getNameIDFormats().push_back(nif->cloneNameIDFormat());
358
359     if (m_uiinfo) {
360         if (!role->getExtensions())
361             role->setExtensions(ExtensionsBuilder::buildExtensions());
362         role->getExtensions()->getUnknownXMLObjects().push_back(m_uiinfo->cloneUIInfo());
363     }
364
365     for (ptr_vector<AttributeConsumingService>::const_iterator acs = m_attrConsumers.begin(); acs != m_attrConsumers.end(); ++acs)
366         role->getAttributeConsumingServices().push_back(acs->cloneAttributeConsumingService());
367
368     if (!m_reqAttrs.empty()) {
369         int index = 1;
370         const vector<AttributeConsumingService*>& svcs = const_cast<const SPSSODescriptor*>(role)->getAttributeConsumingServices();
371         for (indirect_iterator<vector<AttributeConsumingService*>::const_iterator> s = make_indirect_iterator(svcs.begin());
372                 s != make_indirect_iterator(svcs.end()); ++s) {
373             pair<bool,int> i = s->getIndex();
374             if (i.first && index == i.second)
375                 index = i.second + 1;
376         }
377         AttributeConsumingService* svc = AttributeConsumingServiceBuilder::buildAttributeConsumingService();
378         role->getAttributeConsumingServices().push_back(svc);
379         svc->setIndex(index);
380         ServiceName* sn = ServiceNameBuilder::buildServiceName();
381         svc->getServiceNames().push_back(sn);
382         sn->setName(entity->getEntityID());
383         static const XMLCh english[] = UNICODE_LITERAL_2(e,n);
384         sn->setLang(english);
385         for (ptr_vector<RequestedAttribute>::const_iterator req = m_reqAttrs.begin(); req != m_reqAttrs.end(); ++req)
386             svc->getRequestedAttributes().push_back(req->cloneRequestedAttribute());
387     }
388
389     // Policy flags.
390     prop = relyingParty->getString("signing");
391     if (prop.first && (!strcmp(prop.second,"true") || !strcmp(prop.second,"front")))
392         role->AuthnRequestsSigned(true);
393     pair<bool,bool> flagprop = relyingParty->getBool("requireSignedAssertions");
394     if (flagprop.first && flagprop.second)
395         role->WantAssertionsSigned(true);
396
397     // Ask each handler to generate itself.
398     vector<const Handler*> handlers;
399     application.getHandlers(handlers);
400     for (indirect_iterator<vector<const Handler*>::const_iterator> h = make_indirect_iterator(handlers.begin());
401             h != make_indirect_iterator(handlers.end()); ++h) {
402         if (m_bases.empty()) {
403             if (strncmp(handlerURL, "https", 5) == 0) {
404                 if (m_https >= 0)
405                     h->generateMetadata(*role, handlerURL);
406                 if (m_http == 1) {
407                     string temp(handlerURL);
408                     temp.erase(4, 1);
409                     h->generateMetadata(*role, temp.c_str());
410                 }
411             }
412             else {
413                 if (m_http >= 0)
414                     h->generateMetadata(*role, handlerURL);
415                 if (m_https == 1) {
416                     string temp(handlerURL);
417                     temp.insert(temp.begin() + 4, 's');
418                     h->generateMetadata(*role, temp.c_str());
419                 }
420             }
421         }
422         else {
423             for (vector<string>::const_iterator b = m_bases.begin(); b != m_bases.end(); ++b)
424                 h->generateMetadata(*role, b->c_str());
425         }
426     }
427
428     AttributeExtractor* extractor = application.getAttributeExtractor();
429     if (extractor) {
430         Locker extlocker(extractor);
431         extractor->generateMetadata(*role);
432     }
433
434     CredentialResolver* credResolver = application.getCredentialResolver();
435     if (credResolver) {
436         Locker credLocker(credResolver);
437         CredentialCriteria cc;
438         prop = relyingParty->getString("keyName");
439         if (prop.first)
440             cc.getKeyNames().insert(prop.second);
441         vector<const Credential*> signingcreds,enccreds;
442         cc.setUsage(Credential::SIGNING_CREDENTIAL);
443         credResolver->resolve(signingcreds, &cc);
444         cc.setUsage(Credential::ENCRYPTION_CREDENTIAL);
445         credResolver->resolve(enccreds, &cc);
446
447         for (vector<const Credential*>::const_iterator c = signingcreds.begin(); c != signingcreds.end(); ++c) {
448             KeyInfo* kinfo = (*c)->getKeyInfo();
449             if (kinfo) {
450                 KeyDescriptor* kd = KeyDescriptorBuilder::buildKeyDescriptor();
451                 kd->setKeyInfo(kinfo);
452                 const XMLCh* use = KeyDescriptor::KEYTYPE_SIGNING;
453                 for (vector<const Credential*>::iterator match = enccreds.begin(); match != enccreds.end(); ++match) {
454                     if (*match == *c) {
455                         use = nullptr;
456                         enccreds.erase(match);
457                         break;
458                     }
459                 }
460                 kd->setUse(use);
461                 role->getKeyDescriptors().push_back(kd);
462             }
463         }
464
465         for (vector<const Credential*>::const_iterator c = enccreds.begin(); c != enccreds.end(); ++c) {
466             KeyInfo* kinfo = (*c)->getKeyInfo();
467             if (kinfo) {
468                 KeyDescriptor* kd = KeyDescriptorBuilder::buildKeyDescriptor();
469                 kd->setUse(KeyDescriptor::KEYTYPE_ENCRYPTION);
470                 kd->setKeyInfo(kinfo);
471                 role->getKeyDescriptors().push_back(kd);
472             }
473         }
474     }
475
476     // Stream for response.
477     stringstream s;
478
479     // Self-sign it?
480     pair<bool,bool> flag = getBool("signing");
481     if (flag.first && flag.second) {
482         if (credResolver) {
483             Locker credLocker(credResolver);
484             // Fill in criteria to use.
485             CredentialCriteria cc;
486             cc.setUsage(Credential::SIGNING_CREDENTIAL);
487             prop = getString("keyName");
488             if (prop.first)
489                 cc.getKeyNames().insert(prop.second);
490             pair<bool,const XMLCh*> sigalg = getXMLString("signingAlg");
491             pair<bool,const XMLCh*> digalg = getXMLString("digestAlg");
492             if (sigalg.first)
493                 cc.setXMLAlgorithm(sigalg.second);
494             const Credential* cred = credResolver->resolve(&cc);
495             if (!cred)
496                 throw XMLSecurityException("Unable to obtain signing credential to use.");
497
498             // Pretty-print it first and then read it back in.
499             stringstream pretty;
500             XMLHelper::serialize(entity->marshall(), pretty, true);
501             DOMDocument* prettydoc = XMLToolingConfig::getConfig().getParser().parse(pretty);
502             scoped_ptr<XMLObject> prettyentity(XMLObjectBuilder::buildOneFromElement(prettydoc->getDocumentElement(), true));
503
504             Signature* sig = SignatureBuilder::buildSignature();
505             dynamic_cast<EntityDescriptor*>(prettyentity.get())->setSignature(sig);
506             if (sigalg.first)
507                 sig->setSignatureAlgorithm(sigalg.second);
508             if (digalg.first) {
509                 opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
510                 if (cr)
511                     cr->setDigestAlgorithm(digalg.second);
512             }
513
514             // Sign while marshalling.
515             vector<Signature*> sigs(1,sig);
516             prettyentity->marshall(prettydoc,&sigs,cred);
517             s << *prettyentity;
518         }
519         else {
520             throw FatalProfileException("Can't self-sign metadata, no credential resolver found.");
521         }
522     }
523     else {
524         // Pretty-print it directly to client.
525         XMLHelper::serialize(entity->marshall(), s, true);
526     }
527
528     prop = getString("mimeType");
529     httpResponse.setContentType(prop.first ? prop.second : "application/samlmetadata+xml");
530     return make_pair(true, httpResponse.sendResponse(s));
531 #else
532     return make_pair(false, 0L);
533 #endif
534 }