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.saml2.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.decoding.BaseSAMLMessageDecoder;
26  import org.opensaml.common.xml.SAMLConstants;
27  import org.opensaml.saml2.core.Assertion;
28  import org.opensaml.saml2.core.Issuer;
29  import org.opensaml.saml2.core.NameIDType;
30  import org.opensaml.saml2.core.RequestAbstractType;
31  import org.opensaml.saml2.core.Response;
32  import org.opensaml.saml2.core.StatusResponseType;
33  import org.opensaml.saml2.metadata.EntityDescriptor;
34  import org.opensaml.saml2.metadata.RoleDescriptor;
35  import org.opensaml.saml2.metadata.provider.MetadataProvider;
36  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
37  import org.opensaml.ws.message.MessageContext;
38  import org.opensaml.ws.message.decoder.MessageDecodingException;
39  import org.opensaml.xml.parse.ParserPool;
40  import org.opensaml.xml.security.SecurityException;
41  import org.opensaml.xml.util.DatatypeHelper;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  /**
46   * Base class for SAML 2 message decoders.
47   */
48  public abstract class BaseSAML2MessageDecoder extends BaseSAMLMessageDecoder {
49  
50      /** Class logger. */
51      private final Logger log = LoggerFactory.getLogger(BaseSAML2MessageDecoder.class);
52      
53      /** Constructor. */
54      public BaseSAML2MessageDecoder() {
55          super();
56      }
57  
58      /**
59       * Constructor.
60       * 
61       * @param pool parser pool used to deserialize messages
62       */
63      public BaseSAML2MessageDecoder(ParserPool pool) {
64          super(pool);
65      }
66  
67      /** {@inheritDoc} */
68      public void decode(MessageContext messageContext) throws MessageDecodingException, SecurityException {
69          super.decode(messageContext);
70          
71          checkEndpointURI((SAMLMessageContext) messageContext);
72      }
73  
74      /**
75       * Populates the message context with the message ID, issue instant, and issuer as well as the peer's entity
76       * descriptor if a metadata provider is present in the message context and the peer's role descriptor if its entity
77       * descriptor was retrieved and the message context has a populated peer role name.
78       * 
79       * @param messageContext message context to populate
80       * 
81       * @throws MessageDecodingException thrown if there is a problem populating the message context
82       */
83      protected void populateMessageContext(SAMLMessageContext messageContext) throws MessageDecodingException {
84          populateMessageIdIssueInstantIssuer(messageContext);
85          populateRelyingPartyMetadata(messageContext);
86      }
87      
88      /**
89       * Extracts the message ID, issue instant, and issuer from the incoming SAML message and populates the message
90       * context with it.
91       * 
92       * @param messageContext current message context
93       * 
94       * @throws MessageDecodingException thrown if there is a problem populating the message context
95       */
96      protected void populateMessageIdIssueInstantIssuer(SAMLMessageContext messageContext)
97              throws MessageDecodingException {
98          if (!(messageContext instanceof SAMLMessageContext)) {
99              log.debug("Invalid message context type, this policy rule only support SAMLMessageContext");
100             return;
101         }
102         SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
103 
104         SAMLObject samlMsg = samlMsgCtx.getInboundSAMLMessage();
105         if (samlMsg == null) {
106             log.error("Message context did not contain inbound SAML message");
107             throw new MessageDecodingException("Message context did not contain inbound SAML message");
108         }
109 
110         if (samlMsg instanceof RequestAbstractType) {
111             log.debug("Extracting ID, issuer and issue instant from request");
112             extractRequestInfo(samlMsgCtx, (RequestAbstractType) samlMsg);
113         } else if (samlMsg instanceof StatusResponseType) {
114             log.debug("Extracting ID, issuer and issue instant from status response");
115             extractResponseInfo(samlMsgCtx, (StatusResponseType) samlMsg);
116         } else {
117             throw new MessageDecodingException("SAML 2 message was not a request or a response");
118         }
119 
120         if (samlMsgCtx.getInboundMessageIssuer() == null) {
121             log.warn("Issuer could not be extracted from SAML 2 message");
122         }
123 
124     }
125 
126     /**
127      * Extract information from a SAML StatusResponse message.
128      * 
129      * @param messageContext current message context
130      * @param statusResponse the SAML message to process
131      * 
132      * @throws MessageDecodingException thrown if the response issuer has a format other than {@link NameIDType#ENTITY}
133      *             or, if the response does not contain an issuer, if the contained assertions contain issuers that are
134      *             not of {@link NameIDType#ENTITY} format or if the assertions contain different issuers
135      */
136     protected void extractResponseInfo(SAMLMessageContext messageContext, StatusResponseType statusResponse)
137             throws MessageDecodingException {
138 
139         messageContext.setInboundSAMLMessageId(statusResponse.getID());
140         messageContext.setInboundSAMLMessageIssueInstant(statusResponse.getIssueInstant());
141 
142         // If response doesn't have an issuer, look at the first
143         // enclosed assertion
144         String messageIssuer = null;
145         if (statusResponse.getIssuer() != null) {
146             messageIssuer = extractEntityId(statusResponse.getIssuer());
147         } else if (statusResponse instanceof Response) {
148             List<Assertion> assertions = ((Response) statusResponse).getAssertions();
149             if (assertions != null && assertions.size() > 0) {
150                 log.info("Status response message had no issuer, attempting to extract issuer from enclosed Assertion(s)");
151                 String assertionIssuer;
152                 for (Assertion assertion : assertions) {
153                     if (assertion != null && assertion.getIssuer() != null) {
154                         assertionIssuer = extractEntityId(assertion.getIssuer());
155                         if (messageIssuer != null && !messageIssuer.equals(assertionIssuer)) {
156                             throw new MessageDecodingException("SAML 2 assertions, within response "
157                                     + statusResponse.getID() + " contain different issuer IDs");
158                         }
159                         messageIssuer = assertionIssuer;
160                     }
161                 }
162             }
163         }
164 
165         messageContext.setInboundMessageIssuer(messageIssuer);
166     }
167 
168     /**
169      * Extract information from a SAML RequestAbstractType message.
170      * 
171      * @param messageContext current message context
172      * @param request the SAML message to process
173      * 
174      * @throws MessageDecodingException thrown if the request issuer has a format other than {@link NameIDType#ENTITY}
175      */
176     protected void extractRequestInfo(SAMLMessageContext messageContext, RequestAbstractType request)
177             throws MessageDecodingException {
178         messageContext.setInboundSAMLMessageId(request.getID());
179         messageContext.setInboundSAMLMessageIssueInstant(request.getIssueInstant());
180         messageContext.setInboundMessageIssuer(extractEntityId(request.getIssuer()));
181     }
182 
183     /**
184      * Extracts the entity ID from the SAML 2 Issuer.
185      * 
186      * @param issuer issuer to extract the entityID from
187      * 
188      * @return entity ID of the issuer
189      * 
190      * @throws MessageDecodingException thrown if the given issuer has a format other than {@link NameIDType#ENTITY}
191      */
192     protected String extractEntityId(Issuer issuer) throws MessageDecodingException {
193         if (issuer != null) {
194             if (issuer.getFormat() == null || issuer.getFormat().equals(NameIDType.ENTITY)) {
195                 return issuer.getValue();
196             } else {
197                 throw new MessageDecodingException("SAML 2 Issuer is not of ENTITY format type");
198             }
199         }
200 
201         return null;
202     }
203     
204     
205     /**
206      * Populates the peer's entity metadata if a metadata provide is present in the message context. Populates the
207      * peer's role descriptor if the entity metadata was available and the role name is present in the message context.
208      * 
209      * @param messageContext current message context
210      * 
211      * @throws MessageDecodingException thrown if there is a problem populating the message context
212      */
213     protected void populateRelyingPartyMetadata(SAMLMessageContext messageContext) throws MessageDecodingException {
214         MetadataProvider metadataProvider = messageContext.getMetadataProvider();
215         try {
216             if (metadataProvider != null) {
217                 EntityDescriptor relyingPartyMD = metadataProvider.getEntityDescriptor(messageContext
218                         .getInboundMessageIssuer());
219                 messageContext.setPeerEntityMetadata(relyingPartyMD);
220 
221                 QName relyingPartyRole = messageContext.getPeerEntityRole();
222                 if (relyingPartyMD != null && relyingPartyRole != null) {
223                     List<RoleDescriptor> roles = relyingPartyMD.getRoleDescriptors(relyingPartyRole,
224                             SAMLConstants.SAML11P_NS);
225                     if (roles != null && roles.size() > 0) {
226                         messageContext.setPeerEntityRoleMetadata(roles.get(0));
227                     }
228                 }
229             }
230         } catch (MetadataProviderException e) {
231             log.error("Error retrieving metadata for relying party " + messageContext.getInboundMessageIssuer(), e);
232             throw new MessageDecodingException("Error retrieving metadata for relying party "
233                     + messageContext.getInboundMessageIssuer(), e);
234         }
235     }
236 
237     /**
238      * {@inheritDoc} 
239      * 
240      * <p>This SAML 2-specific implementation extracts the value of the protocol message Destination attribute.</p>
241      * 
242      * */
243     protected String getIntendedDestinationEndpointURI(SAMLMessageContext samlMsgCtx) throws MessageDecodingException {
244         SAMLObject samlMessage = samlMsgCtx.getInboundSAMLMessage();
245         String messageDestination = null;
246         if (samlMessage instanceof RequestAbstractType) {
247             RequestAbstractType request =  (RequestAbstractType) samlMessage;
248             messageDestination = DatatypeHelper.safeTrimOrNullString(request.getDestination());
249         } else if (samlMessage instanceof StatusResponseType) {
250             StatusResponseType response = (StatusResponseType) samlMessage;
251             messageDestination = DatatypeHelper.safeTrimOrNullString(response.getDestination());
252         } else {
253             log.error("Invalid SAML message type encountered: {}", samlMessage.getElementQName().toString());
254             throw new MessageDecodingException("Invalid SAML message type encountered");
255         }
256         return messageDestination;
257     }
258     
259 }