7b105c2828802bfc391f45e9813972071b5d07fb
[shibboleth/cpp-opensaml.git] / saml / saml1 / binding / impl / SAML1SOAPEncoder.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  * SAML1SOAPEncoder.cpp
23  * 
24  * SAML 1.x 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 "saml1/core/Protocols.h"
32
33 #include <sstream>
34 #include <xmltooling/logging.h>
35 #include <xmltooling/io/HTTPResponse.h>
36 #include <xmltooling/signature/Signature.h>
37 #include <xmltooling/util/NDC.h>
38 #include <xmltooling/soap/SOAP.h>
39
40 using namespace opensaml::saml1p;
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 saml1p {              
51         class SAML_DLLLOCAL SAML1SOAPEncoder : public MessageEncoder
52         {
53         public:
54             SAML1SOAPEncoder() {}
55             virtual ~SAML1SOAPEncoder() {}
56
57             bool isUserAgentPresent() const {
58                 return false;
59             }
60
61             const XMLCh* getProtocolFamily() const {
62                 return samlconstants::SAML11_PROTOCOL_ENUM;
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 SAML1SOAPEncoderFactory(const pair<const DOMElement*,const XMLCh*>& p)
79         {
80             return new SAML1SOAPEncoder();
81         }
82     };
83 };
84
85 long SAML1SOAPEncoder::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.SAML1SOAP");
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 Response.
118     Response* response = dynamic_cast<Response*>(xmlObject);
119     if (response) {
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(response);
126         xmlObject = env;
127     }
128
129     // Now check for a full Envelope (which might have just been created).
130     Envelope* env = dynamic_cast<Envelope*>(xmlObject);
131     if (env) {
132         if (!response) {
133             response = (env->getBody() && env->getBody()->hasChildren()) ?
134                 dynamic_cast<Response*>(env->getBody()->getUnknownXMLObjects().front()) : nullptr;
135         }
136         try {
137             // Now check for signing requirements.
138             if (response && credential) {
139                 if (response->getSignature()) {
140                     log.debug("response already signed, skipping signature operation");
141                     rootElement = env->marshall();
142                 }
143                 else {
144                     log.debug("signing the response and marshalling the envelope");
145         
146                     // Build a Signature.
147                     Signature* sig = SignatureBuilder::buildSignature();
148                     response->setSignature(sig);    
149                     if (signatureAlg)
150                         sig->setSignatureAlgorithm(signatureAlg);
151                     if (digestAlg) {
152                         opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
153                         if (cr)
154                             cr->setDigestAlgorithm(digestAlg);
155                     }
156             
157                     // Sign message while marshalling.
158                     vector<Signature*> sigs(1,sig);
159                     rootElement = env->marshall((DOMDocument*)nullptr,&sigs,credential);
160                 }
161             }
162             else {
163                 log.debug("marshalling the envelope");
164                 rootElement = env->marshall();
165             }
166
167             stringstream s;
168             s << *rootElement;
169             
170             if (log.isDebugEnabled())
171                 log.debug("marshalled envelope:\n%s", s.str().c_str());
172             
173             log.debug("sending serialized envelope");
174             bool error = (!response && env->getBody() && env->getBody()->hasChildren() &&
175                 dynamic_cast<Fault*>(env->getBody()->getUnknownXMLObjects().front()));
176             long ret = error ? genericResponse.sendError(s) : genericResponse.sendResponse(s);
177         
178             // Cleanup by destroying XML.
179             delete env;
180             return ret;
181         }
182         catch (XMLToolingException&) {
183             if (response && detachOnFailure) {
184                 // A bit weird...we have to "revert" things so that the response is isolated
185                 // so the caller can free it.
186                 if (response->getParent()) {
187                     response->getParent()->detach();
188                     response->detach();
189                 }
190             }
191             throw;
192         }
193     }
194
195     Fault* fault = dynamic_cast<Fault*>(xmlObject);
196     if (fault) {
197         try {
198             log.debug("building envelope and marshalling fault");
199             Envelope* env = EnvelopeBuilder::buildEnvelope();
200             Body* body = BodyBuilder::buildBody();
201             env->setBody(body);
202             body->getUnknownXMLObjects().push_back(fault);
203             rootElement = env->marshall();
204
205             stringstream s;
206             s << *rootElement;
207             
208             if (log.isDebugEnabled())
209                 log.debug("marshalled envelope:\n%s", s.str().c_str());
210             
211             log.debug("sending serialized envelope");
212             long ret = genericResponse.sendError(s);
213         
214             // Cleanup by destroying XML.
215             delete env;
216             return ret;
217         }
218         catch (XMLToolingException&) {
219             // A bit weird...we have to "revert" things so that the fault is isolated
220             // so the caller can free it.
221             if (fault->getParent()) {
222                 fault->getParent()->detach();
223                 fault->detach();
224             }
225             throw;
226         }
227     }
228     
229     throw BindingException("XML content for SAML 1.x SOAP Encoder must be a SAML 1.x <Response> or SOAP Fault/Envelope.");
230 }