Check for empty issuer.
[shibboleth/cpp-sp-resolver.git] / shibresolver / resolver.cpp
1 /*
2  *  Copyright 2010 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/SPRequest.h>
28 #include <shibsp/ServiceProvider.h>
29 #include <shibsp/attribute/Attribute.h>
30 #include <shibsp/remoting/ListenerService.h>
31 #ifndef SHIBSP_LITE
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>
41 #endif
42 #include <xmltooling/XMLObjectBuilder.h>
43 #include <xmltooling/XMLToolingConfig.h>
44 #include <xmltooling/util/ParserPool.h>
45 #include <xmltooling/util/XMLHelper.h>
46
47 using namespace shibresolver;
48 using namespace shibsp;
49 #ifndef SHIBSP_LITE
50 using namespace opensaml;
51 using namespace opensaml::saml2md;
52 #endif
53 using namespace xmltooling;
54 using namespace std;
55
56 namespace shibresolver {
57     class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
58     public:
59         RemotedResolver() {}
60         ~RemotedResolver() {}
61
62         struct Transaction {
63             ~Transaction() {
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>());
67             }
68
69             vector<const XMLObject*> tokens;
70             vector<Attribute*> inputAttrs;
71             vector<Attribute*> resolvedAttrs;
72         };
73
74         void receive(DDF& in, ostream& out);
75         void resolve(
76             const Application& app,
77             const char* issuer,
78             const vector<const XMLObject*>& tokens,
79             const vector<Attribute*>& inputAttrs,
80             vector <Attribute*>& resolvedAttrs
81             ) const;
82     };
83
84     static RemotedResolver g_Remoted;
85 };
86
87 ShibbolethResolver* ShibbolethResolver::create()
88 {
89     return new ShibbolethResolver();
90 }
91
92 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
93 {
94 }
95
96 ShibbolethResolver::~ShibbolethResolver()
97 {
98     for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
99     if (m_sp)
100         m_sp->unlock();
101 }
102
103 void ShibbolethResolver::setRequest(const SPRequest* request)
104 {
105     m_request = request;
106 }
107
108 void ShibbolethResolver::setApplicationID(const char* appID)
109 {
110     m_appID.erase();
111     if (appID)
112         m_appID = appID;
113 }
114
115 void ShibbolethResolver::setIssuer(const char* issuer)
116 {
117     m_issuer.erase();
118     if (issuer)
119         m_issuer = issuer;
120 }
121
122 void ShibbolethResolver::addToken(const XMLObject* token)
123 {
124     if (token)
125         m_tokens.push_back(token);
126 }
127
128 void ShibbolethResolver::addAttribute(Attribute* attr)
129 {
130     if (attr)
131         m_inputAttributes.push_back(attr);
132 }
133
134 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
135 {
136     return m_resolvedAttributes;
137 }
138
139 RequestMapper::Settings ShibbolethResolver::getSettings() const
140 {
141     if (!m_request)
142         throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
143     return m_request->getRequestSettings();
144 }
145
146 void ShibbolethResolver::resolve()
147 {
148     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
149     SPConfig& conf = SPConfig::getConfig();
150     if (!m_request) {
151         m_sp = conf.getServiceProvider();
152         m_sp->lock();
153         if (m_appID.empty())
154             m_appID = "default";
155     }
156
157     const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
158     if (!app)
159         throw ConfigurationException("Unable to locate application for resolution.");
160
161     if (conf.isEnabled(SPConfig::OutOfProcess)) {
162         g_Remoted.resolve(
163             *app,
164             m_issuer.c_str(),
165             m_tokens,
166             m_inputAttributes,
167             m_resolvedAttributes
168             );
169     }
170     else {
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) {
180                 ostringstream os;
181                 os << *(*t);
182                 tokens.add(DDF(NULL).string(os.str().c_str()));
183             }
184         }
185         if (!m_inputAttributes.empty()) {
186             DDF attr;
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();
190                 attrs.add(attr);
191             }
192         }
193
194         out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
195
196         Attribute* attribute;
197         DDF attr = out.first();
198         while (!attr.isnull()) {
199             try {
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" : "");
205             }
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());
209             }
210             attr = out.next();
211         }
212     }
213 }
214
215 void RemotedResolver::receive(DDF& in, ostream& out)
216 {
217     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
218
219     // Find application.
220     const char* aid = in["application_id"].string();
221     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
222     if (!app) {
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?");
226     }
227
228     DDF ret(NULL);
229     DDFJanitor jout(ret);
230
231     Transaction t;
232
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);
242         janitor.release();
243         token = tlist.next();
244     }
245
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" : "");
255         attr = alist.next();
256     }
257
258     resolve(*app, in["issuer"].string(), t.tokens, t.inputAttrs, t.resolvedAttrs);
259
260     if (!t.resolvedAttrs.empty()) {
261         ret.list();
262         for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
263             attr = (*a)->marshall();
264             ret.add(attr);
265         }
266     }
267
268     out << ret;
269 }
270
271 void RemotedResolver::resolve(
272     const Application& app,
273     const char* issuer,
274     const vector<const XMLObject*>& tokens,
275     const vector<Attribute*>& inputAttrs,
276     vector <Attribute*>& resolvedAttrs
277     ) const
278 {
279 #ifndef SHIBSP_LITE
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();
283     Locker locker(m);
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);
288         if (!entity.first) {
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));
291         }
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));
295         }
296     }
297
298     vector<const Assertion*> assertions;
299
300     AttributeExtractor* extractor = app.getAttributeExtractor();
301     if (extractor) {
302         Locker extlocker(extractor);
303         if (entity.second) {
304             pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
305             if (mprefix.first) {
306                 log.debug("extracting metadata-derived attributes...");
307                 try {
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;
314                     }
315                 }
316                 catch (exception& ex) {
317                     log.error("caught exception extracting attributes: %s", ex.what());
318                 }
319             }
320         }
321         log.debug("extracting pushed attributes...");
322         for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t!=tokens.end(); ++t) {
323             try {
324                 // Save off any assertions for later use by resolver.
325                 const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
326                 if (assertion)
327                     assertions.push_back(assertion);
328                 extractor->extractAttributes(app, entity.second, *(*t), resolvedAttrs);
329             }
330             catch (exception& ex) {
331                 log.error("caught exception extracting attributes: %s", ex.what());
332             }
333         }
334
335         AttributeFilter* filter = app.getAttributeFilter();
336         if (filter && !resolvedAttrs.empty()) {
337             BasicFilteringContext fc(app, resolvedAttrs, entity.second);
338             Locker filtlocker(filter);
339             try {
340                 filter->filterAttributes(fc, resolvedAttrs);
341             }
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();
347             }
348         }
349     }
350     else {
351         log.warn("no AttributeExtractor plugin installed, check log during startup");
352     }
353
354     try {
355         AttributeResolver* resolver = app.getAttributeResolver();
356         if (resolver) {
357             log.debug("resolving attributes...");
358
359             vector<Attribute*> inputs = inputAttrs;
360             inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
361
362             Locker locker(resolver);
363             auto_ptr<ResolutionContext> ctx(
364                 resolver->createResolutionContext(
365                     app,
366                     entity.first,
367                     samlconstants::SAML20P_NS,
368                     nullptr,
369                     nullptr,
370                     nullptr,
371                     &assertions,
372                     &inputs
373                     )
374                 );
375             resolver->resolveAttributes(*ctx.get());
376             if (!ctx->getResolvedAttributes().empty())
377                 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
378         }
379     }
380     catch (exception& ex) {
381         log.error("attribute resolution failed: %s", ex.what());
382     }
383 #else
384     throw ConfigurationException("Cannot process request using lite version of shibsp library.");
385 #endif
386 }
387
388 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
389 {
390     if (features && SPConfig::OutOfProcess) {
391 #ifndef SHIBSP_LITE
392         features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
393 #endif
394         if (!(features && SPConfig::InProcess))
395             features |= SPConfig::Listener;
396     }
397     else if (features && SPConfig::InProcess) {
398         features |= SPConfig::Listener;
399     }
400     SPConfig::getConfig().setFeatures(features);
401     if (!SPConfig::getConfig().init())
402         return false;
403     if (!SPConfig::getConfig().instantiate(config, rethrow))
404         return false;
405     return true;
406 }
407
408 /**
409     * Shuts down runtime.
410     *
411     * Each process using the library SHOULD call this function exactly once before terminating itself.
412     */
413 void ShibbolethResolver::term()
414 {
415     SPConfig::getConfig().term();
416 }
417
418
419 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
420 {
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);
425 #endif
426     return 0;   // signal success
427 }
428
429 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
430 {
431     // Factories normally get unregistered during library shutdown, so no work usually required here.
432 }