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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * An embeddable component interface to Shibboleth SP attribute processing.
24 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
25 # ifdef SHIBRESOLVER_HAVE_GSSMIT
26 # include <gssapi/gssapi_ext.h>
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>
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>
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/Threads.h>
53 #include <xmltooling/util/XMLHelper.h>
54 #include <xercesc/util/Base64.hpp>
56 using namespace shibresolver;
57 using namespace shibsp;
59 using namespace opensaml;
60 using namespace opensaml::saml2md;
62 using namespace xmltooling;
65 namespace shibresolver {
66 class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
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>());
78 vector<const XMLObject*> tokens;
79 vector<Attribute*> inputAttrs;
80 vector<Attribute*> resolvedAttrs;
83 void receive(DDF& in, ostream& out);
85 const Application& app,
87 const XMLCh* protocol,
88 const vector<const XMLObject*>& tokens,
89 const vector<Attribute*>& inputAttrs,
90 vector<Attribute*>& resolvedAttrs
96 AttributeExtractor* extractor,
97 const Application& app,
98 const RoleDescriptor* issuer,
99 const XMLObject& token,
100 vector<Attribute*>& resolvedAttrs
103 const RoleDescriptor* lookup(
104 const Application& app,
106 const char* entityID,
107 const XMLCh* protocol
112 static RemotedResolver g_Remoted;
114 static int g_initCount = 0;
115 static auto_ptr<Mutex> g_lock(Mutex::create());
118 ShibbolethResolver* ShibbolethResolver::create()
120 return new ShibbolethResolver();
123 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
124 #ifdef SHIBRESOLVER_HAVE_GSSAPI
130 ShibbolethResolver::~ShibbolethResolver()
132 #ifdef SHIBRESOLVER_HAVE_GSSAPI
135 for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
140 void ShibbolethResolver::setRequest(const SPRequest* request)
143 #if defined(SHIBSP_HAVE_GSSAPI) && defined (SHIBRESOLVER_HAVE_GSSAPI)
145 const GSSRequest* gss = dynamic_cast<const GSSRequest*>(request);
147 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
148 gss_name_t name = gss->getGSSName();
149 if (name != GSS_C_NO_NAME) {
154 gss_ctx_id_t ctx = gss->getGSSContext();
155 if (ctx != GSS_C_NO_CONTEXT)
162 void ShibbolethResolver::setApplicationID(const char* appID)
169 void ShibbolethResolver::setIssuer(const char* issuer)
176 void ShibbolethResolver::setProtocol(const XMLCh* protocol)
180 m_protocol = protocol;
183 void ShibbolethResolver::addToken(const XMLObject* token)
186 m_tokens.push_back(token);
189 #ifdef SHIBRESOLVER_HAVE_GSSAPI
190 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
197 if (ctx && *ctx != GSS_C_NO_CONTEXT) {
199 gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
200 OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
201 if (major == GSS_S_COMPLETE) {
203 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
206 s.append(reinterpret_cast<char*>(out), len);
207 auto_ptr_XMLCh temp(s.c_str());
208 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
209 XMLString::release(&out);
211 XMLString::release((char**)&out);
213 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
214 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
215 m_gsswrapper->setTextContent(temp.get());
218 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
220 gss_release_buffer(&minor, &contextbuf);
223 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
228 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
229 void ShibbolethResolver::addToken(gss_name_t name)
237 gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
238 OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
239 if (major == GSS_S_COMPLETE) {
241 gss_release_buffer(&minor, &namebuf);
244 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
249 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
257 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
260 s.append(reinterpret_cast<char*>(out), len);
261 auto_ptr_XMLCh temp(s.c_str());
262 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
263 XMLString::release(&out);
265 XMLString::release((char**)&out);
267 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
268 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
269 m_gsswrapper->setTextContent(temp.get());
272 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
278 void ShibbolethResolver::addAttribute(Attribute* attr)
281 m_inputAttributes.push_back(attr);
284 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
286 return m_resolvedAttributes;
289 RequestMapper::Settings ShibbolethResolver::getSettings() const
292 throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
293 return m_request->getRequestSettings();
296 void ShibbolethResolver::resolve()
298 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
299 SPConfig& conf = SPConfig::getConfig();
301 m_sp = conf.getServiceProvider();
307 const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
309 throw ConfigurationException("Unable to locate application for resolution.");
311 #ifdef SHIBRESOLVER_HAVE_GSSAPI
313 m_tokens.push_back(m_gsswrapper);
316 if (conf.isEnabled(SPConfig::OutOfProcess)) {
327 // When not out of process, we remote all the message processing.
328 DDF out,in = DDF("org.project-moonshot.shibresolver");
329 DDFJanitor jin(in), jout(out);
330 in.addmember("application_id").string(app->getId());
331 if (!m_issuer.empty())
332 in.addmember("issuer").string(m_issuer.c_str());
333 if (!m_protocol.empty()) {
334 auto_ptr_char prot(m_protocol.c_str());
335 in.addmember("protocol").string(prot.get());
337 if (!m_tokens.empty()) {
338 DDF& tokens = in.addmember("tokens").list();
339 for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
342 tokens.add(DDF(NULL).string(os.str().c_str()));
345 if (!m_inputAttributes.empty()) {
347 DDF& attrs = in.addmember("attributes").list();
348 for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
349 attr = (*a)->marshall();
354 out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
356 Attribute* attribute;
357 DDF attr = out.first();
358 while (!attr.isnull()) {
360 attribute = Attribute::unmarshall(attr);
361 m_resolvedAttributes.push_back(attribute);
362 if (log.isDebugEnabled())
363 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
364 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
366 catch (AttributeException& ex) {
367 const char* id = attr.first().name();
368 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
375 void RemotedResolver::receive(DDF& in, ostream& out)
377 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
380 const char* aid = in["application_id"].string();
381 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
383 // Something's horribly wrong.
384 log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
385 throw ConfigurationException("Unable to locate application for resolution, deleted?");
389 DDFJanitor jout(ret);
393 DDF tlist = in["tokens"];
394 DDF token = tlist.first();
395 while (token.isstring()) {
396 // Parse and bind the document into an XMLObject.
397 istringstream instr(token.string());
398 DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
399 XercesJanitor<DOMDocument> janitor(doc);
400 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
401 t.tokens.push_back(xmlObject);
403 token = tlist.next();
406 DDF alist = in["attributes"];
407 Attribute* attribute;
408 DDF attr = alist.first();
409 while (!attr.isnull()) {
410 attribute = Attribute::unmarshall(attr);
411 t.inputAttrs.push_back(attribute);
412 if (log.isDebugEnabled())
413 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
414 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
418 auto_ptr_XMLCh prot(in["protocol"].string());
420 resolve(*app, in["issuer"].string(), prot.get(), t.tokens, t.inputAttrs, t.resolvedAttrs);
422 if (!t.resolvedAttrs.empty()) {
424 for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
425 attr = (*a)->marshall();
433 void RemotedResolver::resolve(
434 const Application& app,
436 const XMLCh* protocol,
437 const vector<const XMLObject*>& tokens,
438 const vector<Attribute*>& inputAttrs,
439 vector<Attribute*>& resolvedAttrs
443 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
444 MetadataProvider* m = app.getMetadataProvider(false);
447 const RoleDescriptor* role = NULL;
448 if (issuer && *issuer)
449 role = lookup(app, m, issuer, protocol);
451 vector<const Assertion*> assertions;
453 AttributeExtractor* extractor = app.getAttributeExtractor();
455 Locker extlocker(extractor);
456 // Support metadata-based attributes for only the "top-level" issuer.
458 pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
460 log.debug("extracting metadata-derived attributes...");
462 // We pass NULL for "issuer" because the issuer isn't the one asserting metadata-based attributes.
463 extractor->extractAttributes(app, NULL, *role, resolvedAttrs);
464 for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
465 vector<string>& ids = (*a)->getAliases();
466 for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
467 *id = mprefix.second + *id;
470 catch (exception& ex) {
471 log.error("caught exception extracting attributes: %s", ex.what());
476 log.debug("extracting pushed attributes...");
477 const RoleDescriptor* role2;
478 for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {
479 // Save off any assertions for later use by resolver.
481 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
483 assertions.push_back(assertion);
484 const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(assertion);
485 if (saml2token && saml2token->getIssuer() && (saml2token->getIssuer()->getFormat() == NULL ||
486 XMLString::equals(saml2token->getIssuer()->getFormat(), saml2::NameID::ENTITY))) {
487 auto_ptr_char tokenissuer(saml2token->getIssuer()->getName());
488 role2 = lookup(app, m, tokenissuer.get(), protocol);
491 resolve(extractor, app, (role2 ? role2 : role), *(*t), resolvedAttrs);
495 log.warn("no AttributeExtractor plugin installed, check log during startup");
499 AttributeResolver* resolver = app.getAttributeResolver();
501 log.debug("resolving additional attributes...");
503 vector<Attribute*> inputs = inputAttrs;
504 inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
506 Locker locker(resolver);
507 auto_ptr<ResolutionContext> ctx(
508 resolver->createResolutionContext(
510 role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL,
511 protocol ? protocol : samlconstants::SAML20P_NS,
519 resolver->resolveAttributes(*ctx.get());
520 if (!ctx->getResolvedAttributes().empty())
521 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
524 catch (exception& ex) {
525 log.error("attribute resolution failed: %s", ex.what());
528 throw ConfigurationException("Cannot process request using lite version of shibsp library.");
534 void RemotedResolver::resolve(
535 AttributeExtractor* extractor,
536 const Application& app,
537 const RoleDescriptor* issuer,
538 const XMLObject& token,
539 vector<Attribute*>& resolvedAttrs
542 vector<Attribute*> extractedAttrs;
544 extractor->extractAttributes(app, issuer, token, extractedAttrs);
546 catch (exception& ex) {
547 Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception extracting attributes: %s", ex.what());
550 AttributeFilter* filter = app.getAttributeFilter();
551 if (filter && !extractedAttrs.empty()) {
552 BasicFilteringContext fc(app, extractedAttrs, issuer);
553 Locker filtlocker(filter);
555 filter->filterAttributes(fc, extractedAttrs);
557 catch (exception& ex) {
558 Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception filtering attributes: %s", ex.what());
559 Category::getInstance(SHIBRESOLVER_LOGCAT).error("dumping extracted attributes due to filtering exception");
560 for_each(extractedAttrs.begin(), extractedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
561 extractedAttrs.clear();
565 resolvedAttrs.insert(resolvedAttrs.end(), extractedAttrs.begin(), extractedAttrs.end());
568 const RoleDescriptor* RemotedResolver::lookup(
569 const Application& app, MetadataProvider* m, const char* entityID, const XMLCh* protocol
575 MetadataProviderCriteria idpmc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS);
577 idpmc.protocol2 = samlconstants::SAML20P_NS;
578 pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(idpmc);
580 Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate metadata for provider (%s)", entityID);
582 else if (!entity.second) {
583 MetadataProviderCriteria aamc(
584 app, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS
587 aamc.protocol2 = samlconstants::SAML20P_NS;
588 entity = m->getEntityDescriptor(aamc);
589 if (!entity.second) {
590 Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate compatible IdP or AA role for provider (%s)", entityID);
594 return entity.second;
599 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
601 Lock initLock(g_lock.get());
603 if (g_initCount == INT_MAX) {
604 Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("library initialized too many times");
608 if (g_initCount >= 1) {
613 if (features & SPConfig::OutOfProcess) {
615 features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
617 if (!(features & SPConfig::InProcess))
618 features |= SPConfig::Listener;
620 else if (features & SPConfig::InProcess) {
621 features |= SPConfig::Listener;
623 SPConfig::getConfig().setFeatures(features);
624 if (!SPConfig::getConfig().init())
626 if (!SPConfig::getConfig().instantiate(config, rethrow))
633 void ShibbolethResolver::term()
635 Lock initLock(g_lock.get());
636 if (g_initCount == 0) {
637 Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("term without corresponding init");
640 else if (--g_initCount > 0) {
644 SPConfig::getConfig().term();
648 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
650 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
651 SPConfig& conf = SPConfig::getConfig();
652 if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
653 conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
655 return 0; // signal success
658 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
660 // Factories normally get unregistered during library shutdown, so no work usually required here.