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 * SAML1POSTEncoder.cpp
23 *
24 * SAML 1.x POST binding/profile 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 <fstream>
34 #include <sstream>
35 #include <xercesc/util/Base64.hpp>
36 #include <xsec/framework/XSECDefs.hpp>
37 #include <xmltooling/io/HTTPResponse.h>
38 #include <xmltooling/logging.h>
39 #include <xmltooling/XMLToolingConfig.h>
40 #include <xmltooling/signature/Signature.h>
41 #include <xmltooling/util/NDC.h>
42 #include <xmltooling/util/PathResolver.h>
43 #include <xmltooling/util/TemplateEngine.h>
44
45 using namespace opensaml::saml1p;
46 using namespace opensaml::saml2md;
47 using namespace opensaml;
48 using namespace xmlsignature;
49 using namespace xmltooling::logging;
50 using namespace xmltooling;
51 using namespace std;
52
53 namespace opensaml {
54 namespace saml1p {
55 class SAML_DLLLOCAL SAML1POSTEncoder : public MessageEncoder
56 {
57 public:
58 SAML1POSTEncoder(const DOMElement* e);
~SAML1POSTEncoder()59 virtual ~SAML1POSTEncoder() {}
60
getProtocolFamily() const61 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 protected:
78 /** Pathname of HTML template for transmission of message via POST. */
79 string m_template;
80 };
81
SAML1POSTEncoderFactory(const DOMElement * const & e,bool)82 MessageEncoder* SAML_DLLLOCAL SAML1POSTEncoderFactory(const DOMElement* const & e, bool)
83 {
84 return new SAML1POSTEncoder(e);
85 }
86 };
87 };
88
SAML1POSTEncoder(const DOMElement * e)89 SAML1POSTEncoder::SAML1POSTEncoder(const DOMElement* e)
90 {
91 // Fishy alert: we ignore the namespace and look for a matching DOM Attr node by name only.
92 // Can't use DOM 1 calls, so we have to walk the attribute list by hand.
93
94 static const XMLCh _template[] = UNICODE_LITERAL_8(t, e, m, p, l, a, t, e);
95
96 const DOMNamedNodeMap* attributes = e ? e->getAttributes() : nullptr;
97 XMLSize_t size = attributes ? attributes->getLength() : 0;
98 for (XMLSize_t i = 0; i < size; ++i) {
99 const DOMNode* attr = attributes->item(i);
100 if (XMLString::equals(attr->getLocalName(), _template)) {
101 auto_ptr_char val(attr->getNodeValue());
102 if (val.get())
103 m_template = val.get();
104 }
105 }
106
107 if (m_template.empty())
108 m_template = "bindingTemplate.html";
109 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_template, PathResolver::XMLTOOLING_CFG_FILE);
110 }
111
encode(GenericResponse & genericResponse,XMLObject * xmlObject,const char * destination,const EntityDescriptor * recipient,const char * relayState,const ArtifactGenerator * artifactGenerator,const Credential * credential,const XMLCh * signatureAlg,const XMLCh * digestAlg) const112 long SAML1POSTEncoder::encode(
113 GenericResponse& genericResponse,
114 XMLObject* xmlObject,
115 const char* destination,
116 const EntityDescriptor* recipient,
117 const char* relayState,
118 const ArtifactGenerator* artifactGenerator,
119 const Credential* credential,
120 const XMLCh* signatureAlg,
121 const XMLCh* digestAlg
122 ) const
123 {
124 #ifdef _DEBUG
125 xmltooling::NDC ndc("encode");
126 #endif
127 Category& log = Category::getInstance(SAML_LOGCAT ".MessageEncoder.SAML1POST");
128 log.debug("validating input");
129
130 TemplateEngine* engine = XMLToolingConfig::getConfig().getTemplateEngine();
131 if (!engine || !destination)
132 throw BindingException("Encoding response using POST requires a TemplateEngine instance and a destination.");
133 HTTPResponse::sanitizeURL(destination);
134 if (xmlObject->getParent())
135 throw BindingException("Cannot encode XML content with parent.");
136 Response* response = dynamic_cast<Response*>(xmlObject);
137 if (!response)
138 throw BindingException("XML content for SAML 1.x POST Encoder must be a SAML 1.x <Response>.");
139 if (!relayState)
140 throw BindingException("SAML 1.x POST Encoder requires relay state (TARGET) value.");
141
142 DOMElement* rootElement = nullptr;
143 if (credential) {
144 // Signature based on native XML signing.
145 if (response->getSignature()) {
146 log.debug("response already signed, skipping signature operation");
147 }
148 else {
149 log.debug("signing and marshalling the response");
150
151 // Build a Signature.
152 Signature* sig = SignatureBuilder::buildSignature();
153 response->setSignature(sig);
154 if (signatureAlg)
155 sig->setSignatureAlgorithm(signatureAlg);
156 if (digestAlg) {
157 opensaml::ContentReference* cr = dynamic_cast<opensaml::ContentReference*>(sig->getContentReference());
158 if (cr)
159 cr->setDigestAlgorithm(digestAlg);
160 }
161
162 // Sign response while marshalling.
163 vector<Signature*> sigs(1,sig);
164 rootElement = response->marshall((DOMDocument*)nullptr,&sigs,credential);
165 }
166 }
167 else {
168 log.debug("marshalling the response");
169 rootElement = response->marshall();
170 }
171
172 // Push message into template.
173 TemplateEngine::TemplateParameters pmap;
174 string& xmlbuf = pmap.m_map["SAMLResponse"];
175 XMLHelper::serialize(rootElement, xmlbuf);
176 log.debug("marshalled response:\n%s", xmlbuf.c_str());
177
178 // Replace with base-64 encoded version.
179 XMLSize_t len=0;
180 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(xmlbuf.data()),xmlbuf.size(),&len);
181 if (out) {
182 xmlbuf.erase();
183 xmlbuf.append(reinterpret_cast<char*>(out),len);
184 XMLString::release((char**)&out);
185 }
186 else {
187 throw BindingException("Base64 encoding of XML failed.");
188 }
189
190 // Fill in the rest of the data and send to the client.
191 log.debug("message encoded, sending HTML form template to client");
192 ifstream infile(m_template.c_str());
193 if (!infile)
194 throw BindingException("Failed to open HTML template for POST response ($1).", params(1,m_template.c_str()));
195 pmap.m_map["action"] = destination;
196 pmap.m_map["TARGET"] = relayState;
197 stringstream s;
198 engine->run(infile, s, pmap);
199 genericResponse.setContentType("text/html");
200 HTTPResponse* httpResponse = dynamic_cast<HTTPResponse*>(&genericResponse);
201 if (httpResponse) {
202 httpResponse->setResponseHeader("Expires", "01-Jan-1997 12:00:00 GMT");
203 httpResponse->setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");
204 httpResponse->setResponseHeader("Pragma", "no-cache");
205 }
206 long ret = genericResponse.sendResponse(s);
207
208 // Cleanup by destroying XML.
209 delete xmlObject;
210 return ret;
211 }
212