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