View Javadoc

1   /*
2    * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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  package org.opensaml.saml2.binding.encoding;
18  
19  import java.io.OutputStreamWriter;
20  import java.io.UnsupportedEncodingException;
21  import java.io.Writer;
22  
23  import org.apache.velocity.VelocityContext;
24  import org.apache.velocity.app.VelocityEngine;
25  import org.opensaml.common.SAMLObject;
26  import org.opensaml.common.binding.SAMLMessageContext;
27  import org.opensaml.common.xml.SAMLConstants;
28  import org.opensaml.saml2.core.RequestAbstractType;
29  import org.opensaml.saml2.core.StatusResponseType;
30  import org.opensaml.ws.message.MessageContext;
31  import org.opensaml.ws.message.encoder.MessageEncodingException;
32  import org.opensaml.ws.transport.http.HTTPOutTransport;
33  import org.opensaml.ws.transport.http.HTTPTransportUtils;
34  import org.opensaml.xml.util.Base64;
35  import org.opensaml.xml.util.XMLHelper;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * SAML 2.0 HTTP Post binding message encoder.
41   */
42  public class HTTPPostEncoder extends BaseSAML2MessageEncoder {
43  
44      /** Class logger. */
45      private final Logger log = LoggerFactory.getLogger(HTTPPostEncoder.class);
46  
47      /** Velocity engine used to evaluate the template when performing POST encoding. */
48      private VelocityEngine velocityEngine;
49  
50      /** ID of the Velocity template used when performing POST encoding. */
51      private String velocityTemplateId;
52  
53      /**
54       * Constructor.
55       * 
56       * @param engine Velocity engine instance used to create POST body
57       * @param templateId ID of the template used to create POST body
58       */
59      public HTTPPostEncoder(VelocityEngine engine, String templateId) {
60          super();
61          velocityEngine = engine;
62          velocityTemplateId = templateId;
63      }
64  
65      /** {@inheritDoc} */
66      public String getBindingURI() {
67          return SAMLConstants.SAML2_POST_BINDING_URI;
68      }
69  
70      /** {@inheritDoc} */
71      public boolean providesMessageConfidentiality(MessageContext messageContext) throws MessageEncodingException {
72          return false;
73      }
74  
75      /** {@inheritDoc} */
76      public boolean providesMessageIntegrity(MessageContext messageContext) throws MessageEncodingException {
77          return false;
78      }
79  
80      /** {@inheritDoc} */
81      protected void doEncode(MessageContext messageContext) throws MessageEncodingException {
82          if (!(messageContext instanceof SAMLMessageContext)) {
83              log.error("Invalid message context type, this encoder only support SAMLMessageContext");
84              throw new MessageEncodingException(
85                      "Invalid message context type, this encoder only support SAMLMessageContext");
86          }
87  
88          if (!(messageContext.getOutboundMessageTransport() instanceof HTTPOutTransport)) {
89              log.error("Invalid outbound message transport type, this encoder only support HTTPOutTransport");
90              throw new MessageEncodingException(
91                      "Invalid outbound message transport type, this encoder only support HTTPOutTransport");
92          }
93  
94          SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
95  
96          SAMLObject outboundMessage = samlMsgCtx.getOutboundSAMLMessage();
97          if (outboundMessage == null) {
98              throw new MessageEncodingException("No outbound SAML message contained in message context");
99          }
100         String endpointURL = getEndpointURL(samlMsgCtx).buildURL();
101 
102         if (samlMsgCtx.getOutboundSAMLMessage() instanceof StatusResponseType) {
103             ((StatusResponseType) samlMsgCtx.getOutboundSAMLMessage()).setDestination(endpointURL);
104         }
105 
106         signMessage(samlMsgCtx);
107         samlMsgCtx.setOutboundMessage(outboundMessage);
108 
109         postEncode(samlMsgCtx, endpointURL);
110     }
111 
112     /**
113      * Base64 and POST encodes the outbound message and writes it to the outbound transport.
114      * 
115      * @param messageContext current message context
116      * @param endpointURL endpoint URL to which to encode message
117      * 
118      * @throws MessageEncodingException thrown if there is a problem encoding the message
119      */
120     protected void postEncode(SAMLMessageContext messageContext, String endpointURL) throws MessageEncodingException {
121         log.debug("Invoking Velocity template to create POST body");
122         try {
123             VelocityContext context = new VelocityContext();
124 
125             populateVelocityContext(context, messageContext, endpointURL);
126 
127             HTTPOutTransport outTransport = (HTTPOutTransport) messageContext.getOutboundMessageTransport();
128             HTTPTransportUtils.addNoCacheHeaders(outTransport);
129             HTTPTransportUtils.setUTF8Encoding(outTransport);
130             HTTPTransportUtils.setContentType(outTransport, "text/html");
131 
132             Writer out = new OutputStreamWriter(outTransport.getOutgoingStream(), "UTF-8");
133             velocityEngine.mergeTemplate(velocityTemplateId, "UTF-8", context, out);
134             out.flush();
135         } catch (Exception e) {
136             log.error("Error invoking Velocity template", e);
137             throw new MessageEncodingException("Error creating output document", e);
138         }
139     }
140 
141     /**
142      * Populate the Velocity context instance which will be used to render the POST body.
143      * 
144      * @param velocityContext the Velocity context instance to populate with data
145      * @param messageContext the SAML message context source of data
146      * @param endpointURL endpoint URL to which to encode message
147      * @throws MessageEncodingException thrown if there is a problem encoding the message
148      */
149     protected void populateVelocityContext(VelocityContext velocityContext, SAMLMessageContext messageContext,
150             String endpointURL) throws MessageEncodingException {
151 
152         log.debug("Encoding action url of: {}", endpointURL);
153         velocityContext.put("action", endpointURL);
154 
155         log.debug("Marshalling and Base64 encoding SAML message");
156         if (messageContext.getOutboundSAMLMessage().getDOM() == null) {
157             marshallMessage(messageContext.getOutboundSAMLMessage());
158         }
159         try {
160             String messageXML = XMLHelper.nodeToString(messageContext.getOutboundSAMLMessage().getDOM());
161             String encodedMessage = Base64.encodeBytes(messageXML.getBytes("UTF-8"), Base64.DONT_BREAK_LINES);
162             if (messageContext.getOutboundSAMLMessage() instanceof RequestAbstractType) {
163                 velocityContext.put("SAMLRequest", encodedMessage);
164             } else if (messageContext.getOutboundSAMLMessage() instanceof StatusResponseType) {
165                 velocityContext.put("SAMLResponse", encodedMessage);
166             } else {
167                 throw new MessageEncodingException(
168                         "SAML message is neither a SAML RequestAbstractType or StatusResponseType");
169             }
170         } catch (UnsupportedEncodingException e) {
171             log.error("UTF-8 encoding is not supported, this VM is not Java compliant.");
172             throw new MessageEncodingException("Unable to encode message, UTF-8 encoding is not supported");
173         }
174 
175         String relayState = messageContext.getRelayState();
176         if (checkRelayState(relayState)) {
177             log.debug("Encoding relay state of: {}", relayState);
178             velocityContext.put("RelayState", relayState);
179         }
180     }
181 }