c23dc634124ef29b6804b8b6c7c910fe030d21f5
[shibboleth/cpp-sp-resolver.git] / src / shibresolver / resolver.cpp
1 /**
2  * See the NOTICE file distributed with this work for information
3  * regarding copyright ownership. Licensed under the Apache License,
4  * Version 2.0 (the "License"); you may not use this file except in
5  * compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /**
17  * resolver.cpp
18  *
19  * An embeddable component interface to Shibboleth SP attribute processing.
20  */
21
22 #include "internal.h"
23
24 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
25 # ifdef SHIBRESOLVER_HAVE_GSSMIT
26 #  include <gssapi/gssapi_ext.h>
27 # endif
28 #endif
29
30 #include <shibsp/exceptions.h>
31 #include <shibsp/Application.h>
32 #include <shibsp/GSSRequest.h>
33 #include <shibsp/SPRequest.h>
34 #include <shibsp/ServiceProvider.h>
35 #include <shibsp/attribute/Attribute.h>
36 #include <shibsp/remoting/ListenerService.h>
37 #ifndef SHIBSP_LITE
38 # include <saml/saml2/metadata/Metadata.h>
39 # include <saml/saml2/metadata/MetadataProvider.h>
40 # include <saml/util/SAMLConstants.h>
41 # include <shibsp/attribute/filtering/AttributeFilter.h>
42 # include <shibsp/attribute/filtering/BasicFilteringContext.h>
43 # include <shibsp/attribute/resolver/AttributeExtractor.h>
44 # include <shibsp/attribute/resolver/AttributeResolver.h>
45 # include <shibsp/attribute/resolver/ResolutionContext.h>
46 # include <shibsp/metadata/MetadataProviderCriteria.h>
47 #endif
48 #include <xmltooling/XMLObjectBuilder.h>
49 #include <xmltooling/XMLToolingConfig.h>
50 #include <xmltooling/impl/AnyElement.h>
51 #include <xmltooling/util/ParserPool.h>
52 #include <xmltooling/util/XMLHelper.h>
53 #include <xercesc/util/Base64.hpp>
54
55 using namespace shibresolver;
56 using namespace shibsp;
57 #ifndef SHIBSP_LITE
58 using namespace opensaml;
59 using namespace opensaml::saml2md;
60 #endif
61 using namespace xmltooling;
62 using namespace std;
63
64 namespace shibresolver {
65     class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
66     public:
67         RemotedResolver() {}
68         ~RemotedResolver() {}
69
70         struct Transaction {
71             ~Transaction() {
72                 for_each(tokens.begin(), tokens.end(), xmltooling::cleanup<XMLObject>());
73                 for_each(inputAttrs.begin(), inputAttrs.end(), xmltooling::cleanup<Attribute>());
74                 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<Attribute>());
75             }
76
77             vector<const XMLObject*> tokens;
78             vector<Attribute*> inputAttrs;
79             vector<Attribute*> resolvedAttrs;
80         };
81
82         void receive(DDF& in, ostream& out);
83         void resolve(
84             const Application& app,
85             const char* issuer,
86             const XMLCh* protocol,
87             const vector<const XMLObject*>& tokens,
88             const vector<Attribute*>& inputAttrs,
89             vector<Attribute*>& resolvedAttrs
90             ) const;
91
92     private:
93 #ifndef SHIBSP_LITE
94         void resolve(
95             AttributeExtractor* extractor,
96             const Application& app,
97             const RoleDescriptor* issuer,
98             const XMLObject& token,
99             vector<Attribute*>& resolvedAttrs
100             ) const;
101
102         const RoleDescriptor* lookup(
103             const Application& app,
104             MetadataProvider* m,
105             const char* entityID,
106             const XMLCh* protocol
107             ) const;
108 #endif
109     };
110
111     static RemotedResolver g_Remoted;
112 };
113
114 ShibbolethResolver* ShibbolethResolver::create()
115 {
116     return new ShibbolethResolver();
117 }
118
119 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
120 #ifdef SHIBRESOLVER_HAVE_GSSAPI
121         ,m_gsswrapper(NULL)
122 #endif
123 {
124 }
125
126 ShibbolethResolver::~ShibbolethResolver()
127 {
128 #ifdef SHIBRESOLVER_HAVE_GSSAPI
129     delete m_gsswrapper;
130 #endif
131     for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
132     if (m_sp)
133         m_sp->unlock();
134 }
135
136 void ShibbolethResolver::setRequest(const SPRequest* request)
137 {
138     m_request = request;
139 #if defined(SHIBSP_HAVE_GSSAPI) && defined (SHIBRESOLVER_HAVE_GSSAPI)
140     if (request) {
141         const GSSRequest* gss = dynamic_cast<const GSSRequest*>(request);
142         if (gss) {
143             // TODO: fix API to prevent destruction of contexts
144             gss_ctx_id_t ctx = gss->getGSSContext();
145             addToken(&ctx);
146         }
147     }
148 #endif
149 }
150
151 void ShibbolethResolver::setApplicationID(const char* appID)
152 {
153     m_appID.erase();
154     if (appID)
155         m_appID = appID;
156 }
157
158 void ShibbolethResolver::setIssuer(const char* issuer)
159 {
160     m_issuer.erase();
161     if (issuer)
162         m_issuer = issuer;
163 }
164
165 void ShibbolethResolver::setProtocol(const XMLCh* protocol)
166 {
167     m_protocol.erase();
168     if (protocol)
169         m_protocol = protocol;
170 }
171
172 void ShibbolethResolver::addToken(const XMLObject* token)
173 {
174     if (token)
175         m_tokens.push_back(token);
176 }
177
178 #ifdef SHIBRESOLVER_HAVE_GSSAPI
179 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
180 {
181     if (m_gsswrapper) {
182         delete m_gsswrapper;
183         m_gsswrapper = NULL;
184     }
185
186     if (ctx && *ctx != GSS_C_NO_CONTEXT) {
187         OM_uint32 minor;
188         gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
189         OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
190         if (major == GSS_S_COMPLETE) {
191             xsecsize_t len=0;
192             XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
193             if (out) {
194                 string s;
195                 s.append(reinterpret_cast<char*>(out), len);
196                 auto_ptr_XMLCh temp(s.c_str());
197 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
198                 XMLString::release(&out);
199 #else
200                 XMLString::release((char**)&out);
201 #endif
202                 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
203                 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
204                 m_gsswrapper->setTextContent(temp.get());
205             }
206             else {
207                 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
208             }
209             gss_release_buffer(&minor, &contextbuf);
210         }
211         else {
212             Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
213         }
214     }
215 }
216
217 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
218 void ShibbolethResolver::addToken(gss_name_t name)
219 {
220     if (m_gsswrapper) {
221         delete m_gsswrapper;
222         m_gsswrapper = NULL;
223     }
224
225     OM_uint32 minor;
226     gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
227     OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
228     if (major == GSS_S_COMPLETE) {
229         addToken(&namebuf);
230         gss_release_buffer(&minor, &namebuf);
231     }
232     else {
233         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
234     }
235 }
236 #endif
237
238 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
239 {
240     if (m_gsswrapper) {
241         delete m_gsswrapper;
242         m_gsswrapper = NULL;
243     }
244
245     xsecsize_t len=0;
246     XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
247     if (out) {
248         string s;
249         s.append(reinterpret_cast<char*>(out), len);
250         auto_ptr_XMLCh temp(s.c_str());
251 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
252         XMLString::release(&out);
253 #else
254         XMLString::release((char**)&out);
255 #endif
256         static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
257         m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
258         m_gsswrapper->setTextContent(temp.get());
259     }
260     else {
261         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
262     }
263 }
264
265 #endif
266
267 void ShibbolethResolver::addAttribute(Attribute* attr)
268 {
269     if (attr)
270         m_inputAttributes.push_back(attr);
271 }
272
273 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
274 {
275     return m_resolvedAttributes;
276 }
277
278 RequestMapper::Settings ShibbolethResolver::getSettings() const
279 {
280     if (!m_request)
281         throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
282     return m_request->getRequestSettings();
283 }
284
285 void ShibbolethResolver::resolve()
286 {
287     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
288     SPConfig& conf = SPConfig::getConfig();
289     if (!m_request) {
290         m_sp = conf.getServiceProvider();
291         m_sp->lock();
292         if (m_appID.empty())
293             m_appID = "default";
294     }
295
296     const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
297     if (!app)
298         throw ConfigurationException("Unable to locate application for resolution.");
299
300 #ifdef SHIBRESOLVER_HAVE_GSSAPI
301     if (m_gsswrapper)
302         m_tokens.push_back(m_gsswrapper);
303 #endif
304
305     if (conf.isEnabled(SPConfig::OutOfProcess)) {
306         g_Remoted.resolve(
307             *app,
308             m_issuer.c_str(),
309             m_protocol.c_str(),
310             m_tokens,
311             m_inputAttributes,
312             m_resolvedAttributes
313             );
314     }
315     else {
316         // When not out of process, we remote all the message processing.
317         DDF out,in = DDF("org.project-moonshot.shibresolver");
318         DDFJanitor jin(in), jout(out);
319         in.addmember("application_id").string(app->getId());
320         if (!m_issuer.empty())
321             in.addmember("issuer").string(m_issuer.c_str());
322         if (!m_protocol.empty()) {
323             auto_ptr_char prot(m_protocol.c_str());
324             in.addmember("protocol").string(prot.get());
325         }
326         if (!m_tokens.empty()) {
327             DDF& tokens = in.addmember("tokens").list();
328             for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
329                 ostringstream os;
330                 os << *(*t);
331                 tokens.add(DDF(NULL).string(os.str().c_str()));
332             }
333         }
334         if (!m_inputAttributes.empty()) {
335             DDF attr;
336             DDF& attrs = in.addmember("attributes").list();
337             for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
338                 attr = (*a)->marshall();
339                 attrs.add(attr);
340             }
341         }
342
343         out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
344
345         Attribute* attribute;
346         DDF attr = out.first();
347         while (!attr.isnull()) {
348             try {
349                 attribute = Attribute::unmarshall(attr);
350                 m_resolvedAttributes.push_back(attribute);
351                 if (log.isDebugEnabled())
352                     log.debug("unmarshalled attribute (ID: %s) with %d value%s",
353                         attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
354             }
355             catch (AttributeException& ex) {
356                 const char* id = attr.first().name();
357                 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
358             }
359             attr = out.next();
360         }
361     }
362 }
363
364 void RemotedResolver::receive(DDF& in, ostream& out)
365 {
366     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
367
368     // Find application.
369     const char* aid = in["application_id"].string();
370     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
371     if (!app) {
372         // Something's horribly wrong.
373         log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
374         throw ConfigurationException("Unable to locate application for resolution, deleted?");
375     }
376
377     DDF ret(NULL);
378     DDFJanitor jout(ret);
379
380     Transaction t;
381
382     DDF tlist = in["tokens"];
383     DDF token = tlist.first();
384     while (token.isstring()) {
385         // Parse and bind the document into an XMLObject.
386         istringstream instr(token.string());
387         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
388         XercesJanitor<DOMDocument> janitor(doc);
389         XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
390         t.tokens.push_back(xmlObject);
391         janitor.release();
392         token = tlist.next();
393     }
394
395     DDF alist = in["attributes"];
396     Attribute* attribute;
397     DDF attr = alist.first();
398     while (!attr.isnull()) {
399         attribute = Attribute::unmarshall(attr);
400         t.inputAttrs.push_back(attribute);
401         if (log.isDebugEnabled())
402             log.debug("unmarshalled attribute (ID: %s) with %d value%s",
403                 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
404         attr = alist.next();
405     }
406
407     auto_ptr_XMLCh prot(in["protocol"].string());
408
409     resolve(*app, in["issuer"].string(), prot.get(), t.tokens, t.inputAttrs, t.resolvedAttrs);
410
411     if (!t.resolvedAttrs.empty()) {
412         ret.list();
413         for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
414             attr = (*a)->marshall();
415             ret.add(attr);
416         }
417     }
418
419     out << ret;
420 }
421
422 void RemotedResolver::resolve(
423     const Application& app,
424     const char* issuer,
425     const XMLCh* protocol,
426     const vector<const XMLObject*>& tokens,
427     const vector<Attribute*>& inputAttrs,
428     vector<Attribute*>& resolvedAttrs
429     ) const
430 {
431 #ifndef SHIBSP_LITE
432     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
433     MetadataProvider* m = app.getMetadataProvider(false);
434     Locker locker(m);
435
436     const RoleDescriptor* role = NULL;
437     if (issuer && *issuer)
438         role = lookup(app, m, issuer, protocol);
439
440     vector<const Assertion*> assertions;
441
442     AttributeExtractor* extractor = app.getAttributeExtractor();
443     if (extractor) {
444         Locker extlocker(extractor);
445         // Support metadata-based attributes for only the "top-level" issuer.
446         if (role) {
447             pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
448             if (mprefix.first) {
449                 log.debug("extracting metadata-derived attributes...");
450                 try {
451                     // We pass NULL for "issuer" because the issuer isn't the one asserting metadata-based attributes.
452                     extractor->extractAttributes(app, NULL, *role, resolvedAttrs);
453                     for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
454                         vector<string>& ids = (*a)->getAliases();
455                         for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
456                             *id = mprefix.second + *id;
457                     }
458                 }
459                 catch (exception& ex) {
460                     log.error("caught exception extracting attributes: %s", ex.what());
461                 }
462             }
463         }
464
465         log.debug("extracting pushed attributes...");
466         const RoleDescriptor* role2;
467         for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {
468             // Save off any assertions for later use by resolver.
469             role2 = NULL;
470             const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
471             if (assertion) {
472                 assertions.push_back(assertion);
473                 const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(assertion);
474                 if (saml2token && saml2token->getIssuer() && (saml2token->getIssuer()->getFormat() == NULL ||
475                         XMLString::equals(saml2token->getIssuer()->getFormat(), saml2::NameID::ENTITY))) {
476                     auto_ptr_char tokenissuer(saml2token->getIssuer()->getName());
477                     role2 = lookup(app, m, tokenissuer.get(), protocol);
478                 }
479             }
480             resolve(extractor, app, (role2 ? role2 : role), *(*t), resolvedAttrs);
481         }
482     }
483     else {
484         log.warn("no AttributeExtractor plugin installed, check log during startup");
485     }
486
487     try {
488         AttributeResolver* resolver = app.getAttributeResolver();
489         if (resolver) {
490             log.debug("resolving additional attributes...");
491
492             vector<Attribute*> inputs = inputAttrs;
493             inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
494
495             Locker locker(resolver);
496             auto_ptr<ResolutionContext> ctx(
497                 resolver->createResolutionContext(
498                     app,
499                     role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL,
500                     protocol ? protocol : samlconstants::SAML20P_NS,
501                     NULL,
502                     NULL,
503                     NULL,
504                     &assertions,
505                     &inputs
506                     )
507                 );
508             resolver->resolveAttributes(*ctx.get());
509             if (!ctx->getResolvedAttributes().empty())
510                 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
511         }
512     }
513     catch (exception& ex) {
514         log.error("attribute resolution failed: %s", ex.what());
515     }
516 #else
517     throw ConfigurationException("Cannot process request using lite version of shibsp library.");
518 #endif
519 }
520
521 #ifndef SHIBSP_LITE
522
523 void RemotedResolver::resolve(
524     AttributeExtractor* extractor,
525     const Application& app,
526     const RoleDescriptor* issuer,
527     const XMLObject& token,
528     vector<Attribute*>& resolvedAttrs
529     ) const
530 {
531     vector<Attribute*> extractedAttrs;
532     try {
533         extractor->extractAttributes(app, issuer, token, extractedAttrs);
534     }
535     catch (exception& ex) {
536         Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception extracting attributes: %s", ex.what());
537     }
538
539     AttributeFilter* filter = app.getAttributeFilter();
540     if (filter && !extractedAttrs.empty()) {
541         BasicFilteringContext fc(app, extractedAttrs, issuer);
542         Locker filtlocker(filter);
543         try {
544             filter->filterAttributes(fc, extractedAttrs);
545         }
546         catch (exception& ex) {
547             Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception filtering attributes: %s", ex.what());
548             Category::getInstance(SHIBRESOLVER_LOGCAT).error("dumping extracted attributes due to filtering exception");
549             for_each(extractedAttrs.begin(), extractedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
550             extractedAttrs.clear();
551         }
552     }
553
554     resolvedAttrs.insert(resolvedAttrs.end(), extractedAttrs.begin(), extractedAttrs.end());
555 }
556
557 const RoleDescriptor* RemotedResolver::lookup(
558     const Application& app, MetadataProvider* m, const char* entityID, const XMLCh* protocol
559     ) const
560 {
561     if (!m)
562         return NULL;
563
564     MetadataProviderCriteria idpmc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS);
565     if (protocol)
566         idpmc.protocol2 = samlconstants::SAML20P_NS;
567     pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(idpmc);
568     if (!entity.first) {
569         Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate metadata for provider (%s)", entityID);
570     }
571     else if (!entity.second) {
572         MetadataProviderCriteria aamc(
573             app, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS
574             );
575         if (protocol)
576             aamc.protocol2 = samlconstants::SAML20P_NS;
577         entity = m->getEntityDescriptor(aamc);
578         if (!entity.second) {
579             Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate compatible IdP or AA role for provider (%s)", entityID);
580         }
581     }
582
583     return entity.second;
584 }
585
586 #endif
587
588 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
589 {
590     if (features & SPConfig::OutOfProcess) {
591 #ifndef SHIBSP_LITE
592         features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
593 #endif
594         if (!(features & SPConfig::InProcess))
595             features |= SPConfig::Listener;
596     }
597     else if (features & SPConfig::InProcess) {
598         features |= SPConfig::Listener;
599     }
600     SPConfig::getConfig().setFeatures(features);
601     if (!SPConfig::getConfig().init())
602         return false;
603     if (!SPConfig::getConfig().instantiate(config, rethrow))
604         return false;
605     return true;
606 }
607
608 /**
609     * Shuts down runtime.
610     *
611     * Each process using the library SHOULD call this function exactly once before terminating itself.
612     */
613 void ShibbolethResolver::term()
614 {
615     SPConfig::getConfig().term();
616 }
617
618
619 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
620 {
621 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
622     SPConfig& conf = SPConfig::getConfig();
623     if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
624         conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
625 #endif
626     return 0;   // signal success
627 }
628
629 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
630 {
631     // Factories normally get unregistered during library shutdown, so no work usually required here.
632 }