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