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