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 // TODO: fix API to prevent destruction of contexts
148 gss_ctx_id_t ctx = gss->getGSSContext();
155 void ShibbolethResolver::setApplicationID(const char* appID)
162 void ShibbolethResolver::setIssuer(const char* issuer)
169 void ShibbolethResolver::setProtocol(const XMLCh* protocol)
173 m_protocol = protocol;
176 void ShibbolethResolver::addToken(const XMLObject* token)
179 m_tokens.push_back(token);
182 #ifdef SHIBRESOLVER_HAVE_GSSAPI
183 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
190 if (ctx && *ctx != GSS_C_NO_CONTEXT) {
192 gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
193 OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
194 if (major == GSS_S_COMPLETE) {
196 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
199 s.append(reinterpret_cast<char*>(out), len);
200 auto_ptr_XMLCh temp(s.c_str());
201 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
202 XMLString::release(&out);
204 XMLString::release((char**)&out);
206 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
207 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
208 m_gsswrapper->setTextContent(temp.get());
211 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
213 gss_release_buffer(&minor, &contextbuf);
216 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
221 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
222 void ShibbolethResolver::addToken(gss_name_t name)
230 gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
231 OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
232 if (major == GSS_S_COMPLETE) {
234 gss_release_buffer(&minor, &namebuf);
237 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
242 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
250 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
253 s.append(reinterpret_cast<char*>(out), len);
254 auto_ptr_XMLCh temp(s.c_str());
255 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
256 XMLString::release(&out);
258 XMLString::release((char**)&out);
260 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
261 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
262 m_gsswrapper->setTextContent(temp.get());
265 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
271 void ShibbolethResolver::addAttribute(Attribute* attr)
274 m_inputAttributes.push_back(attr);
277 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
279 return m_resolvedAttributes;
282 RequestMapper::Settings ShibbolethResolver::getSettings() const
285 throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
286 return m_request->getRequestSettings();
289 void ShibbolethResolver::resolve()
291 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
292 SPConfig& conf = SPConfig::getConfig();
294 m_sp = conf.getServiceProvider();
300 const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
302 throw ConfigurationException("Unable to locate application for resolution.");
304 #ifdef SHIBRESOLVER_HAVE_GSSAPI
306 m_tokens.push_back(m_gsswrapper);
309 if (conf.isEnabled(SPConfig::OutOfProcess)) {
320 // When not out of process, we remote all the message processing.
321 DDF out,in = DDF("org.project-moonshot.shibresolver");
322 DDFJanitor jin(in), jout(out);
323 in.addmember("application_id").string(app->getId());
324 if (!m_issuer.empty())
325 in.addmember("issuer").string(m_issuer.c_str());
326 if (!m_protocol.empty()) {
327 auto_ptr_char prot(m_protocol.c_str());
328 in.addmember("protocol").string(prot.get());
330 if (!m_tokens.empty()) {
331 DDF& tokens = in.addmember("tokens").list();
332 for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
335 tokens.add(DDF(NULL).string(os.str().c_str()));
338 if (!m_inputAttributes.empty()) {
340 DDF& attrs = in.addmember("attributes").list();
341 for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
342 attr = (*a)->marshall();
347 out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
349 Attribute* attribute;
350 DDF attr = out.first();
351 while (!attr.isnull()) {
353 attribute = Attribute::unmarshall(attr);
354 m_resolvedAttributes.push_back(attribute);
355 if (log.isDebugEnabled())
356 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
357 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
359 catch (AttributeException& ex) {
360 const char* id = attr.first().name();
361 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
368 void RemotedResolver::receive(DDF& in, ostream& out)
370 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
373 const char* aid = in["application_id"].string();
374 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
376 // Something's horribly wrong.
377 log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
378 throw ConfigurationException("Unable to locate application for resolution, deleted?");
382 DDFJanitor jout(ret);
386 DDF tlist = in["tokens"];
387 DDF token = tlist.first();
388 while (token.isstring()) {
389 // Parse and bind the document into an XMLObject.
390 istringstream instr(token.string());
391 DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
392 XercesJanitor<DOMDocument> janitor(doc);
393 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
394 t.tokens.push_back(xmlObject);
396 token = tlist.next();
399 DDF alist = in["attributes"];
400 Attribute* attribute;
401 DDF attr = alist.first();
402 while (!attr.isnull()) {
403 attribute = Attribute::unmarshall(attr);
404 t.inputAttrs.push_back(attribute);
405 if (log.isDebugEnabled())
406 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
407 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
411 auto_ptr_XMLCh prot(in["protocol"].string());
413 resolve(*app, in["issuer"].string(), prot.get(), t.tokens, t.inputAttrs, t.resolvedAttrs);
415 if (!t.resolvedAttrs.empty()) {
417 for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
418 attr = (*a)->marshall();
426 void RemotedResolver::resolve(
427 const Application& app,
429 const XMLCh* protocol,
430 const vector<const XMLObject*>& tokens,
431 const vector<Attribute*>& inputAttrs,
432 vector<Attribute*>& resolvedAttrs
436 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
437 MetadataProvider* m = app.getMetadataProvider(false);
440 const RoleDescriptor* role = NULL;
441 if (issuer && *issuer)
442 role = lookup(app, m, issuer, protocol);
444 vector<const Assertion*> assertions;
446 AttributeExtractor* extractor = app.getAttributeExtractor();
448 Locker extlocker(extractor);
449 // Support metadata-based attributes for only the "top-level" issuer.
451 pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
453 log.debug("extracting metadata-derived attributes...");
455 // We pass NULL for "issuer" because the issuer isn't the one asserting metadata-based attributes.
456 extractor->extractAttributes(app, NULL, *role, resolvedAttrs);
457 for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
458 vector<string>& ids = (*a)->getAliases();
459 for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
460 *id = mprefix.second + *id;
463 catch (exception& ex) {
464 log.error("caught exception extracting attributes: %s", ex.what());
469 log.debug("extracting pushed attributes...");
470 const RoleDescriptor* role2;
471 for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {
472 // Save off any assertions for later use by resolver.
474 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
476 assertions.push_back(assertion);
477 const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(assertion);
478 if (saml2token && saml2token->getIssuer() && (saml2token->getIssuer()->getFormat() == NULL ||
479 XMLString::equals(saml2token->getIssuer()->getFormat(), saml2::NameID::ENTITY))) {
480 auto_ptr_char tokenissuer(saml2token->getIssuer()->getName());
481 role2 = lookup(app, m, tokenissuer.get(), protocol);
484 resolve(extractor, app, (role2 ? role2 : role), *(*t), resolvedAttrs);
488 log.warn("no AttributeExtractor plugin installed, check log during startup");
492 AttributeResolver* resolver = app.getAttributeResolver();
494 log.debug("resolving additional attributes...");
496 vector<Attribute*> inputs = inputAttrs;
497 inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
499 Locker locker(resolver);
500 auto_ptr<ResolutionContext> ctx(
501 resolver->createResolutionContext(
503 role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL,
504 protocol ? protocol : samlconstants::SAML20P_NS,
512 resolver->resolveAttributes(*ctx.get());
513 if (!ctx->getResolvedAttributes().empty())
514 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
517 catch (exception& ex) {
518 log.error("attribute resolution failed: %s", ex.what());
521 throw ConfigurationException("Cannot process request using lite version of shibsp library.");
527 void RemotedResolver::resolve(
528 AttributeExtractor* extractor,
529 const Application& app,
530 const RoleDescriptor* issuer,
531 const XMLObject& token,
532 vector<Attribute*>& resolvedAttrs
535 vector<Attribute*> extractedAttrs;
537 extractor->extractAttributes(app, issuer, token, extractedAttrs);
539 catch (exception& ex) {
540 Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception extracting attributes: %s", ex.what());
543 AttributeFilter* filter = app.getAttributeFilter();
544 if (filter && !extractedAttrs.empty()) {
545 BasicFilteringContext fc(app, extractedAttrs, issuer);
546 Locker filtlocker(filter);
548 filter->filterAttributes(fc, extractedAttrs);
550 catch (exception& ex) {
551 Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception filtering attributes: %s", ex.what());
552 Category::getInstance(SHIBRESOLVER_LOGCAT).error("dumping extracted attributes due to filtering exception");
553 for_each(extractedAttrs.begin(), extractedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
554 extractedAttrs.clear();
558 resolvedAttrs.insert(resolvedAttrs.end(), extractedAttrs.begin(), extractedAttrs.end());
561 const RoleDescriptor* RemotedResolver::lookup(
562 const Application& app, MetadataProvider* m, const char* entityID, const XMLCh* protocol
568 MetadataProviderCriteria idpmc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS);
570 idpmc.protocol2 = samlconstants::SAML20P_NS;
571 pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(idpmc);
573 Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate metadata for provider (%s)", entityID);
575 else if (!entity.second) {
576 MetadataProviderCriteria aamc(
577 app, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS
580 aamc.protocol2 = samlconstants::SAML20P_NS;
581 entity = m->getEntityDescriptor(aamc);
582 if (!entity.second) {
583 Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate compatible IdP or AA role for provider (%s)", entityID);
587 return entity.second;
592 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
594 Lock initLock(g_lock.get());
596 if (g_initCount == INT_MAX) {
597 Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("library initialized too many times");
601 if (g_initCount >= 1) {
606 if (features & SPConfig::OutOfProcess) {
608 features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
610 if (!(features & SPConfig::InProcess))
611 features |= SPConfig::Listener;
613 else if (features & SPConfig::InProcess) {
614 features |= SPConfig::Listener;
616 SPConfig::getConfig().setFeatures(features);
617 if (!SPConfig::getConfig().init())
619 if (!SPConfig::getConfig().instantiate(config, rethrow))
626 void ShibbolethResolver::term()
628 Lock initLock(g_lock.get());
629 if (g_initCount == 0) {
630 Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("term without corresponding init");
633 else if (--g_initCount > 0) {
637 SPConfig::getConfig().term();
641 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
643 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
644 SPConfig& conf = SPConfig::getConfig();
645 if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
646 conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
648 return 0; // signal success
651 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
653 // Factories normally get unregistered during library shutdown, so no work usually required here.