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