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;
18  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import org.opensaml.common.binding.BasicEndpointSelector;
24  import org.opensaml.saml2.core.AuthnRequest;
25  import org.opensaml.saml2.metadata.Endpoint;
26  import org.opensaml.saml2.metadata.IndexedEndpoint;
27  import org.opensaml.xml.util.DatatypeHelper;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * An endpoint selector that implements the additional selection constraints described within the SAML 2.0 AuthnRequest
33   * specification. If an endpoint can not be resolved using either the information within the assertion consumer service
34   * index or the assertion consumer service URL given in the authentication request, or if this information isn't
35   * present, than the rules for the {@link BasicEndpointSelector} are used.
36   */
37  public class AuthnResponseEndpointSelector extends BasicEndpointSelector {
38  
39      /** Class logger. */
40      private final Logger log = LoggerFactory.getLogger(AuthnResponseEndpointSelector.class);
41  
42      /** {@inheritDoc} */
43      @SuppressWarnings("unchecked")
44      public Endpoint selectEndpoint() {
45          if (getEntityRoleMetadata() == null) {
46              log.debug("Unable to select endpoint, no entity role metadata available.");
47              return null;
48          }
49  
50          List<? extends Endpoint> endpoints = getEntityRoleMetadata().getEndpoints(getEndpointType());
51          if (endpoints == null || endpoints.size() == 0) {
52              return null;
53          }
54  
55          Endpoint endpoint = null;
56          AuthnRequest request = (AuthnRequest) getSamlRequest();
57          if (request != null) {
58              endpoints = filterEndpointsByProtocolBinding(endpoints);
59              if (endpoints == null || endpoints.isEmpty()) {
60                  return null;
61              }
62  
63              if (request.getAssertionConsumerServiceIndex() != null) {
64                  log.debug("Selecting endpoint by ACS index '{}' for request '{}' from entity '{}'",
65                          new Object[] { request.getAssertionConsumerServiceIndex(), request.getID(),
66                                  getEntityMetadata().getEntityID() });
67                  endpoint = selectEndpointByACSIndex(request, (List<IndexedEndpoint>) endpoints);
68              } else if (request.getAssertionConsumerServiceURL() != null) {
69                  log
70                          .debug(
71                                  "Selecting endpoint by ACS URL '{}' and protocol binding '{}' for request '{}' from entity '{}'",
72                                  new Object[] { request.getAssertionConsumerServiceURL(), request.getProtocolBinding(),
73                                          request.getID(), getEntityMetadata().getEntityID() });
74                  endpoint = selectEndpointByACSURL(request, (List<IndexedEndpoint>) endpoints);
75              }
76          }
77  
78          if (endpoint == null && request.getAssertionConsumerServiceIndex() == null
79                  && request.getAssertionConsumerServiceURL() == null) {
80              log.debug("No ACS index or URL given, selecting endpoint without additional constraints.");
81              if (endpoints.get(0) instanceof IndexedEndpoint) {
82                  endpoint = selectIndexedEndpoint((List<IndexedEndpoint>) endpoints);
83              } else {
84                  endpoint = selectNonIndexedEndpoint((List<Endpoint>) endpoints);
85              }
86          }
87  
88          return endpoint;
89      }
90  
91      /**
92       * Filters the list of possible endpoints by supported outbound bindings and, if the authentication request contains
93       * a requested binding and not an ACS index, that too is used to filter the list.
94       * 
95       * @param endpoints raw list of endpoints
96       * 
97       * @return filtered endpoints
98       */
99      protected List<? extends Endpoint> filterEndpointsByProtocolBinding(List<? extends Endpoint> endpoints) {
100         log.debug("Filtering peer endpoints.  Supported peer endpoint bindings: {}", getSupportedIssuerBindings());
101         AuthnRequest request = (AuthnRequest) getSamlRequest();
102 
103         boolean filterByRequestBinding = false;
104         String acsBinding = DatatypeHelper.safeTrimOrNullString(request.getProtocolBinding());
105         if (acsBinding != null && request.getAssertionConsumerServiceIndex() != null) {
106             filterByRequestBinding = true;
107         }
108 
109         List<Endpoint> filteredEndpoints = new ArrayList<Endpoint>(endpoints);
110         Iterator<Endpoint> endpointItr = filteredEndpoints.iterator();
111         Endpoint endpoint;
112         while (endpointItr.hasNext()) {
113             endpoint = endpointItr.next();
114             if (!getSupportedIssuerBindings().contains(endpoint.getBinding())) {
115                 log.debug("Removing endpoint {} because its binding {} is not supported", endpoint.getLocation(),
116                         endpoint.getBinding());
117                 endpointItr.remove();
118                 continue;
119             }
120 
121             if (filterByRequestBinding && !endpoint.getBinding().equals(acsBinding)) {
122                 log.debug("Removing endpoint {} because its binding {} does not match request's requested binding",
123                         endpoint.getLocation(), endpoint.getBinding());
124                 endpointItr.remove();
125             }
126         }
127 
128         return filteredEndpoints;
129     }
130 
131     /**
132      * Selects the endpoint by way of the assertion consumer service index given in the AuthnRequest.
133      * 
134      * @param request the AuthnRequest
135      * @param endpoints list of endpoints to select from
136      * 
137      * @return the selected endpoint
138      */
139     protected Endpoint selectEndpointByACSIndex(AuthnRequest request, List<IndexedEndpoint> endpoints) {
140         Integer acsIndex = request.getAssertionConsumerServiceIndex();
141         for (IndexedEndpoint endpoint : endpoints) {
142             if (endpoint == null || !getSupportedIssuerBindings().contains(endpoint.getBinding())) {
143                 log
144                         .debug(
145                                 "Endpoint '{}' with binding '{}' discarded because it requires an unsupported outbound binding.",
146                                 endpoint.getLocation(), endpoint.getBinding());
147                 continue;
148             }
149 
150             if (DatatypeHelper.safeEquals(acsIndex, endpoint.getIndex())) {
151                 return endpoint;
152             } else {
153                 log.debug("Endpoint '{}' with index '{}' discard because it does have the required index '{}'",
154                         new Object[] { endpoint.getLocation(), endpoint.getIndex(), acsIndex });
155             }
156         }
157 
158         log.warn("Relying party '{}' requested the response to be returned to endpoint with ACS index '{}' "
159                 + "however no endpoint, with that index and using a supported binding, can be found "
160                 + " in the relying party's metadata ", getEntityMetadata().getEntityID(), acsIndex);
161         return null;
162     }
163 
164     /**
165      * Selects the endpoint by way of the assertion consumer service URL given in the AuthnRequest.
166      * 
167      * @param request the AuthnRequest
168      * @param endpoints list of endpoints to select from
169      * 
170      * @return the selected endpoint
171      */
172     protected Endpoint selectEndpointByACSURL(AuthnRequest request, List<IndexedEndpoint> endpoints) {
173         String acsBinding = DatatypeHelper.safeTrimOrNullString(request.getProtocolBinding());
174 
175         for (IndexedEndpoint endpoint : endpoints) {
176             if (!getSupportedIssuerBindings().contains(endpoint.getBinding())) {
177                 log.debug(
178                         "Endpoint '{}' with binding '{}' discarded because that is not a supported outbound binding.",
179                         endpoint.getLocation(), endpoint.getBinding());
180                 continue;
181             }
182 
183             if (acsBinding != null) {
184                 if (!DatatypeHelper.safeEquals(acsBinding, endpoint.getBinding())) {
185                     log.debug("Endpoint '{}' with binding '{}' discarded because it does not meet protocol binding selection criteria",
186                                     endpoint.getLocation(), endpoint.getBinding());
187                     continue;
188                 }
189             }
190 
191             if (DatatypeHelper.safeEquals(endpoint.getLocation(), request.getAssertionConsumerServiceURL())
192                     || DatatypeHelper.safeEquals(endpoint.getResponseLocation(), request
193                             .getAssertionConsumerServiceURL())) {
194                 return endpoint;
195             } else {
196                 log.debug(
197                         "Endpoint '{}' discarded because neither its Location nor ResponseLocation match ACS URL '{}'",
198                         endpoint.getLocation(), request.getAssertionConsumerServiceURL());
199             }
200         }
201 
202         log.warn("Relying party '{}' requested the response to be returned to endpoint with ACS URL '{}' "
203                 + " and binding '{}' however no endpoint, with that URL and using a supported binding, "
204                 + " can be found in the relying party's metadata ", new Object[] { getEntityMetadata().getEntityID(),
205                 request.getAssertionConsumerServiceURL(), (acsBinding == null) ? "any" : acsBinding });
206         return null;
207     }
208 }