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