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