Settable digest algorithm, enhanced prefix handling in signatures, pending xmlsec...
[shibboleth/cpp-opensaml.git] / saml / saml1 / binding / impl / SAML1SOAPEncoder.cpp
1 /*
2  *  Copyright 2001-2007 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  * SAML1SOAPEncoder.cpp
19  * 
20  * SAML 1.x SOAP binding message encoder
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "binding/HTTPResponse.h"
26 #include "binding/MessageEncoder.h"
27 #include "signature/ContentReference.h"
28 #include "saml1/core/Protocols.h"
29
30 #include <sstream>
31 #include <log4cpp/Category.hh>
32 #include <xmltooling/util/NDC.h>
33 #include <xmltooling/soap/SOAP.h>
34
35 using namespace opensaml::saml1p;
36 using namespace opensaml;
37 using namespace xmlsignature;
38 using namespace soap11;
39 using namespace xmltooling;
40 using namespace log4cpp;
41 using namespace std;
42
43 namespace opensaml {
44     namespace saml1p {              
45         class SAML_DLLLOCAL SAML1SOAPEncoder : public MessageEncoder
46         {
47         public:
48             SAML1SOAPEncoder(const DOMElement* e) {}
49             virtual ~SAML1SOAPEncoder() {}
50             
51             long encode(
52                 GenericResponse& genericResponse,
53                 XMLObject* xmlObject,
54                 const char* destination,
55                 const char* recipientID=NULL,
56                 const char* relayState=NULL,
57                 const Credential* credential=NULL,
58                 const XMLCh* signatureAlg=NULL,
59                 const XMLCh* digestAlg=NULL
60                 ) const;
61         };
62
63         MessageEncoder* SAML_DLLLOCAL SAML1SOAPEncoderFactory(const DOMElement* const & e)
64         {
65             return new SAML1SOAPEncoder(e);
66         }
67     };
68 };
69
70 long SAML1SOAPEncoder::encode(
71     GenericResponse& genericResponse,
72     XMLObject* xmlObject,
73     const char* destination,
74     const char* recipientID,
75     const char* relayState,
76     const Credential* credential,
77     const XMLCh* signatureAlg,
78     const XMLCh* digestAlg
79     ) const
80 {
81 #ifdef _DEBUG
82     xmltooling::NDC ndc("encode");
83 #endif
84     Category& log = Category::getInstance(SAML_LOGCAT".MessageEncoder.SAML1SOAP");
85
86     log.debug("validating input");
87     if (xmlObject->getParent())
88         throw BindingException("Cannot encode XML content with parent.");
89
90     genericResponse.setContentType("text/xml");
91     HTTPResponse* httpResponse = dynamic_cast<HTTPResponse*>(&genericResponse);
92     if (httpResponse) {
93         httpResponse->setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");
94         httpResponse->setResponseHeader("Pragma", "no-cache");
95     }
96
97     DOMElement* rootElement = NULL;
98     Response* response = dynamic_cast<Response*>(xmlObject);
99     if (response) {
100         try {
101             Envelope* env = EnvelopeBuilder::buildEnvelope();
102             Body* body = BodyBuilder::buildBody();
103             env->setBody(body);
104             body->getUnknownXMLObjects().push_back(response);
105             if (credential) {
106                 if (response->getSignature()) {
107                     log.debug("response already signed, skipping signature operation");
108                     rootElement = env->marshall();
109                 }
110                 else {
111                     log.debug("signing and marshalling the response");
112         
113                     // Build a Signature.
114                     Signature* sig = SignatureBuilder::buildSignature();
115                     response->setSignature(sig);    
116                     if (signatureAlg)
117                         sig->setSignatureAlgorithm(signatureAlg);
118                     if (digestAlg) {
119                         opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
120                         if (cr)
121                             cr->setDigestAlgorithm(digestAlg);
122                     }
123             
124                     // Sign response while marshalling.
125                     vector<Signature*> sigs(1,sig);
126                     rootElement = env->marshall((DOMDocument*)NULL,&sigs,credential);
127                 }
128             }
129             else {
130                 log.debug("marshalling the response");
131                 rootElement = env->marshall();
132             }
133             
134             stringstream s;
135             s << *rootElement;
136             log.debug("sending serialized response");
137             long ret = genericResponse.sendResponse(s);
138         
139             // Cleanup by destroying XML.
140             delete env;
141             return ret;
142         }
143         catch (XMLToolingException&) {
144             // A bit weird...we have to "revert" things so that the response is isolated
145             // so the caller can free it.
146             if (response->getParent()) {
147                 response->getParent()->detach();
148                 response->detach();
149             }
150             throw;
151         }
152     }
153
154     Fault* fault = dynamic_cast<Fault*>(xmlObject);
155     if (fault) {
156         try {
157             log.debug("building Envelope and marshalling Fault");
158             Envelope* env = EnvelopeBuilder::buildEnvelope();
159             Body* body = BodyBuilder::buildBody();
160             env->setBody(body);
161             body->getUnknownXMLObjects().push_back(fault);
162             rootElement = env->marshall();
163     
164             string xmlbuf;
165             XMLHelper::serialize(rootElement, xmlbuf);
166             istringstream s(xmlbuf);
167             log.debug("sending serialized fault");
168             long ret = genericResponse.sendError(s);
169         
170             // Cleanup by destroying XML.
171             delete env;
172             return ret;
173         }
174         catch (XMLToolingException&) {
175             // A bit weird...we have to "revert" things so that the fault is isolated
176             // so the caller can free it.
177             if (fault->getParent()) {
178                 fault->getParent()->detach();
179                 fault->detach();
180             }
181             throw;
182         }
183     }
184
185     Envelope* env = dynamic_cast<Envelope*>(xmlObject);
186     if (env) {
187         log.debug("marshalling envelope");
188         rootElement = env->marshall();
189
190         bool error =
191             (env->getBody() &&
192                 env->getBody()->hasChildren() &&
193                     dynamic_cast<Fault*>(env->getBody()->getUnknownXMLObjects().front()));
194
195         string xmlbuf;
196         XMLHelper::serialize(rootElement, xmlbuf);
197         istringstream s(xmlbuf);
198         log.debug("sending serialized envelope");
199         long ret = error ? genericResponse.sendError(s) : genericResponse.sendResponse(s);
200     
201         // Cleanup by destroying XML.
202         delete env;
203         return ret;
204     }
205
206     throw BindingException("XML content for SAML 1.x SOAP Encoder must be a SAML 1.x <Response> or SOAP Fault/Envelope.");
207 }