View Javadoc

1   /*
2    * Copyright 2007 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.saml1.binding.decoding;
18  
19  import java.util.List;
20  
21  import javax.xml.namespace.QName;
22  
23  import org.opensaml.common.SAMLObject;
24  import org.opensaml.common.binding.SAMLMessageContext;
25  import org.opensaml.common.binding.artifact.SAMLArtifactMap;
26  import org.opensaml.common.binding.artifact.SAMLArtifactMap.SAMLArtifactMapEntry;
27  import org.opensaml.common.binding.decoding.BaseSAMLMessageDecoder;
28  import org.opensaml.common.xml.SAMLConstants;
29  import org.opensaml.saml1.core.Assertion;
30  import org.opensaml.saml1.core.AssertionArtifact;
31  import org.opensaml.saml1.core.AttributeQuery;
32  import org.opensaml.saml1.core.AuthorizationDecisionQuery;
33  import org.opensaml.saml1.core.Request;
34  import org.opensaml.saml1.core.RequestAbstractType;
35  import org.opensaml.saml1.core.Response;
36  import org.opensaml.saml1.core.ResponseAbstractType;
37  import org.opensaml.saml2.metadata.EntityDescriptor;
38  import org.opensaml.saml2.metadata.RoleDescriptor;
39  import org.opensaml.saml2.metadata.provider.MetadataProvider;
40  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
41  import org.opensaml.ws.message.MessageContext;
42  import org.opensaml.ws.message.decoder.MessageDecodingException;
43  import org.opensaml.xml.parse.ParserPool;
44  import org.opensaml.xml.security.SecurityException;
45  import org.opensaml.xml.util.DatatypeHelper;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * Base class for SAML 1 message decoders.
51   */
52  public abstract class BaseSAML1MessageDecoder extends BaseSAMLMessageDecoder {
53  
54      /** Class logger. */
55      private final Logger log = LoggerFactory.getLogger(BaseSAML1MessageDecoder.class);
56  
57      /** Map used to map artifacts to SAML. */
58      private SAMLArtifactMap artifactMap;
59  
60      /** Whether to use the resource of an attribute query as the relying party entity ID. */
61      private boolean useQueryResourceAsEntityId;
62  
63      /** Constructor. */
64      public BaseSAML1MessageDecoder() {
65          super();
66          useQueryResourceAsEntityId = true;
67      }
68      
69      /**
70       * Constructor.
71       * 
72       * @param pool parser pool used to deserialize messages
73       */
74      public BaseSAML1MessageDecoder(ParserPool pool) {
75          super(pool);
76          useQueryResourceAsEntityId = true;
77      }
78      
79      /**
80       * Constructor.
81       * 
82       * @param map used to map artifacts to SAML
83       * 
84       * @deprecated
85       */
86      public BaseSAML1MessageDecoder(SAMLArtifactMap map) {
87          super();
88          artifactMap = map;
89          useQueryResourceAsEntityId = true;
90      }
91  
92      /**
93       * Constructor.
94       * 
95       * @param map used to map artifacts to SAML
96       * @param pool parser pool used to deserialize messages
97       * 
98       * @deprecated
99       */
100     public BaseSAML1MessageDecoder(SAMLArtifactMap map, ParserPool pool) {
101         super(pool);
102         artifactMap = map;
103         useQueryResourceAsEntityId = true;
104     }
105     
106     /** {@inheritDoc} */
107     public void decode(MessageContext messageContext) throws MessageDecodingException, SecurityException {
108         super.decode(messageContext);
109         
110         SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
111         if (samlMsgCtx.getInboundSAMLMessage() instanceof ResponseAbstractType) {
112             checkEndpointURI(samlMsgCtx);
113         }
114     }
115 
116     /**
117      * Gets the artifact map used to retrieve SAML information from an artifact.
118      * 
119      * @return artifact map used to retrieve SAML information from an artifact
120      */
121     public SAMLArtifactMap getArtifactMap() {
122         return artifactMap;
123     }
124 
125     /**
126      * Gets whether to use the Resource attribute of some SAML 1 queries as the entity ID of the inbound message issuer.
127      * 
128      * @return whether to use the Resource attribute of some SAML 1 queries as the entity ID of the inbound message
129      *         issuer
130      */
131     public boolean getUseQueryResourceAsEntityId() {
132         return useQueryResourceAsEntityId;
133     }
134 
135     /**
136      * Sets whether to use the Resource attribute of some SAML 1 queries as the entity ID of the inbound message issuer.
137      * 
138      * @param useResource whether to use the Resource attribute of some SAML 1 queries as the entity ID of the inbound
139      *            message issuer
140      */
141     public void setUseQueryResourceAsEntityId(boolean useResource) {
142         useQueryResourceAsEntityId = useResource;
143     }
144 
145     /**
146      * Populates the message context with the message ID, issue instant, and issuer as well as the peer's entity
147      * descriptor if a metadata provider is present in the message context and the peer's role descriptor if its entity
148      * descriptor was retrieved and the message context has a populated peer role name.
149      * 
150      * @param messageContext message context to populate
151      * 
152      * @throws MessageDecodingException thrown if there is a problem populating the message context
153      */
154     protected void populateMessageContext(SAMLMessageContext messageContext) throws MessageDecodingException {
155         populateMessageIdIssueInstantIssuer(messageContext);
156         populateRelyingPartyMetadata(messageContext);
157     }
158 
159     /**
160      * Extracts the message ID, issue instant, and issuer from the incoming SAML message and populates the message
161      * context with it.
162      * 
163      * @param messageContext current message context
164      * 
165      * @throws MessageDecodingException thrown if there is a problem populating the message context
166      */
167     protected void populateMessageIdIssueInstantIssuer(SAMLMessageContext messageContext)
168             throws MessageDecodingException {
169         SAMLObject samlMsg = messageContext.getInboundSAMLMessage();
170         if (samlMsg == null) {
171             return;
172         }
173 
174         if (samlMsg instanceof RequestAbstractType) {
175             log.debug("Extracting ID, issuer and issue instant from request");
176             extractRequestInfo(messageContext, (RequestAbstractType) samlMsg);
177         } else if (samlMsg instanceof Response) {
178             log.debug("Extracting ID, issuer and issue instant from response");
179             extractResponseInfo(messageContext, (Response) samlMsg);
180         } else {
181             throw new MessageDecodingException("SAML 1.x message was not a request or a response");
182         }
183     }
184 
185     /**
186      * Extract information from a SAML RequestAbstractType message.
187      * 
188      * @param messageContext current message context
189      * @param abstractRequest the SAML message to process
190      */
191     protected void extractRequestInfo(SAMLMessageContext messageContext, RequestAbstractType abstractRequest) {
192         messageContext.setInboundSAMLMessageId(abstractRequest.getID());
193         messageContext.setInboundSAMLMessageIssueInstant(abstractRequest.getIssueInstant());
194 
195         if (abstractRequest instanceof Request) {
196             Request request = (Request) abstractRequest;
197             if (request.getAttributeQuery() != null) {
198                 extractAttributeQueryInfo(messageContext, request.getAttributeQuery());
199             }
200 
201             if (request.getAuthorizationDecisionQuery() != null) {
202                 extractAuthorizationDecisionQueryInfo(messageContext, request.getAuthorizationDecisionQuery());
203             }
204 
205             if (request.getAssertionArtifacts() != null) {
206                 extractAssertionArtifactInfo(messageContext, request.getAssertionArtifacts());
207             }
208         }
209     }
210 
211     /**
212      * Extract the issuer, and populate message context, from the Resource attribute of the Attribute query if
213      * {@link #useQueryResourceAsEntityId} is true.
214      * 
215      * @param messageContext current message context
216      * @param query query to extract resource name from
217      */
218     protected void extractAttributeQueryInfo(SAMLMessageContext messageContext, AttributeQuery query) {
219         if (useQueryResourceAsEntityId) {
220             log.debug("Attempting to extract issuer from SAML 1 AttributeQuery Resource attribute");
221             String resource = DatatypeHelper.safeTrimOrNullString(query.getResource());
222 
223             if (resource != null) {
224                 messageContext.setInboundMessageIssuer(resource);
225                 log.debug("Extracted issuer from SAML 1.x AttributeQuery: {}", resource);
226             }
227         }
228     }
229 
230     /**
231      * Extract the issuer, and populate message context, from the Resource attribute of the AuthorizationDecisionQuery
232      * query if {@link #useQueryResourceAsEntityId} is true.
233      * 
234      * @param messageContext current message context
235      * @param query query to extract resource name from
236      */
237     protected void extractAuthorizationDecisionQueryInfo(SAMLMessageContext messageContext,
238             AuthorizationDecisionQuery query) {
239         if (useQueryResourceAsEntityId) {
240             log.debug("Attempting to extract issuer from SAML 1 AuthorizationDecisionQuery Resource attribute");
241             String resource = DatatypeHelper.safeTrimOrNullString(query.getResource());
242 
243             if (resource != null) {
244                 messageContext.setInboundMessageIssuer(resource);
245                 log.debug("Extracted issuer from SAML 1.x AuthorizationDecisionQuery: {}", resource);
246             }
247         }
248     }
249 
250     /**
251      * Extract the issuer, and populate message context, as the relying party corresponding to the first
252      * AssertionArtifact in the message.
253      * 
254      * @param messageContext current message context
255      * @param artifacts AssertionArtifacts in the request
256      */
257     protected void extractAssertionArtifactInfo(SAMLMessageContext messageContext, List<AssertionArtifact> artifacts) {
258         if (artifacts.size() == 0) {
259             return;
260         }
261 
262         log.debug("Attempting to extract issuer based on first AssertionArtifact in request");
263         AssertionArtifact artifact = artifacts.get(0);
264         SAMLArtifactMapEntry artifactEntry = artifactMap.get(artifact.getAssertionArtifact());
265         messageContext.setInboundMessageIssuer(artifactEntry.getRelyingPartyId());
266 
267         log.debug("Extracted issuer from SAML 1.x AssertionArtifact: {}", messageContext.getInboundMessageIssuer());
268     }
269 
270     /**
271      * Extract information from a SAML StatusResponse message.
272      * 
273      * @param messageContext current message context
274      * @param response the SAML message to process
275      * 
276      * @throws MessageDecodingException thrown if the assertions within the response contain differening issuer IDs
277      */
278     protected void extractResponseInfo(SAMLMessageContext messageContext, Response response)
279             throws MessageDecodingException {
280 
281         messageContext.setInboundSAMLMessageId(response.getID());
282         messageContext.setInboundSAMLMessageIssueInstant(response.getIssueInstant());
283 
284         String issuer = null;
285         List<Assertion> assertions = ((Response) response).getAssertions();
286         if (assertions != null && assertions.size() > 0) {
287             log.info("Attempting to extract issuer from enclosed SAML 1.x Assertion(s)");
288             for (Assertion assertion : assertions) {
289                 if (assertion != null && assertion.getIssuer() != null) {
290                     if (issuer != null && !issuer.equals(assertion.getIssuer())) {
291                         throw new MessageDecodingException("SAML 1.x assertions, within response " + response.getID()
292                                 + " contain different issuer IDs");
293                     }
294                     issuer = assertion.getIssuer();
295                 }
296             }
297         }
298 
299         if (issuer == null) {
300             log.warn("Issuer could not be extracted from standard SAML 1.x response message");
301         }
302 
303         messageContext.setInboundMessageIssuer(issuer);
304     }
305 
306     /**
307      * Populates the peer's entity metadata if a metadata provide is present in the message context. Populates the
308      * peer's role descriptor if the entity metadata was available and the role name is present in the message context.
309      * 
310      * @param messageContext current message context
311      * 
312      * @throws MessageDecodingException thrown if there is a problem populating the message context
313      */
314     protected void populateRelyingPartyMetadata(SAMLMessageContext messageContext) throws MessageDecodingException {
315         MetadataProvider metadataProvider = messageContext.getMetadataProvider();
316         try {
317             if (metadataProvider != null) {
318                 EntityDescriptor relyingPartyMD = metadataProvider.getEntityDescriptor(messageContext
319                         .getInboundMessageIssuer());
320                 messageContext.setPeerEntityMetadata(relyingPartyMD);
321 
322                 QName relyingPartyRole = messageContext.getPeerEntityRole();
323                 if (relyingPartyMD != null && relyingPartyRole != null) {
324                     List<RoleDescriptor> roles = relyingPartyMD.getRoleDescriptors(relyingPartyRole,
325                             SAMLConstants.SAML11P_NS);
326                     if (roles != null && roles.size() > 0) {
327                         messageContext.setPeerEntityRoleMetadata(roles.get(0));
328                     }
329                 }
330             }
331         } catch (MetadataProviderException e) {
332             log.error("Error retrieving metadata for relying party " + messageContext.getInboundMessageIssuer(), e);
333             throw new MessageDecodingException("Error retrieving metadata for relying party "
334                     + messageContext.getInboundMessageIssuer(), e);
335         }
336     }
337     
338     /**
339      * {@inheritDoc} 
340      * 
341      * <p>This SAML 1-specific implementation extracts the value of the ResponseAbstractType 
342      * protocol message Recipient attribute.</p>
343      * 
344      * */
345     protected String getIntendedDestinationEndpointURI(SAMLMessageContext samlMsgCtx) throws MessageDecodingException {
346         SAMLObject samlMessage = samlMsgCtx.getInboundSAMLMessage();
347         String messageDestination = null;
348         if (samlMessage instanceof ResponseAbstractType) {
349             ResponseAbstractType response = (ResponseAbstractType) samlMessage;
350             messageDestination = DatatypeHelper.safeTrimOrNullString(response.getRecipient());
351         } else if (samlMessage instanceof RequestAbstractType) {
352             // don't treat as an error, just return null
353             return null;
354         } else {
355             log.error("Invalid SAML message type encountered: {}", samlMessage.getElementQName().toString());
356             throw new MessageDecodingException("Invalid SAML message type encountered");
357         }
358         return messageDestination;
359     }
360     
361 }