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