SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-sp.git] / shibsp / binding / impl / ArtifactResolver.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  * ArtifactResolver.cpp
23  * 
24  * SAML artifact resolver for SP use.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "binding/ArtifactResolver.h"
30 #include "binding/SOAPClient.h"
31 #include "security/SecurityPolicy.h"
32 #include "util/SPConstants.h"
33
34 #include <fstream>
35 #include <boost/bind.hpp>
36 #include <boost/algorithm/string.hpp>
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/ParserPool.h>
39 #include <xmltooling/util/PathResolver.h>
40 #include <saml/exceptions.h>
41 #include <saml/saml1/core/Protocols.h>
42 #include <saml/saml1/binding/SAML1SOAPClient.h>
43 #include <saml/saml2/core/Protocols.h>
44 #include <saml/saml2/binding/SAML2Artifact.h>
45 #include <saml/saml2/binding/SAML2SOAPClient.h>
46 #include <saml/saml2/metadata/EndpointManager.h>
47 #include <saml/saml2/metadata/Metadata.h>
48 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
49 #include <saml/util/SAMLConstants.h>
50
51 using namespace shibsp;
52 using namespace opensaml::saml1p;
53 using namespace opensaml::saml2;
54 using namespace opensaml::saml2p;
55 using namespace opensaml::saml2md;
56 using namespace opensaml;
57 using namespace xmltooling;
58 using namespace boost;
59 using namespace std;
60
61 ArtifactResolver::ArtifactResolver()
62 {
63 }
64
65 ArtifactResolver::~ArtifactResolver()
66 {
67 }
68
69 bool ArtifactResolver::isSupported(const SSODescriptorType& ssoDescriptor) const
70 {
71     if (MessageDecoder::ArtifactResolver::isSupported(ssoDescriptor))
72         return true;
73
74     EndpointManager<ArtifactResolutionService> mgr(ssoDescriptor.getArtifactResolutionServices());
75     if (ssoDescriptor.hasSupport(samlconstants::SAML20P_NS)) {
76         return (mgr.getByBinding(shibspconstants::SHIB2_BINDING_FILE) != nullptr);
77     }
78
79     return false;
80 }
81
82 saml1p::Response* ArtifactResolver::resolve(
83     const vector<SAMLArtifact*>& artifacts,
84     const IDPSSODescriptor& idpDescriptor,
85     opensaml::SecurityPolicy& policy
86     ) const
87 {
88     MetadataCredentialCriteria mcc(idpDescriptor);
89     shibsp::SecurityPolicy& sppolicy = dynamic_cast<shibsp::SecurityPolicy&>(policy);
90     shibsp::SOAPClient soaper(sppolicy);
91
92     bool foundEndpoint = false;
93     auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);
94     saml1p::Response* response=nullptr;
95     const vector<ArtifactResolutionService*>& endpoints=idpDescriptor.getArtifactResolutionServices();
96     for (vector<ArtifactResolutionService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
97         try {
98             if (!XMLString::equals((*ep)->getBinding(),binding.get()))
99                 continue;
100             foundEndpoint = true;
101             auto_ptr_char loc((*ep)->getLocation());
102             saml1p::Request* request = saml1p::RequestBuilder::buildRequest();
103             request->setMinorVersion(idpDescriptor.hasSupport(samlconstants::SAML11_PROTOCOL_ENUM) ? 1 : 0);
104             for (vector<SAMLArtifact*>::const_iterator a = artifacts.begin(); a!=artifacts.end(); ++a) {
105                 auto_ptr_XMLCh artbuf((*a)->encode().c_str());
106                 AssertionArtifact* aa = AssertionArtifactBuilder::buildAssertionArtifact();
107                 aa->setArtifact(artbuf.get());
108                 request->getAssertionArtifacts().push_back(aa);
109             }
110
111             SAML1SOAPClient client(soaper, false);
112             client.sendSAML(request, sppolicy.getApplication().getId(), mcc, loc.get());
113             response = client.receiveSAML();
114         }
115         catch (std::exception& ex) {
116             Category::getInstance(SHIBSP_LOGCAT ".ArtifactResolver").error("exception resolving SAML 1.x artifact(s): %s", ex.what());
117             soaper.reset();
118         }
119     }
120
121     if (!foundEndpoint)
122         throw MetadataException("No compatible endpoint found in issuer's metadata.");
123     else if (!response)
124         throw BindingException("Unable to resolve artifact(s) into a SAML response.");
125     const xmltooling::QName* code = (response->getStatus() && response->getStatus()->getStatusCode()) ? response->getStatus()->getStatusCode()->getValue() : nullptr;
126     if (!code || *code != saml1p::StatusCode::SUCCESS) {
127         auto_ptr<saml1p::Response> wrapper(response);
128         BindingException ex("Identity provider returned a SAML error during artifact resolution.");
129         annotateException(&ex, &idpDescriptor, response->getStatus());  // rethrow
130     }
131
132     // The SOAP client handles policy evaluation against the SOAP and Response layer,
133     // but no security checking is done here.
134     return response;
135 }
136
137 ArtifactResponse* ArtifactResolver::resolve(
138     const SAML2Artifact& artifact,
139     const SSODescriptorType& ssoDescriptor,
140     opensaml::SecurityPolicy& policy
141     ) const
142 {
143     Category& log = Category::getInstance(SHIBSP_LOGCAT ".ArtifactResolver");
144
145     MetadataCredentialCriteria mcc(ssoDescriptor);
146     shibsp::SecurityPolicy& sppolicy = dynamic_cast<shibsp::SecurityPolicy&>(policy);
147     shibsp::SOAPClient soaper(sppolicy);
148
149     bool foundEndpoint = false;
150     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
151     ArtifactResponse* response=nullptr;
152
153     vector<ArtifactResolutionService*>::const_iterator ep_start, ep_end;
154     const vector<ArtifactResolutionService*>& endpoints = ssoDescriptor.getArtifactResolutionServices();
155     ep_start = find_if(endpoints.begin(), endpoints.end(),
156         boost::bind(&pair<bool,int>::second, boost::bind(&IndexedEndpointType::getIndex, _1)) == artifact.getEndpointIndex());
157     if (ep_start == endpoints.end()) {
158         ep_start = endpoints.begin();
159         ep_end = endpoints.end();
160     }
161     else {
162         ep_end = ep_start + 1;
163     }
164
165     const PropertySet* rp = sppolicy.getApplication().getRelyingParty(dynamic_cast<const EntityDescriptor*>(ssoDescriptor.getParent()));
166     pair<bool,bool> artifactByFilesystem = rp->getBool("artifactByFilesystem");
167
168     for (vector<ArtifactResolutionService*>::const_iterator ep = ep_start; !response && ep != ep_end; ++ep) {
169         try {
170             if (XMLString::equals((*ep)->getBinding(), binding.get())) {
171                 foundEndpoint = true;
172                 auto_ptr_char loc((*ep)->getLocation());
173                 ArtifactResolve* request = ArtifactResolveBuilder::buildArtifactResolve();
174                 Issuer* iss = IssuerBuilder::buildIssuer();
175                 request->setIssuer(iss);
176                 iss->setName(rp->getXMLString("entityID").second);
177                 auto_ptr_XMLCh artbuf(artifact.encode().c_str());
178                 Artifact* a = ArtifactBuilder::buildArtifact();
179                 a->setArtifact(artbuf.get());
180                 request->setArtifact(a);
181
182                 SAML2SOAPClient client(soaper, false);
183                 client.sendSAML(request, sppolicy.getApplication().getId(), mcc, loc.get());
184                 StatusResponseType* srt = client.receiveSAML();
185                 if (!(response = dynamic_cast<ArtifactResponse*>(srt))) {
186                     delete srt;
187                     break;
188                 }
189             }
190             else if (artifactByFilesystem.first && artifactByFilesystem.second && XMLString::equals((*ep)->getBinding(), shibspconstants::SHIB2_BINDING_FILE)) {
191                 // This implements a resolution process against the local file system for custom integration needs.
192                 // The local filesystem is presumed to be "secure" so that unsigned, unencrypted responses are acceptable.
193                 // The binding here is not SOAP, but rather REST-like, with the base location used to construct a filename
194                 // containing the artifact message handle.
195                 foundEndpoint = true;
196                 auto_ptr_char temp((*ep)->getLocation());
197                 if (temp.get()) {
198                     string loc(temp.get());
199                     if (starts_with(loc, "file://"))
200                         loc = loc.substr(7);
201                     XMLToolingConfig::getConfig().getPathResolver()->resolve(loc, PathResolver::XMLTOOLING_RUN_FILE);
202                     loc += '/' + SAMLArtifact::toHex(artifact.getMessageHandle());
203                     ifstream in(loc.c_str());
204                     if (in) {
205                         auto_ptr<XMLObject> xmlObject;
206                         try {
207                             DOMDocument* doc = (policy.getValidating() ? XMLToolingConfig::getConfig().getValidatingParser() : XMLToolingConfig::getConfig().getParser()).parse(in);
208                             XercesJanitor<DOMDocument> docjanitor(doc);
209
210                             if (log.isDebugEnabled()) {
211                                 string buf;
212                                 XMLHelper::serialize(doc->getDocumentElement(), buf);
213                                 log.debugStream() << "received XML:\n" << buf << logging::eol;
214                             }
215                             xmlObject.reset(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
216                             docjanitor.release();
217                         }
218                         catch (std::exception&) {
219                             in.close();
220                             remove(loc.c_str());
221                             throw;
222                         }
223                         in.close();
224                         remove(loc.c_str());
225                         if (response = dynamic_cast<ArtifactResponse*>(xmlObject.get())) {
226                             xmlObject.release();
227                             policy.setAuthenticated(true);
228                         }
229                         else {
230                             break;
231                         }
232                     }
233                     else {
234                         throw BindingException("Unable to open artifact response file ($1)", params(1, loc.c_str()));
235                     }
236                 }
237             }
238         }
239         catch (std::exception& ex) {
240             log.error("exception resolving SAML 2.0 artifact: %s", ex.what());
241             soaper.reset();
242         }
243     }
244
245     if (!foundEndpoint)
246         throw MetadataException("No compatible endpoint found in issuer's metadata.");
247     else if (!response)
248         throw BindingException("Unable to resolve artifact(s) into a SAML response.");
249     else if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
250            !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
251         auto_ptr<ArtifactResponse> wrapper(response);
252         BindingException ex("Identity provider returned a SAML error during artifact resolution.");
253         annotateException(&ex, &ssoDescriptor, response->getStatus());  // rethrow
254     }
255
256     // The SOAP client handles policy evaluation against the SOAP and Response layer,
257     // but no security checking is done here.
258     return response;
259 }