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