SSPCPP-437 and SSPCPP-438
[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     for (vector<ArtifactResolutionService*>::const_iterator ep = ep_start; !response && ep != ep_end; ++ep) {
166         try {
167             if (XMLString::equals((*ep)->getBinding(), binding.get())) {
168                 foundEndpoint = true;
169                 auto_ptr_char loc((*ep)->getLocation());
170                 ArtifactResolve* request = ArtifactResolveBuilder::buildArtifactResolve();
171                 Issuer* iss = IssuerBuilder::buildIssuer();
172                 request->setIssuer(iss);
173                 iss->setName(sppolicy.getApplication().getRelyingParty(dynamic_cast<EntityDescriptor*>(ssoDescriptor.getParent()))->getXMLString("entityID").second);
174                 auto_ptr_XMLCh artbuf(artifact.encode().c_str());
175                 Artifact* a = ArtifactBuilder::buildArtifact();
176                 a->setArtifact(artbuf.get());
177                 request->setArtifact(a);
178
179                 SAML2SOAPClient client(soaper, false);
180                 client.sendSAML(request, sppolicy.getApplication().getId(), mcc, loc.get());
181                 StatusResponseType* srt = client.receiveSAML();
182                 if (!(response = dynamic_cast<ArtifactResponse*>(srt))) {
183                     delete srt;
184                     break;
185                 }
186             }
187             else if (XMLString::equals((*ep)->getBinding(), shibspconstants::SHIB2_BINDING_FILE)) {
188                 // This implements a resolution process against the local file system for custom integration needs.
189                 // The local filesystem is presumed to be "secure" so that unsigned, unencrypted responses are acceptable.
190                 // The binding here is not SOAP, but rather REST-like, with the base location used to construct a filename
191                 // containing the artifact message handle.
192                 foundEndpoint = true;
193                 auto_ptr_char temp((*ep)->getLocation());
194                 if (temp.get()) {
195                     string loc(temp.get());
196                     if (starts_with(loc, "file://"))
197                         loc = loc.substr(7);
198                     XMLToolingConfig::getConfig().getPathResolver()->resolve(loc, PathResolver::XMLTOOLING_RUN_FILE);
199                     loc += '/' + SAMLArtifact::toHex(artifact.getMessageHandle());
200                     ifstream in(loc);
201                     if (in) {
202                         auto_ptr<XMLObject> xmlObject;
203                         try {
204                             DOMDocument* doc = (policy.getValidating() ? XMLToolingConfig::getConfig().getValidatingParser() : XMLToolingConfig::getConfig().getParser()).parse(in);
205                             XercesJanitor<DOMDocument> docjanitor(doc);
206
207                             if (log.isDebugEnabled()) {
208 #ifdef XMLTOOLING_LOG4SHIB
209                                 log.debugStream() << "received XML:\n" << *(doc->getDocumentElement()) << logging::eol;
210 #else
211                                 string buf;
212                                 XMLHelper::serialize(doc->getDocumentElement(), buf);
213                                 log.debugStream() << "received XML:\n" << buf << logging::eol;
214 #endif
215     }
216                             xmlObject.reset(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
217                             docjanitor.release();
218                         }
219                         catch (std::exception&) {
220                             in.close();
221                             remove(loc.c_str());
222                             throw;
223                         }
224                         in.close();
225                         remove(loc.c_str());
226                         if (response = dynamic_cast<ArtifactResponse*>(xmlObject.get())) {
227                             xmlObject.release();
228                             policy.setAuthenticated(true);
229                         }
230                         else {
231                             break;
232                         }
233                     }
234                     else {
235                         throw BindingException("Unable to open artifact response file ($1)", params(1, loc.c_str()));
236                     }
237                 }
238             }
239         }
240         catch (std::exception& ex) {
241             log.error("exception resolving SAML 2.0 artifact: %s", ex.what());
242             soaper.reset();
243         }
244     }
245
246     if (!foundEndpoint)
247         throw MetadataException("No compatible endpoint found in issuer's metadata.");
248     else if (!response)
249         throw BindingException("Unable to resolve artifact(s) into a SAML response.");
250     else if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
251            !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
252         auto_ptr<ArtifactResponse> wrapper(response);
253         BindingException ex("Identity provider returned a SAML error during artifact resolution.");
254         annotateException(&ex, &ssoDescriptor, response->getStatus());  // rethrow
255     }
256
257     // The SOAP client handles policy evaluation against the SOAP and Response layer,
258     // but no security checking is done here.
259     return response;
260 }