2 * Copyright 2010 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 #include <shibsp/exceptions.h>
26 #include <shibsp/Application.h>
27 #include <shibsp/SPRequest.h>
28 #include <shibsp/ServiceProvider.h>
29 #include <shibsp/attribute/Attribute.h>
30 #include <shibsp/remoting/ListenerService.h>
32 # include <saml/saml2/metadata/Metadata.h>
33 # include <saml/saml2/metadata/MetadataProvider.h>
34 # include <saml/util/SAMLConstants.h>
35 # include <shibsp/attribute/filtering/AttributeFilter.h>
36 # include <shibsp/attribute/filtering/BasicFilteringContext.h>
37 # include <shibsp/attribute/resolver/AttributeExtractor.h>
38 # include <shibsp/attribute/resolver/AttributeResolver.h>
39 # include <shibsp/attribute/resolver/ResolutionContext.h>
40 # include <shibsp/metadata/MetadataProviderCriteria.h>
42 #include <xmltooling/XMLObjectBuilder.h>
43 #include <xmltooling/XMLToolingConfig.h>
44 #include <xmltooling/util/ParserPool.h>
45 #include <xmltooling/util/XMLHelper.h>
47 using namespace shibresolver;
48 using namespace shibsp;
50 using namespace opensaml;
51 using namespace opensaml::saml2md;
53 using namespace xmltooling;
56 namespace shibresolver {
57 class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
64 for_each(tokens.begin(), tokens.end(), xmltooling::cleanup<XMLObject>());
65 for_each(inputAttrs.begin(), inputAttrs.end(), xmltooling::cleanup<Attribute>());
66 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<Attribute>());
69 vector<const XMLObject*> tokens;
70 vector<Attribute*> inputAttrs;
71 vector<Attribute*> resolvedAttrs;
74 void receive(DDF& in, ostream& out);
76 const Application& app,
78 const vector<const XMLObject*>& tokens,
79 const vector<Attribute*>& inputAttrs,
80 vector <Attribute*>& resolvedAttrs
84 static RemotedResolver g_Remoted;
87 ShibbolethResolver* ShibbolethResolver::create()
89 return new ShibbolethResolver();
92 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
96 ShibbolethResolver::~ShibbolethResolver()
98 for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
103 void ShibbolethResolver::setRequest(const SPRequest* request)
108 void ShibbolethResolver::setApplicationID(const char* appID)
115 void ShibbolethResolver::setIssuer(const char* issuer)
122 void ShibbolethResolver::addToken(const XMLObject* token)
125 m_tokens.push_back(token);
128 void ShibbolethResolver::addAttribute(Attribute* attr)
131 m_inputAttributes.push_back(attr);
134 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
136 return m_resolvedAttributes;
139 RequestMapper::Settings ShibbolethResolver::getSettings() const
142 throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
143 return m_request->getRequestSettings();
146 void ShibbolethResolver::resolve()
148 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
149 SPConfig& conf = SPConfig::getConfig();
151 m_sp = conf.getServiceProvider();
157 const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
159 throw ConfigurationException("Unable to locate application for resolution.");
161 if (conf.isEnabled(SPConfig::OutOfProcess)) {
171 // When not out of process, we remote all the message processing.
172 DDF out,in = DDF("org.project-moonshot.shibresolver");
173 DDFJanitor jin(in), jout(out);
174 in.addmember("application_id").string(app->getId());
175 if (!m_issuer.empty())
176 in.addmember("issuer").string(m_issuer.c_str());
177 if (!m_tokens.empty()) {
178 DDF& tokens = in.addmember("tokens").list();
179 for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
182 tokens.add(DDF(NULL).string(os.str().c_str()));
185 if (!m_inputAttributes.empty()) {
187 DDF& attrs = in.addmember("attributes").list();
188 for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
189 attr = (*a)->marshall();
194 out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
196 Attribute* attribute;
197 DDF attr = out.first();
198 while (!attr.isnull()) {
200 attribute = Attribute::unmarshall(attr);
201 m_resolvedAttributes.push_back(attribute);
202 if (log.isDebugEnabled())
203 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
204 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
206 catch (AttributeException& ex) {
207 const char* id = attr.first().name();
208 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
215 void RemotedResolver::receive(DDF& in, ostream& out)
217 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
220 const char* aid = in["application_id"].string();
221 const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
223 // Something's horribly wrong.
224 log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
225 throw ConfigurationException("Unable to locate application for resolution, deleted?");
229 DDFJanitor jout(ret);
233 DDF tlist = in["tokens"];
234 DDF token = tlist.first();
235 while (token.isstring()) {
236 // Parse and bind the document into an XMLObject.
237 istringstream instr(token.string());
238 DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
239 XercesJanitor<DOMDocument> janitor(doc);
240 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
241 t.tokens.push_back(xmlObject);
243 token = tlist.next();
246 DDF alist = in["attributes"];
247 Attribute* attribute;
248 DDF attr = alist.first();
249 while (!attr.isnull()) {
250 attribute = Attribute::unmarshall(attr);
251 t.inputAttrs.push_back(attribute);
252 if (log.isDebugEnabled())
253 log.debug("unmarshalled attribute (ID: %s) with %d value%s",
254 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
258 resolve(*app, in["issuer"].string(), t.tokens, t.inputAttrs, t.resolvedAttrs);
260 if (!t.resolvedAttrs.empty()) {
262 for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
263 attr = (*a)->marshall();
271 void RemotedResolver::resolve(
272 const Application& app,
274 const vector<const XMLObject*>& tokens,
275 const vector<Attribute*>& inputAttrs,
276 vector <Attribute*>& resolvedAttrs
280 Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
281 pair<const EntityDescriptor*,const RoleDescriptor*> entity = pair<const EntityDescriptor*,const RoleDescriptor*>(nullptr,nullptr);
282 MetadataProvider* m = app.getMetadataProvider();
284 if (issuer && *issuer) {
285 // Use metadata to locate the IdP's SSO service.
286 MetadataProviderCriteria mc(app, issuer, &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
287 entity = m->getEntityDescriptor(mc);
289 log.warn("unable to locate metadata for provider (%s)", issuer);
290 throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", issuer));
292 else if (!entity.second) {
293 log.warn("unable to locate SAML 2.0 identity provider role for provider (%s)", issuer);
294 throw MetadataException("Unable to locate SAML 2.0 identity provider role for provider ($entityID)", namedparams(1, "entityID", issuer));
298 vector<const Assertion*> assertions;
300 AttributeExtractor* extractor = app.getAttributeExtractor();
302 Locker extlocker(extractor);
304 pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
306 log.debug("extracting metadata-derived attributes...");
308 // We pass nullptr for "issuer" because the IdP isn't the one asserting metadata-based attributes.
309 extractor->extractAttributes(app, nullptr, *entity.second, resolvedAttrs);
310 for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
311 vector<string>& ids = (*a)->getAliases();
312 for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
313 *id = mprefix.second + *id;
316 catch (exception& ex) {
317 log.error("caught exception extracting attributes: %s", ex.what());
321 log.debug("extracting pushed attributes...");
322 for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
324 // Save off any assertions for later use by resolver.
325 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
327 assertions.push_back(assertion);
328 extractor->extractAttributes(app, entity.second, *(*t), resolvedAttrs);
330 catch (exception& ex) {
331 log.error("caught exception extracting attributes: %s", ex.what());
335 AttributeFilter* filter = app.getAttributeFilter();
336 if (filter && !resolvedAttrs.empty()) {
337 BasicFilteringContext fc(app, resolvedAttrs, entity.second);
338 Locker filtlocker(filter);
340 filter->filterAttributes(fc, resolvedAttrs);
342 catch (exception& ex) {
343 log.error("caught exception filtering attributes: %s", ex.what());
344 log.error("dumping extracted attributes due to filtering exception");
345 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
346 resolvedAttrs.clear();
351 log.warn("no AttributeExtractor plugin installed, check log during startup");
355 AttributeResolver* resolver = app.getAttributeResolver();
357 log.debug("resolving attributes...");
359 vector<Attribute*> inputs = inputAttrs;
360 inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
362 Locker locker(resolver);
363 auto_ptr<ResolutionContext> ctx(
364 resolver->createResolutionContext(
367 samlconstants::SAML20P_NS,
375 resolver->resolveAttributes(*ctx.get());
376 if (!ctx->getResolvedAttributes().empty())
377 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
380 catch (exception& ex) {
381 log.error("attribute resolution failed: %s", ex.what());
384 throw ConfigurationException("Cannot process request using lite version of shibsp library.");
388 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
390 if (features && SPConfig::OutOfProcess) {
392 features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
394 if (!(features && SPConfig::InProcess))
395 features |= SPConfig::Listener;
397 else if (features && SPConfig::InProcess) {
398 features |= SPConfig::Listener;
400 SPConfig::getConfig().setFeatures(features);
401 if (!SPConfig::getConfig().init())
403 if (!SPConfig::getConfig().instantiate(config, rethrow))
409 * Shuts down runtime.
411 * Each process using the library SHOULD call this function exactly once before terminating itself.
413 void ShibbolethResolver::term()
415 SPConfig::getConfig().term();
419 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
421 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
422 SPConfig& conf = SPConfig::getConfig();
423 if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
424 conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
426 return 0; // signal success
429 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
431 // Factories normally get unregistered during library shutdown, so no work usually required here.