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