SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-opensaml.git] / saml / saml2 / binding / impl / SAML2SOAPEncoder.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  * SAML2SOAPEncoder.cpp
23  * 
24  * SAML 2.0 SOAP binding message encoder.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "binding/MessageEncoder.h"
30 #include "signature/ContentReference.h"
31 #include "saml2/core/Protocols.h"
32
33 #include <sstream>
34 #include <xmltooling/logging.h>
35 #include <xmltooling/io/HTTPResponse.h>
36 #include <xmltooling/util/NDC.h>
37 #include <xmltooling/signature/Signature.h>
38 #include <xmltooling/soap/SOAP.h>
39
40 using namespace opensaml::saml2p;
41 using namespace opensaml::saml2md;
42 using namespace opensaml;
43 using namespace xmlsignature;
44 using namespace soap11;
45 using namespace xmltooling::logging;
46 using namespace xmltooling;
47 using namespace std;
48
49 namespace opensaml {
50     namespace saml2p {              
51         class SAML_DLLLOCAL SAML2SOAPEncoder : public MessageEncoder
52         {
53         public:
54             SAML2SOAPEncoder() {}
55             virtual ~SAML2SOAPEncoder() {}
56
57             bool isUserAgentPresent() const {
58                 return false;
59             }
60
61             const XMLCh* getProtocolFamily() const {
62                 return samlconstants::SAML20P_NS;
63             }
64
65             long encode(
66                 GenericResponse& genericResponse,
67                 XMLObject* xmlObject,
68                 const char* destination,
69                 const EntityDescriptor* recipient=nullptr,
70                 const char* relayState=nullptr,
71                 const ArtifactGenerator* artifactGenerator=nullptr,
72                 const Credential* credential=nullptr,
73                 const XMLCh* signatureAlg=nullptr,
74                 const XMLCh* digestAlg=nullptr
75                 ) const;
76         };
77
78         MessageEncoder* SAML_DLLLOCAL SAML2SOAPEncoderFactory(const pair<const DOMElement*,const XMLCh*>& p)
79         {
80             return new SAML2SOAPEncoder();
81         }
82     };
83 };
84
85 long SAML2SOAPEncoder::encode(
86     GenericResponse& genericResponse,
87     XMLObject* xmlObject,
88     const char* destination,
89     const EntityDescriptor* recipient,
90     const char* relayState,
91     const ArtifactGenerator* artifactGenerator,
92     const Credential* credential,
93     const XMLCh* signatureAlg,
94     const XMLCh* digestAlg
95     ) const
96 {
97 #ifdef _DEBUG
98     xmltooling::NDC ndc("encode");
99 #endif
100     Category& log = Category::getInstance(SAML_LOGCAT ".MessageEncoder.SAML2SOAP");
101
102     log.debug("validating input");
103     if (xmlObject->getParent())
104         throw BindingException("Cannot encode XML content with parent.");
105
106     genericResponse.setContentType("text/xml");
107     HTTPResponse* httpResponse = dynamic_cast<HTTPResponse*>(&genericResponse);
108     if (httpResponse) {
109         httpResponse->setResponseHeader("Expires", "01-Jan-1997 12:00:00 GMT");
110         httpResponse->setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");
111         httpResponse->setResponseHeader("Pragma", "no-cache");
112     }
113
114     bool detachOnFailure = false;
115     DOMElement* rootElement = nullptr;
116
117     // Check for a naked message.
118     SignableObject* msg = dynamic_cast<SignableObject*>(xmlObject);
119     if (msg) {
120         // Wrap it in a SOAP envelope and point xmlObject at that.
121         detachOnFailure = true;
122         Envelope* env = EnvelopeBuilder::buildEnvelope();
123         Body* body = BodyBuilder::buildBody();
124         env->setBody(body);
125         body->getUnknownXMLObjects().push_back(msg);
126         xmlObject = env;
127     }
128
129     Envelope* env = dynamic_cast<Envelope*>(xmlObject);
130     if (env) {
131         if (!msg) {
132             msg = (env->getBody() && env->getBody()->hasChildren()) ?
133                 dynamic_cast<SignableObject*>(env->getBody()->getUnknownXMLObjects().front()) : nullptr;
134         }
135         try {
136             if (msg && credential) {
137                 if (msg->getSignature()) {
138                     log.debug("message already signed, skipping signature operation");
139                     rootElement = env->marshall();
140                 }
141                 else {
142                     log.debug("signing the message and marshalling the envelope");
143         
144                     // Build a Signature.
145                     Signature* sig = SignatureBuilder::buildSignature();
146                     msg->setSignature(sig);    
147                     if (signatureAlg)
148                         sig->setSignatureAlgorithm(signatureAlg);
149                     if (digestAlg) {
150                         opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
151                         if (cr)
152                             cr->setDigestAlgorithm(digestAlg);
153                     }
154             
155                     // Sign message while marshalling.
156                     vector<Signature*> sigs(1,sig);
157                     rootElement = env->marshall((DOMDocument*)nullptr,&sigs,credential);
158                 }
159             }
160             else {
161                 log.debug("marshalling the envelope");
162                 rootElement = env->marshall();
163             }
164
165             stringstream s;
166             s << *rootElement;
167             
168             if (log.isDebugEnabled())
169                 log.debug("marshalled envelope:\n%s", s.str().c_str());
170
171             log.debug("sending serialized envelope");
172             bool error = (!msg && env->getBody() && env->getBody()->hasChildren() &&
173                 dynamic_cast<Fault*>(env->getBody()->getUnknownXMLObjects().front()));
174             long ret = error ? genericResponse.sendError(s) : genericResponse.sendResponse(s);
175         
176             // Cleanup by destroying XML.
177             delete env;
178             return ret;
179         }
180         catch (XMLToolingException&) {
181             if (msg && detachOnFailure) {
182                 // A bit weird...we have to "revert" things so that the message is isolated
183                 // so the caller can free it.
184                 if (msg->getParent()) {
185                     msg->getParent()->detach();
186                     msg->detach();
187                 }
188             }
189             throw;
190         }
191     }
192
193     Fault* fault = dynamic_cast<Fault*>(xmlObject);
194     if (fault) {
195         try {
196             log.debug("building envelope and marshalling fault");
197             Envelope* env = EnvelopeBuilder::buildEnvelope();
198             Body* body = BodyBuilder::buildBody();
199             env->setBody(body);
200             body->getUnknownXMLObjects().push_back(fault);
201             rootElement = env->marshall();
202     
203             stringstream s;
204             s << *rootElement;
205             
206             if (log.isDebugEnabled())
207                 log.debug("marshalled envelope:\n%s", s.str().c_str());
208
209             log.debug("sending serialized envelope");
210             long ret = genericResponse.sendError(s);
211         
212             // Cleanup by destroying XML.
213             delete env;
214             return ret;
215         }
216         catch (XMLToolingException&) {
217             // A bit weird...we have to "revert" things so that the fault is isolated
218             // so the caller can free it.
219             if (fault->getParent()) {
220                 fault->getParent()->detach();
221                 fault->detach();
222             }
223             throw;
224         }
225     }
226
227     throw BindingException("XML content for SAML 2.0 SOAP Encoder must be a SAML 2.0 message or SOAP Fault/Envelope.");
228 }