Clean up file headers and NOTICE file.
[shibboleth/cpp-sp-resolver.git] / src / shibresolver / resolver.cpp
1 /**
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 /**
17  * resolver.cpp
18  *
19  * An embeddable component interface to Shibboleth SP attribute processing.
20  */
21
22 #include "internal.h"
23
24 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
25 # ifdef SHIBRESOLVER_HAVE_GSSMIT
26 #  include <gssapi/gssapi_ext.h>
27 # endif
28 #endif
29
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>
37 #ifndef SHIBSP_LITE
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>
47 #endif
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/XMLHelper.h>
53 #include <xercesc/util/Base64.hpp>
54
55 using namespace shibresolver;
56 using namespace shibsp;
57 #ifndef SHIBSP_LITE
58 using namespace opensaml;
59 using namespace opensaml::saml2md;
60 #endif
61 using namespace xmltooling;
62 using namespace std;
63
64 namespace shibresolver {
65     class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
66     public:
67         RemotedResolver() {}
68         ~RemotedResolver() {}
69
70         struct Transaction {
71             ~Transaction() {
72                 for_each(tokens.begin(), tokens.end(), xmltooling::cleanup<XMLObject>());
73                 for_each(inputAttrs.begin(), inputAttrs.end(), xmltooling::cleanup<Attribute>());
74                 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<Attribute>());
75             }
76
77             vector<const XMLObject*> tokens;
78             vector<Attribute*> inputAttrs;
79             vector<Attribute*> resolvedAttrs;
80         };
81
82         void receive(DDF& in, ostream& out);
83         void resolve(
84             const Application& app,
85             const char* issuer,
86             const vector<const XMLObject*>& tokens,
87             const vector<Attribute*>& inputAttrs,
88             vector <Attribute*>& resolvedAttrs
89             ) const;
90     };
91
92     static RemotedResolver g_Remoted;
93 };
94
95 ShibbolethResolver* ShibbolethResolver::create()
96 {
97     return new ShibbolethResolver();
98 }
99
100 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
101 #ifdef SHIBRESOLVER_HAVE_GSSAPI
102         ,m_gsswrapper(NULL)
103 #endif
104 {
105 }
106
107 ShibbolethResolver::~ShibbolethResolver()
108 {
109 #ifdef SHIBRESOLVER_HAVE_GSSAPI
110     delete m_gsswrapper;
111 #endif
112     for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
113     if (m_sp)
114         m_sp->unlock();
115 }
116
117 void ShibbolethResolver::setRequest(const SPRequest* request)
118 {
119     m_request = request;
120 #if defined(SHIBSP_HAVE_GSSAPI) && defined (SHIBRESOLVER_HAVE_GSSAPI)
121     if (request) {
122         const GSSRequest* gss = dynamic_cast<const GSSRequest*>(request);
123         if (gss) {
124             // TODO: fix API to prevent destruction of contexts
125             gss_ctx_id_t ctx = gss->getGSSContext();
126             addToken(&ctx);
127         }
128     }
129 #endif
130 }
131
132 void ShibbolethResolver::setApplicationID(const char* appID)
133 {
134     m_appID.erase();
135     if (appID)
136         m_appID = appID;
137 }
138
139 void ShibbolethResolver::setIssuer(const char* issuer)
140 {
141     m_issuer.erase();
142     if (issuer)
143         m_issuer = issuer;
144 }
145
146 void ShibbolethResolver::addToken(const XMLObject* token)
147 {
148     if (token)
149         m_tokens.push_back(token);
150 }
151
152 #ifdef SHIBRESOLVER_HAVE_GSSAPI
153 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
154 {
155     if (m_gsswrapper) {
156         delete m_gsswrapper;
157         m_gsswrapper = NULL;
158     }
159
160     if (ctx && *ctx != GSS_C_NO_CONTEXT) {
161         OM_uint32 minor;
162         gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
163         OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
164         if (major == GSS_S_COMPLETE) {
165             xsecsize_t len=0;
166             XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
167             if (out) {
168                 string s;
169                 s.append(reinterpret_cast<char*>(out), len);
170                 auto_ptr_XMLCh temp(s.c_str());
171 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
172                 XMLString::release(&out);
173 #else
174                 XMLString::release((char**)&out);
175 #endif
176                 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
177                 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
178                 m_gsswrapper->setTextContent(temp.get());
179             }
180             else {
181                 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
182             }
183             gss_release_buffer(&minor, &contextbuf);
184         }
185         else {
186             Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
187         }
188     }
189 }
190
191 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
192 void ShibbolethResolver::addToken(gss_name_t name)
193 {
194     if (m_gsswrapper) {
195         delete m_gsswrapper;
196         m_gsswrapper = NULL;
197     }
198
199     OM_uint32 minor;
200     gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
201     OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
202     if (major == GSS_S_COMPLETE) {
203         addToken(&namebuf);
204         gss_release_buffer(&minor, &namebuf);
205     }
206     else {
207         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
208     }
209 }
210 #endif
211
212 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
213 {
214     if (m_gsswrapper) {
215         delete m_gsswrapper;
216         m_gsswrapper = NULL;
217     }
218
219     xsecsize_t len=0;
220     XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
221     if (out) {
222         string s;
223         s.append(reinterpret_cast<char*>(out), len);
224         auto_ptr_XMLCh temp(s.c_str());
225 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
226         XMLString::release(&out);
227 #else
228         XMLString::release((char**)&out);
229 #endif
230         static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
231         m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
232         m_gsswrapper->setTextContent(temp.get());
233     }
234     else {
235         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
236     }
237 }
238
239 #endif
240
241 void ShibbolethResolver::addAttribute(Attribute* attr)
242 {
243     if (attr)
244         m_inputAttributes.push_back(attr);
245 }
246
247 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
248 {
249     return m_resolvedAttributes;
250 }
251
252 RequestMapper::Settings ShibbolethResolver::getSettings() const
253 {
254     if (!m_request)
255         throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
256     return m_request->getRequestSettings();
257 }
258
259 void ShibbolethResolver::resolve()
260 {
261     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
262     SPConfig& conf = SPConfig::getConfig();
263     if (!m_request) {
264         m_sp = conf.getServiceProvider();
265         m_sp->lock();
266         if (m_appID.empty())
267             m_appID = "default";
268     }
269
270     const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
271     if (!app)
272         throw ConfigurationException("Unable to locate application for resolution.");
273
274 #ifdef SHIBRESOLVER_HAVE_GSSAPI
275     if (m_gsswrapper)
276         m_tokens.push_back(m_gsswrapper);
277 #endif
278
279     if (conf.isEnabled(SPConfig::OutOfProcess)) {
280         g_Remoted.resolve(
281             *app,
282             m_issuer.c_str(),
283             m_tokens,
284             m_inputAttributes,
285             m_resolvedAttributes
286             );
287     }
288     else {
289         // When not out of process, we remote all the message processing.
290         DDF out,in = DDF("org.project-moonshot.shibresolver");
291         DDFJanitor jin(in), jout(out);
292         in.addmember("application_id").string(app->getId());
293         if (!m_issuer.empty())
294             in.addmember("issuer").string(m_issuer.c_str());
295         if (!m_tokens.empty()) {
296             DDF& tokens = in.addmember("tokens").list();
297             for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
298                 ostringstream os;
299                 os << *(*t);
300                 tokens.add(DDF(NULL).string(os.str().c_str()));
301             }
302         }
303         if (!m_inputAttributes.empty()) {
304             DDF attr;
305             DDF& attrs = in.addmember("attributes").list();
306             for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
307                 attr = (*a)->marshall();
308                 attrs.add(attr);
309             }
310         }
311
312         out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
313
314         Attribute* attribute;
315         DDF attr = out.first();
316         while (!attr.isnull()) {
317             try {
318                 attribute = Attribute::unmarshall(attr);
319                 m_resolvedAttributes.push_back(attribute);
320                 if (log.isDebugEnabled())
321                     log.debug("unmarshalled attribute (ID: %s) with %d value%s",
322                         attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
323             }
324             catch (AttributeException& ex) {
325                 const char* id = attr.first().name();
326                 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
327             }
328             attr = out.next();
329         }
330     }
331 }
332
333 void RemotedResolver::receive(DDF& in, ostream& out)
334 {
335     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
336
337     // Find application.
338     const char* aid = in["application_id"].string();
339     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
340     if (!app) {
341         // Something's horribly wrong.
342         log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
343         throw ConfigurationException("Unable to locate application for resolution, deleted?");
344     }
345
346     DDF ret(NULL);
347     DDFJanitor jout(ret);
348
349     Transaction t;
350
351     DDF tlist = in["tokens"];
352     DDF token = tlist.first();
353     while (token.isstring()) {
354         // Parse and bind the document into an XMLObject.
355         istringstream instr(token.string());
356         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
357         XercesJanitor<DOMDocument> janitor(doc);
358         XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
359         t.tokens.push_back(xmlObject);
360         janitor.release();
361         token = tlist.next();
362     }
363
364     DDF alist = in["attributes"];
365     Attribute* attribute;
366     DDF attr = alist.first();
367     while (!attr.isnull()) {
368         attribute = Attribute::unmarshall(attr);
369         t.inputAttrs.push_back(attribute);
370         if (log.isDebugEnabled())
371             log.debug("unmarshalled attribute (ID: %s) with %d value%s",
372                 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
373         attr = alist.next();
374     }
375
376     resolve(*app, in["issuer"].string(), t.tokens, t.inputAttrs, t.resolvedAttrs);
377
378     if (!t.resolvedAttrs.empty()) {
379         ret.list();
380         for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
381             attr = (*a)->marshall();
382             ret.add(attr);
383         }
384     }
385
386     out << ret;
387 }
388
389 void RemotedResolver::resolve(
390     const Application& app,
391     const char* issuer,
392     const vector<const XMLObject*>& tokens,
393     const vector<Attribute*>& inputAttrs,
394     vector <Attribute*>& resolvedAttrs
395     ) const
396 {
397 #ifndef SHIBSP_LITE
398     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
399     string issuerstr(issuer ? issuer : "");
400     pair<const EntityDescriptor*,const RoleDescriptor*> entity = make_pair((EntityDescriptor*)NULL, (RoleDescriptor*)NULL);
401     MetadataProvider* m = app.getMetadataProvider(false);
402     Locker locker(m);
403     if (!m) {
404         log.warn("no metadata providers are configured");
405     }
406     else {
407         if (issuerstr.empty()) {
408             // Attempt to locate an issuer based on input token.
409             for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
410                 const saml2::Assertion* assertion = dynamic_cast<const saml2::Assertion*>(*t);
411                 if (assertion && assertion->getIssuer()) {
412                     auto_ptr_char iss(assertion->getIssuer()->getName());
413                     if (iss.get() && *iss.get()) {
414                         issuerstr = iss.get();
415                         break;
416                     }
417                 }
418             }
419             if (!issuerstr.empty()) {
420                 log.info("setting issuer based on input token (%s)", issuerstr.c_str());
421             }
422         }
423
424         if (!issuerstr.empty()) {
425             // Lookup metadata for the issuer.
426             MetadataProviderCriteria idpmc(app, issuerstr.c_str(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
427             entity = m->getEntityDescriptor(idpmc);
428             if (!entity.first) {
429                 log.warn("unable to locate metadata for provider (%s)", issuerstr.c_str());
430             }
431             else if (!entity.second) {
432                 MetadataProviderCriteria aamc(app, issuerstr.c_str(), &AttributeAuthorityDescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
433                 entity = m->getEntityDescriptor(aamc);
434                 if (!entity.second) {
435                     log.warn("unable to locate SAML 2.0 IdP or AA role for provider (%s)", issuerstr.c_str());
436                 }
437             }
438         }
439     }
440
441     vector<const Assertion*> assertions;
442
443     AttributeExtractor* extractor = app.getAttributeExtractor();
444     if (extractor) {
445         Locker extlocker(extractor);
446         if (entity.second) {
447             pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
448             if (mprefix.first) {
449                 log.debug("extracting metadata-derived attributes...");
450                 try {
451                     // We pass NULL for "issuer" because the IdP isn't the one asserting metadata-based attributes.
452                     extractor->extractAttributes(app, NULL, *entity.second, resolvedAttrs);
453                     for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
454                         vector<string>& ids = (*a)->getAliases();
455                         for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
456                             *id = mprefix.second + *id;
457                     }
458                 }
459                 catch (exception& ex) {
460                     log.error("caught exception extracting attributes: %s", ex.what());
461                 }
462             }
463         }
464         log.debug("extracting pushed attributes...");
465         for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
466             try {
467                 // Save off any assertions for later use by resolver.
468                 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
469                 if (assertion)
470                     assertions.push_back(assertion);
471                 extractor->extractAttributes(app, entity.second, *(*t), resolvedAttrs);
472             }
473             catch (exception& ex) {
474                 log.error("caught exception extracting attributes: %s", ex.what());
475             }
476         }
477
478         AttributeFilter* filter = app.getAttributeFilter();
479         if (filter && !resolvedAttrs.empty()) {
480             BasicFilteringContext fc(app, resolvedAttrs, entity.second);
481             Locker filtlocker(filter);
482             try {
483                 filter->filterAttributes(fc, resolvedAttrs);
484             }
485             catch (exception& ex) {
486                 log.error("caught exception filtering attributes: %s", ex.what());
487                 log.error("dumping extracted attributes due to filtering exception");
488                 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
489                 resolvedAttrs.clear();
490             }
491         }
492     }
493     else {
494         log.warn("no AttributeExtractor plugin installed, check log during startup");
495     }
496
497     try {
498         AttributeResolver* resolver = app.getAttributeResolver();
499         if (resolver) {
500             log.debug("resolving attributes...");
501
502             vector<Attribute*> inputs = inputAttrs;
503             inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
504
505             Locker locker(resolver);
506             auto_ptr<ResolutionContext> ctx(
507                 resolver->createResolutionContext(
508                     app,
509                     entity.first,
510                     samlconstants::SAML20P_NS,
511                     NULL,
512                     NULL,
513                     NULL,
514                     &assertions,
515                     &inputs
516                     )
517                 );
518             resolver->resolveAttributes(*ctx.get());
519             if (!ctx->getResolvedAttributes().empty())
520                 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
521         }
522     }
523     catch (exception& ex) {
524         log.error("attribute resolution failed: %s", ex.what());
525     }
526 #else
527     throw ConfigurationException("Cannot process request using lite version of shibsp library.");
528 #endif
529 }
530
531 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
532 {
533     if (features && SPConfig::OutOfProcess) {
534 #ifndef SHIBSP_LITE
535         features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
536 #endif
537         if (!(features && SPConfig::InProcess))
538             features |= SPConfig::Listener;
539     }
540     else if (features && SPConfig::InProcess) {
541         features |= SPConfig::Listener;
542     }
543     SPConfig::getConfig().setFeatures(features);
544     if (!SPConfig::getConfig().init())
545         return false;
546     if (!SPConfig::getConfig().instantiate(config, rethrow))
547         return false;
548     return true;
549 }
550
551 /**
552     * Shuts down runtime.
553     *
554     * Each process using the library SHOULD call this function exactly once before terminating itself.
555     */
556 void ShibbolethResolver::term()
557 {
558     SPConfig::getConfig().term();
559 }
560
561
562 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
563 {
564 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
565     SPConfig& conf = SPConfig::getConfig();
566     if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
567         conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
568 #endif
569     return 0;   // signal success
570 }
571
572 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
573 {
574     // Factories normally get unregistered during library shutdown, so no work usually required here.
575 }