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.util.List;
21  
22  import org.apache.velocity.VelocityContext;
23  import org.apache.velocity.app.VelocityEngine;
24  import org.opensaml.Configuration;
25  import org.opensaml.common.binding.SAMLMessageContext;
26  import org.opensaml.common.binding.artifact.AbstractSAMLArtifact;
27  import org.opensaml.common.binding.artifact.SAMLArtifactMap;
28  import org.opensaml.common.xml.SAMLConstants;
29  import org.opensaml.saml2.binding.artifact.AbstractSAML2Artifact;
30  import org.opensaml.saml2.binding.artifact.SAML2ArtifactBuilder;
31  import org.opensaml.saml2.binding.artifact.SAML2ArtifactType0004;
32  import org.opensaml.util.URLBuilder;
33  import org.opensaml.ws.message.MessageContext;
34  import org.opensaml.ws.message.encoder.MessageEncodingException;
35  import org.opensaml.ws.transport.http.HTTPOutTransport;
36  import org.opensaml.ws.transport.http.HTTPTransportUtils;
37  import org.opensaml.xml.io.MarshallingException;
38  import org.opensaml.xml.util.Pair;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * SAML 2 Artifact Binding encoder, support both HTTP GET and POST.
44   */
45  public class HTTPArtifactEncoder extends BaseSAML2MessageEncoder {
46  
47      /** Class logger. */
48      private final Logger log = LoggerFactory.getLogger(HTTPArtifactEncoder.class);
49  
50      /** Whether the POST encoding should be used, instead of GET. */
51      private boolean postEncoding;
52  
53      /** Velocity engine used to evaluate the template when performing POST encoding. */
54      private VelocityEngine velocityEngine;
55  
56      /** ID of the velocity template used when performing POST encoding. */
57      private String velocityTemplateId;
58  
59      /** SAML artifact map used to store created artifacts for later retrieval. */
60      private SAMLArtifactMap artifactMap;
61  
62      /** Default artifact type to use when encoding messages. */
63      private byte[] defaultArtifactType;
64  
65      /**
66       * Constructor.
67       * 
68       * @param engine velocity engine used to construct the POST form
69       * @param template ID of velocity template used to construct the POST form
70       * @param map artifact map used to store artifact/message bindings
71       */
72      public HTTPArtifactEncoder(VelocityEngine engine, String template, SAMLArtifactMap map) {
73          super();
74          postEncoding = false;
75          velocityEngine = engine;
76          velocityTemplateId = template;
77          artifactMap = map;
78          defaultArtifactType = SAML2ArtifactType0004.TYPE_CODE;
79      }
80  
81      /** {@inheritDoc} */
82      public String getBindingURI() {
83          return SAMLConstants.SAML2_ARTIFACT_BINDING_URI;
84      }
85  
86      /**
87       * Gets whether the encoder will encode the artifact via POST encoding.
88       * 
89       * @return true if POST encoding will be used, false if GET encoding will be used
90       */
91      public boolean isPostEncoding() {
92          return postEncoding;
93      }
94  
95      /**
96       * Sets whether the encoder will encode the artifact via POST encoding.
97       * 
98       * @param post true if POST encoding will be used, false if GET encoding will be used
99       */
100     public void setPostEncoding(boolean post) {
101         postEncoding = post;
102     }
103 
104     /** {@inheritDoc} */
105     public boolean providesMessageConfidentiality(MessageContext messageContext) throws MessageEncodingException {
106         return false;
107     }
108 
109     /** {@inheritDoc} */
110     public boolean providesMessageIntegrity(MessageContext messageContext) throws MessageEncodingException {
111         return false;
112     }
113 
114     /** {@inheritDoc} */
115     protected void doEncode(MessageContext messageContext) throws MessageEncodingException {
116         if (!(messageContext instanceof SAMLMessageContext)) {
117             log.error("Invalid message context type, this encoder only support SAMLMessageContext");
118             throw new MessageEncodingException(
119                     "Invalid message context type, this encoder only support SAMLMessageContext");
120         }
121 
122         if (!(messageContext.getOutboundMessageTransport() instanceof HTTPOutTransport)) {
123             log.error("Invalid outbound message transport type, this encoder only support HTTPOutTransport");
124             throw new MessageEncodingException(
125                     "Invalid outbound message transport type, this encoder only support HTTPOutTransport");
126         }
127 
128         SAMLMessageContext artifactContext = (SAMLMessageContext) messageContext;
129         HTTPOutTransport outTransport = (HTTPOutTransport) artifactContext.getOutboundMessageTransport();
130         outTransport.setCharacterEncoding("UTF-8");
131 
132         if (postEncoding) {
133             postEncode(artifactContext, outTransport);
134         } else {
135             getEncode(artifactContext, outTransport);
136         }
137     }
138 
139     /**
140      * Performs HTTP POST based encoding.
141      * 
142      * @param artifactContext current request context
143      * @param outTransport outbound HTTP transport
144      * 
145      * @throws MessageEncodingException thrown if there is a problem POST encoding the artifact
146      */
147     protected void postEncode(SAMLMessageContext artifactContext, HTTPOutTransport outTransport)
148             throws MessageEncodingException {
149         log.debug("Performing HTTP POST SAML 2 artifact encoding");
150 
151         log.debug("Creating velocity context");
152         VelocityContext context = new VelocityContext();
153         context.put("action", getEndpointURL(artifactContext));
154         context.put("SAMLArt", buildArtifact(artifactContext).base64Encode());
155 
156         if (checkRelayState(artifactContext.getRelayState())) {
157             context.put("RelayState", HTTPTransportUtils.urlEncode(artifactContext.getRelayState()));
158         }
159 
160         try {
161             log.debug("Invoking velocity template");
162             OutputStreamWriter outWriter = new OutputStreamWriter(outTransport.getOutgoingStream());
163             velocityEngine.mergeTemplate(velocityTemplateId, "UTF-8", context, outWriter);
164         } catch (Exception e) {
165             log.error("Error invoking velocity template to create POST form", e);
166             throw new MessageEncodingException("Error creating output document", e);
167         }
168     }
169 
170     /**
171      * Performs HTTP GET based encoding.
172      * 
173      * @param artifactContext current request context
174      * @param outTransport outbound HTTP transport
175      * 
176      * @throws MessageEncodingException thrown if there is a problem GET encoding the artifact
177      */
178     protected void getEncode(SAMLMessageContext artifactContext, HTTPOutTransport outTransport)
179             throws MessageEncodingException {
180         log.debug("Performing HTTP GET SAML 2 artifact encoding");
181 
182         URLBuilder urlBuilder = getEndpointURL(artifactContext);
183 
184         List<Pair<String, String>> params = urlBuilder.getQueryParams();
185 
186         AbstractSAMLArtifact artifact = buildArtifact(artifactContext);
187         if(artifact == null){
188             log.error("Unable to build artifact for message to relying party");
189             throw new MessageEncodingException("Unable to builder artifact for message to relying party");
190         }
191         params.add(new Pair<String, String>("SAMLart", artifact.base64Encode()));
192 
193         if (checkRelayState(artifactContext.getRelayState())) {
194             params.add(new Pair<String, String>("RelayState", artifactContext.getRelayState()));
195         }
196 
197         outTransport.sendRedirect(urlBuilder.buildURL());
198     }
199 
200     /**
201      * Builds the SAML 2 artifact for the outgoing message.
202      * 
203      * @param artifactContext current request context
204      * 
205      * @return SAML 2 artifact for outgoing message
206      * 
207      * @throws MessageEncodingException thrown if the artifact can not be created
208      */
209     protected AbstractSAML2Artifact buildArtifact(SAMLMessageContext artifactContext) throws MessageEncodingException {
210 
211         SAML2ArtifactBuilder artifactBuilder;
212         if (artifactContext.getOutboundMessageArtifactType() != null) {
213             artifactBuilder = Configuration.getSAML2ArtifactBuilderFactory().getArtifactBuilder(
214                     artifactContext.getOutboundMessageArtifactType());
215         } else {
216             artifactBuilder = Configuration.getSAML2ArtifactBuilderFactory().getArtifactBuilder(defaultArtifactType);
217             artifactContext.setOutboundMessageArtifactType(defaultArtifactType);
218         }
219 
220         AbstractSAML2Artifact artifact = artifactBuilder.buildArtifact(artifactContext);
221         if(artifact == null){
222             log.error("Unable to build artifact for message to relying party");
223             throw new MessageEncodingException("Unable to builder artifact for message to relying party");
224         }
225         String encodedArtifact = artifact.base64Encode();
226         try {
227             artifactMap.put(encodedArtifact, artifactContext.getInboundMessageIssuer(), artifactContext
228                     .getOutboundMessageIssuer(), artifactContext.getOutboundSAMLMessage());
229         } catch (MarshallingException e) {
230             log.error("Unable to marshall assertion to be represented as an artifact", e);
231             throw new MessageEncodingException("Unable to marshall assertion to be represented as an artifact", e);
232         }
233 
234         return artifact;
235     }
236 }