2 * Copyright 2010-2011 JANET(UK)
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * An embeddable component interface to Shibboleth SP attribute processing.
25 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
26 # include <gssapi/gssapi_ext.h>
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>
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>
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>
54 using namespace shibresolver;
55 using namespace shibsp;
57 using namespace opensaml;
58 using namespace opensaml::saml2md;
60 using namespace xmltooling;
63 namespace shibresolver {
64 class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
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>());
76 vector<const XMLObject*> tokens;
77 vector<Attribute*> inputAttrs;
78 vector<Attribute*> resolvedAttrs;
81 void receive(DDF& in, ostream& out);
83 const Application& app,
85 const vector<const XMLObject*>& tokens,
86 const vector<Attribute*>& inputAttrs,
87 vector <Attribute*>& resolvedAttrs
91 static RemotedResolver g_Remoted;
94 ShibbolethResolver* ShibbolethResolver::create()
96 return new ShibbolethResolver();
99 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
100 #ifdef SHIBRESOLVER_HAVE_GSSAPI
106 ShibbolethResolver::~ShibbolethResolver()
108 #ifdef SHIBRESOLVER_HAVE_GSSAPI
111 for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
116 void ShibbolethResolver::setRequest(const SPRequest* request)
119 #if defined(SHIBSP_HAVE_GSSAPI) && defined (SHIBRESOLVER_HAVE_GSSAPI)
121 const GSSRequest* gss = dynamic_cast<const GSSRequest*>(request);
123 // TODO: fix API to prevent destruction of contexts
124 gss_ctx_id_t ctx = gss->getGSSContext();
131 void ShibbolethResolver::setApplicationID(const char* appID)
138 void ShibbolethResolver::setIssuer(const char* issuer)
145 void ShibbolethResolver::addToken(const XMLObject* token)
148 m_tokens.push_back(token);
151 #ifdef SHIBRESOLVER_HAVE_GSSAPI
152 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
159 if (ctx && *ctx != GSS_C_NO_CONTEXT) {
160 OM_uint32 major, minor;
161 gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
163 major = gss_export_sec_context(&minor, ctx, &contextbuf);
164 if (major == GSS_S_COMPLETE) {
165 addToken(&contextbuf);
166 gss_release_buffer(&minor, &contextbuf);
169 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
174 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
182 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
185 s.append(reinterpret_cast<char*>(out), len);
186 auto_ptr_XMLCh temp(s.c_str());
187 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
188 XMLString::release(&out);
190 XMLString::release((char**)&out);
192 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
193 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
194 m_gsswrapper->setTextContent(temp.get());
197 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
201 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
202 void ShibbolethResolver::addToken(gss_name_t name)
209 OM_uint32 major, minor;
210 gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
212 major = gss_export_name_composite(&minor, name, &namebuf);
213 if (major == GSS_S_COMPLETE) {
215 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(namebuf.value), namebuf.length, &len);
218 s.append(reinterpret_cast<char*>(out), len);
219 auto_ptr_XMLCh temp(s.c_str());
220 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
221 XMLString::release(&out);
223 XMLString::release((char**)&out);
225 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
226 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
227 m_gsswrapper->setTextContent(temp.get());
230 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
232 gss_release_buffer(&minor, &namebuf);
235 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
241 void ShibbolethResolver::addAttribute(Attribute* attr)
244 m_inputAttributes.push_back(attr);
247 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
249 return m_resolvedAttributes;
252 RequestMapper::Settings ShibbolethResolver::getSettings() const
255 throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
256 return m_request->getRequestSettings();
259 void ShibbolethResolver::resolve()
261 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
262 SPConfig& conf = SPConfig::getConfig();
264 m_sp = conf.getServiceProvider();
270 const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
272 throw ConfigurationException("Unable to locate application for resolution.");
274 #ifdef SHIBRESOLVER_HAVE_GSSAPI
276 m_tokens.push_back(m_gsswrapper);
279 if (conf.isEnabled(SPConfig::OutOfProcess)) {
289 // When not out of process, we remote all the message processing.
290 DDF out,in = DDF("org.project-moonshot.shibresolver");
291 DDFJanitor jin(in), jout(out);
292 in.addmember("application_id").string(app->getId());
293 if (!m_issuer.empty())
294 in.addmember("issuer").string(m_issuer.c_str());
295 if (!m_tokens.empty()) {
296 DDF& tokens = in.addmember("tokens").list();
297 for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
300 tokens.add(DDF(NULL).string(os.str().c_str()));
303 if (!m_inputAttributes.empty()) {
305 DDF& attrs = in.addmember("attributes").list();
306 for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
307 attr = (*a)->marshall();
312 out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
314 Attribute* attribute;
315 DDF attr = out.first();
316 while (!attr.isnull()) {
318 attribute = Attribute::unmarshall(attr);
319 m_resolvedAttributes.push_back(attribute);
320 if (log.isDebugEnabled())
321 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
322 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
324 catch (AttributeException& ex) {
325 const char* id = attr.first().name();
326 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
333 void RemotedResolver::receive(DDF& in, ostream& out)
335 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
338 const char* aid = in["application_id"].string();
339 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
341 // Something's horribly wrong.
342 log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
343 throw ConfigurationException("Unable to locate application for resolution, deleted?");
347 DDFJanitor jout(ret);
351 DDF tlist = in["tokens"];
352 DDF token = tlist.first();
353 while (token.isstring()) {
354 // Parse and bind the document into an XMLObject.
355 istringstream instr(token.string());
356 DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
357 XercesJanitor<DOMDocument> janitor(doc);
358 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
359 t.tokens.push_back(xmlObject);
361 token = tlist.next();
364 DDF alist = in["attributes"];
365 Attribute* attribute;
366 DDF attr = alist.first();
367 while (!attr.isnull()) {
368 attribute = Attribute::unmarshall(attr);
369 t.inputAttrs.push_back(attribute);
370 if (log.isDebugEnabled())
371 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
372 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
376 resolve(*app, in["issuer"].string(), t.tokens, t.inputAttrs, t.resolvedAttrs);
378 if (!t.resolvedAttrs.empty()) {
380 for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
381 attr = (*a)->marshall();
389 void RemotedResolver::resolve(
390 const Application& app,
392 const vector<const XMLObject*>& tokens,
393 const vector<Attribute*>& inputAttrs,
394 vector <Attribute*>& resolvedAttrs
398 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
399 string issuerstr(issuer ? issuer : "");
400 pair<const EntityDescriptor*,const RoleDescriptor*> entity = make_pair((EntityDescriptor*)NULL, (RoleDescriptor*)NULL);
401 MetadataProvider* m = app.getMetadataProvider(false);
404 log.warn("no metadata providers are configured");
407 if (!issuerstr.empty()) {
408 // Attempt to locate an issuer based on input token.
409 for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
410 const saml2::Assertion* assertion = dynamic_cast<const saml2::Assertion*>(*t);
411 if (assertion && assertion->getIssuer()) {
412 auto_ptr_char iss(assertion->getIssuer()->getName());
413 if (iss.get() && *iss.get()) {
414 issuerstr = iss.get();
419 if (!issuerstr.empty()) {
420 log.info("setting issuer based on input token (%s)", issuerstr.c_str());
424 if (!issuerstr.empty()) {
425 // Lookup metadata for the issuer.
426 MetadataProviderCriteria idpmc(app, issuerstr.c_str(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
427 entity = m->getEntityDescriptor(idpmc);
429 log.warn("unable to locate metadata for provider (%s)", issuerstr.c_str());
431 else if (!entity.second) {
432 MetadataProviderCriteria aamc(app, issuerstr.c_str(), &AttributeAuthorityDescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
433 entity = m->getEntityDescriptor(aamc);
434 if (!entity.second) {
435 log.warn("unable to locate SAML 2.0 IdP or AA role for provider (%s)", issuerstr.c_str());
441 vector<const Assertion*> assertions;
443 AttributeExtractor* extractor = app.getAttributeExtractor();
445 Locker extlocker(extractor);
447 pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
449 log.debug("extracting metadata-derived attributes...");
451 // We pass NULL for "issuer" because the IdP isn't the one asserting metadata-based attributes.
452 extractor->extractAttributes(app, NULL, *entity.second, 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;
459 catch (exception& ex) {
460 log.error("caught exception extracting attributes: %s", ex.what());
464 log.debug("extracting pushed attributes...");
465 for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
467 // Save off any assertions for later use by resolver.
468 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
470 assertions.push_back(assertion);
471 extractor->extractAttributes(app, entity.second, *(*t), resolvedAttrs);
473 catch (exception& ex) {
474 log.error("caught exception extracting attributes: %s", ex.what());
478 AttributeFilter* filter = app.getAttributeFilter();
479 if (filter && !resolvedAttrs.empty()) {
480 BasicFilteringContext fc(app, resolvedAttrs, entity.second);
481 Locker filtlocker(filter);
483 filter->filterAttributes(fc, resolvedAttrs);
485 catch (exception& ex) {
486 log.error("caught exception filtering attributes: %s", ex.what());
487 log.error("dumping extracted attributes due to filtering exception");
488 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
489 resolvedAttrs.clear();
494 log.warn("no AttributeExtractor plugin installed, check log during startup");
498 AttributeResolver* resolver = app.getAttributeResolver();
500 log.debug("resolving attributes...");
502 vector<Attribute*> inputs = inputAttrs;
503 inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
505 Locker locker(resolver);
506 auto_ptr<ResolutionContext> ctx(
507 resolver->createResolutionContext(
510 samlconstants::SAML20P_NS,
518 resolver->resolveAttributes(*ctx.get());
519 if (!ctx->getResolvedAttributes().empty())
520 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
523 catch (exception& ex) {
524 log.error("attribute resolution failed: %s", ex.what());
527 throw ConfigurationException("Cannot process request using lite version of shibsp library.");
531 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
533 if (features && SPConfig::OutOfProcess) {
535 features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
537 if (!(features && SPConfig::InProcess))
538 features |= SPConfig::Listener;
540 else if (features && SPConfig::InProcess) {
541 features |= SPConfig::Listener;
543 SPConfig::getConfig().setFeatures(features);
544 if (!SPConfig::getConfig().init())
546 if (!SPConfig::getConfig().instantiate(config, rethrow))
552 * Shuts down runtime.
554 * Each process using the library SHOULD call this function exactly once before terminating itself.
556 void ShibbolethResolver::term()
558 SPConfig::getConfig().term();
562 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
564 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
565 SPConfig& conf = SPConfig::getConfig();
566 if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
567 conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
569 return 0; // signal success
572 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
574 // Factories normally get unregistered during library shutdown, so no work usually required here.