a267af5ca9ac1a5c4e914dd5486f3429b3f20b98
[shibboleth/cpp-sp.git] / shibsp / handler / impl / SAML2ArtifactResolution.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * SAML2ArtifactResolution.cpp
23  * 
24  * Handler for resolving SAML 2.0 artifacts.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/AbstractHandler.h"
33 #include "handler/RemotedHandler.h"
34 #include "util/SPConstants.h"
35
36 #ifndef SHIBSP_LITE
37 # include "security/SecurityPolicy.h"
38 # include "security/SecurityPolicyProvider.h"
39 # include <saml/exceptions.h>
40 # include <saml/SAMLConfig.h>
41 # include <saml/binding/ArtifactMap.h>
42 # include <saml/binding/MessageEncoder.h>
43 # include <saml/binding/MessageDecoder.h>
44 # include <saml/binding/SAMLArtifact.h>
45 # include <saml/saml2/core/Assertions.h>
46 # include <saml/saml2/core/Protocols.h>
47 # include <saml/saml2/metadata/Metadata.h>
48 using namespace opensaml::saml2md;
49 using namespace opensaml::saml2p;
50 using namespace opensaml::saml2;
51 using namespace opensaml;
52 #else
53 # include "lite/SAMLConstants.h"
54 #endif
55
56 #include <boost/scoped_ptr.hpp>
57 #include <xmltooling/soap/SOAP.h>
58
59 using namespace shibspconstants;
60 using namespace shibsp;
61 using namespace soap11;
62 using namespace xmltooling;
63 using namespace boost;
64 using namespace std;
65
66 namespace shibsp {
67
68 #if defined (_MSC_VER)
69     #pragma warning( push )
70     #pragma warning( disable : 4250 )
71 #endif
72
73     class SHIBSP_API SAML2ArtifactResolution : public AbstractHandler, public RemotedHandler 
74     {
75     public:
76         SAML2ArtifactResolution(const DOMElement* e, const char* appId);
77         virtual ~SAML2ArtifactResolution() {}
78
79         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
80         void receive(DDF& in, ostream& out);
81
82 #ifndef SHIBSP_LITE
83         const char* getType() const {
84             return "ArtifactResolutionService";
85         }
86
87         void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
88             // Initial guess at index to use.
89             pair<bool,unsigned int> ix = pair<bool,unsigned int>(false,0);
90             if (!strncmp(handlerURL, "https", 5))
91                 ix = getUnsignedInt("sslIndex", shibspconstants::ASCII_SHIB2SPCONFIG_NS);
92             if (!ix.first)
93                 ix = getUnsignedInt("index");
94             if (!ix.first)
95                 ix.second = 1;
96
97             // Find maximum index in use and go one higher.
98             const vector<ArtifactResolutionService*>& services = const_cast<const SPSSODescriptor&>(role).getArtifactResolutionServices();
99             if (!services.empty() && ix.second <= services.back()->getIndex().second)
100                 ix.second = services.back()->getIndex().second + 1;
101
102             const char* loc = getString("Location").second;
103             string hurl(handlerURL);
104             if (*loc != '/')
105                 hurl += '/';
106             hurl += loc;
107             auto_ptr_XMLCh widen(hurl.c_str());
108
109             auto_ptr<ArtifactResolutionService> ep(ArtifactResolutionServiceBuilder::buildArtifactResolutionService());
110             ep->setLocation(widen.get());
111             ep->setBinding(getXMLString("Binding").second);
112             ep->setIndex(ix.second);
113             role.getArtifactResolutionServices().push_back(ep.get());
114             ep.release();
115         }
116 #endif
117         const XMLCh* getProtocolFamily() const {
118             return samlconstants::SAML20P_NS;
119         }
120
121     private:
122         pair<bool,long> processMessage(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
123 #ifndef SHIBSP_LITE
124         pair<bool,long> emptyResponse(
125             const Application& app, const ArtifactResolve& request, HTTPResponse& httpResponse, const EntityDescriptor* recipient
126             ) const;
127
128         scoped_ptr<MessageEncoder> m_encoder;
129         scoped_ptr<MessageDecoder> m_decoder;
130 #endif
131     };
132
133 #if defined (_MSC_VER)
134     #pragma warning( pop )
135 #endif
136
137     Handler* SHIBSP_DLLLOCAL SAML2ArtifactResolutionFactory(const pair<const DOMElement*,const char*>& p)
138     {
139         return new SAML2ArtifactResolution(p.first, p.second);
140     }
141
142 };
143
144 SAML2ArtifactResolution::SAML2ArtifactResolution(const DOMElement* e, const char* appId)
145     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".ArtifactResolution.SAML2"))
146 {
147 #ifndef SHIBSP_LITE
148     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
149         m_encoder.reset(
150             SAMLConfig::getConfig().MessageEncoderManager.newPlugin(
151                 getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
152                 )
153             );
154         m_decoder.reset(
155             SAMLConfig::getConfig().MessageDecoderManager.newPlugin(
156                 getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
157                 )
158             );
159     }
160 #endif
161     string address(appId);
162     address += getString("Location").second;
163     address += "::run::SAML2Artifact";
164     setAddress(address.c_str());
165 }
166
167 pair<bool,long> SAML2ArtifactResolution::run(SPRequest& request, bool isHandler) const
168 {
169     string relayState;
170     SPConfig& conf = SPConfig::getConfig();
171     
172     try {
173         if (conf.isEnabled(SPConfig::OutOfProcess)) {
174             // When out of process, we run natively and directly process the message.
175             return processMessage(request.getApplication(), request, request);
176         }
177         else {
178             // When not out of process, we remote all the message processing.
179             DDF out,in = wrap(request, nullptr, true);
180             DDFJanitor jin(in), jout(out);
181             
182             out = request.getServiceProvider().getListenerService()->send(in);
183             return unwrap(request, out);
184         }
185     }
186     catch (std::exception& ex) {
187         m_log.error("error while processing request: %s", ex.what());
188
189         // Build a SOAP fault around the error.
190         auto_ptr<Fault> fault(FaultBuilder::buildFault());
191         Faultcode* code = FaultcodeBuilder::buildFaultcode();
192         fault->setFaultcode(code);
193         code->setCode(&Faultcode::SERVER);
194         Faultstring* fs = FaultstringBuilder::buildFaultstring();
195         fault->setFaultstring(fs);
196         pair<bool,bool> flag = getBool("detailedErrors", m_configNS.get());
197         auto_ptr_XMLCh msg((flag.first && flag.second) ? ex.what() : "Error processing request.");
198         fs->setString(msg.get());
199 #ifndef SHIBSP_LITE
200         // Use MessageEncoder to send back the fault.
201         long ret = m_encoder->encode(request, fault.get(), nullptr);
202         fault.release();
203         return make_pair(true, ret);
204 #else
205         // Brute force the fault to avoid library dependency.
206         scoped_ptr<Envelope> env(EnvelopeBuilder::buildEnvelope());
207         Body* body = BodyBuilder::buildBody();
208         env->setBody(body);
209         body->getUnknownXMLObjects().push_back(fault.get());
210         fault.release();
211         string xmlbuf;
212         XMLHelper::serialize(env->marshall(), xmlbuf);
213         istringstream s(xmlbuf);
214         request.setContentType("text/xml");
215         return make_pair(true, request.sendError(s));
216 #endif
217     }
218 }
219
220 void SAML2ArtifactResolution::receive(DDF& in, ostream& out)
221 {
222     // Find application.
223     const char* aid=in["application_id"].string();
224     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
225     if (!app) {
226         // Something's horribly wrong.
227         m_log.error("couldn't find application (%s) for artifact resolution", aid ? aid : "(missing)");
228         throw ConfigurationException("Unable to locate application for artifact resolution, deleted?");
229     }
230     
231     // Unpack the request.
232     scoped_ptr<HTTPRequest> req(getRequest(in));
233     //m_log.debug("found %d client certificates", req->getClientCertificates().size());
234
235     // Wrap a response shim.
236     DDF ret(nullptr);
237     DDFJanitor jout(ret);
238     scoped_ptr<HTTPResponse> resp(getResponse(ret));
239         
240     try {
241         // Since we're remoted, the result should either be a throw, a false/0 return,
242         // which we just return as an empty structure, or a response/redirect,
243         // which we capture in the facade and send back.
244         processMessage(*app, *req, *resp);
245         out << ret;
246     }
247     catch (std::exception& ex) {
248 #ifndef SHIBSP_LITE
249         m_log.error("error while processing request: %s", ex.what());
250
251         // Use MessageEncoder to send back a SOAP fault.
252         auto_ptr<Fault> fault(FaultBuilder::buildFault());
253         Faultcode* code = FaultcodeBuilder::buildFaultcode();
254         fault->setFaultcode(code);
255         code->setCode(&Faultcode::SERVER);
256         Faultstring* fs = FaultstringBuilder::buildFaultstring();
257         fault->setFaultstring(fs);
258         pair<bool,bool> flag = getBool("detailedErrors", m_configNS.get());
259         auto_ptr_XMLCh msg((flag.first && flag.second) ? ex.what() : "Error processing request.");
260         fs->setString(msg.get());
261         m_encoder->encode(*resp, fault.get(), nullptr);
262         fault.release();
263         out << ret;
264 #else
265         throw;  // should never happen anyway
266 #endif
267     }
268 }
269
270 pair<bool,long> SAML2ArtifactResolution::processMessage(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const
271 {
272 #ifndef SHIBSP_LITE
273     m_log.debug("processing SAML 2.0 ArtifactResolve request");
274
275     ArtifactMap* artmap = SAMLConfig::getConfig().getArtifactMap();
276     if (!artmap)
277         throw ConfigurationException("No ArtifactMap instance installed.");
278
279     // Locate policy key.
280     pair<bool,const char*> policyId = getString("policyId", m_configNS.get());  // namespace-qualified if inside handler element
281     if (!policyId.first)
282         policyId = application.getString("policyId");   // unqualified in Application(s) element
283         
284     // Lock metadata for use by policy.
285     Locker metadataLocker(application.getMetadataProvider());
286
287     // Create the policy.
288     scoped_ptr<SecurityPolicy> policy(
289         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, &IDPSSODescriptor::ELEMENT_QNAME, policyId.second)
290         );
291     
292     // Decode the message and verify that it's a secured ArtifactResolve request.
293     string relayState;
294     scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, *policy));
295     if (!msg)
296         throw BindingException("Failed to decode a SAML request.");
297     const ArtifactResolve* req = dynamic_cast<const ArtifactResolve*>(msg.get());
298     if (!req)
299         throw FatalProfileException("Decoded message was not a samlp::ArtifactResolve request.");
300
301     const EntityDescriptor* entity = policy->getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) : nullptr;
302
303     try {
304         auto_ptr_char artifact(req->getArtifact() ? req->getArtifact()->getArtifact() : nullptr);
305         if (!artifact.get() || !*artifact.get())
306             return emptyResponse(application, *req, httpResponse, entity);
307         auto_ptr_char issuer(policy->getIssuer() ? policy->getIssuer()->getName() : nullptr);
308
309         m_log.info("resolving artifact (%s) for (%s)", artifact.get(), issuer.get() ? issuer.get() : "unknown");
310
311         // Parse the artifact and retrieve the object.
312         scoped_ptr<SAMLArtifact> artobj(SAMLArtifact::parse(artifact.get()));
313         auto_ptr<XMLObject> payload(artmap->retrieveContent(artobj.get(), issuer.get()));
314
315         if (!policy->isAuthenticated()) {
316             m_log.error("request for artifact was unauthenticated, purging the artifact mapping");
317             return emptyResponse(application, *req, httpResponse, entity);
318         }
319
320         m_log.debug("artifact resolved, preparing response");
321
322         // Wrap it in a response.
323         auto_ptr<ArtifactResponse> resp(ArtifactResponseBuilder::buildArtifactResponse());
324         resp->setInResponseTo(req->getID());
325         Issuer* me = IssuerBuilder::buildIssuer();
326         me->setName(application.getRelyingParty(entity)->getXMLString("entityID").second);
327         resp->setPayload(payload.get());
328         payload.release();
329
330         long ret = sendMessage(
331             *m_encoder, resp.get(), relayState.c_str(), nullptr, policy->getIssuerMetadata(), application, httpResponse, "signResponses"
332             );
333         resp.release();  // freed by encoder
334         return make_pair(true, ret);
335     }
336     catch (std::exception& ex) {
337         // Trap localized errors.
338         m_log.error("error processing artifact request: %s", ex.what());
339         return emptyResponse(application, *req, httpResponse, entity);
340     }
341 #else
342     return make_pair(false,0L);
343 #endif
344 }
345
346 #ifndef SHIBSP_LITE
347 pair<bool,long> SAML2ArtifactResolution::emptyResponse(
348     const Application& app, const ArtifactResolve& request, HTTPResponse& httpResponse, const EntityDescriptor* recipient
349     ) const
350 {
351     auto_ptr<ArtifactResponse> resp(ArtifactResponseBuilder::buildArtifactResponse());
352     resp->setInResponseTo(request.getID());
353     Issuer* me = IssuerBuilder::buildIssuer();
354     me->setName(app.getRelyingParty(recipient)->getXMLString("entityID").second);
355     fillStatus(*resp, StatusCode::SUCCESS);
356     long ret = m_encoder->encode(httpResponse, resp.get(), nullptr);
357     resp.release();  // freed by encoder
358     return make_pair(true, ret);
359 }
360 #endif