View Javadoc
1   /*
2    * Copyright 2008 Web Cohesion
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    *   https://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.springframework.security.oauth.provider.filter;
18  
19  import org.apache.commons.codec.DecoderException;
20  import static org.springframework.security.oauth.common.OAuthCodec.oauthDecode;
21  import static org.springframework.security.oauth.common.OAuthCodec.oauthEncode;
22  import org.springframework.security.oauth.common.OAuthConsumerParameter;
23  import org.springframework.security.oauth.common.StringSplitUtils;
24  import org.springframework.security.oauth.provider.OAuthProviderSupport;
25  
26  import javax.servlet.http.HttpServletRequest;
27  import java.util.*;
28  import java.net.URL;
29  import java.net.MalformedURLException;
30  
31  /**
32   * Utility for common logic for supporting an OAuth provider.
33   *
34   * @author Ryan Heaton
35   */
36  public class CoreOAuthProviderSupport implements OAuthProviderSupport {
37  
38    private final Set<String> supportedOAuthParameters;
39    private String baseUrl = null;
40  
41    public CoreOAuthProviderSupport() {
42      Set<String> supportedOAuthParameters = new TreeSet<String>();
43      for (OAuthConsumerParameter supportedParameter : OAuthConsumerParameter.values()) {
44        supportedOAuthParameters.add(supportedParameter.toString());
45      }
46      this.supportedOAuthParameters = supportedOAuthParameters;
47    }
48  
49    // Inherited.
50    public Map<String, String> parseParameters(HttpServletRequest request) {
51      Map<String, String> parameters = parseHeaderParameters(request);
52  
53      if (parameters == null) {
54        //if there is no header authorization parameters, then the oauth parameters are the supported OAuth request parameters.
55        parameters = new HashMap<String, String>();
56        for (String supportedOAuthParameter : getSupportedOAuthParameters()) {
57          String param = request.getParameter(supportedOAuthParameter);
58          if (param != null) {
59            parameters.put(supportedOAuthParameter, param);
60          }
61        }
62      }
63  
64      return parameters;
65    }
66  
67    /**
68     * Parse the OAuth header parameters. The parameters will be oauth-decoded.
69     *
70     * @param request The request.
71     * @return The parsed parameters, or null if no OAuth authorization header was supplied.
72     */
73    protected Map<String, String> parseHeaderParameters(HttpServletRequest request) {
74      String header = null;
75      Enumeration<String> headers = request.getHeaders("Authorization");
76      while (headers.hasMoreElements()) {
77        String value = headers.nextElement();
78        if ((value.toLowerCase().startsWith("oauth "))) {
79          header = value;
80          break;
81        }
82      }
83  
84      Map<String, String> parameters = null;
85      if (header != null) {
86        parameters = new HashMap<String, String>();
87        String authHeaderValue = header.substring(6);
88  
89        //create a map of the authorization header values per OAuth Core 1.0, section 5.4.1
90        String[] headerEntries = StringSplitUtils.splitIgnoringQuotes(authHeaderValue, ',');
91        for (Object o : StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"").entrySet()) {
92          Map.Entry entry = (Map.Entry) o;
93          try {
94            String key = oauthDecode((String) entry.getKey());
95            String value = oauthDecode((String) entry.getValue());
96            parameters.put(key, value);
97          }
98          catch (DecoderException e) {
99            throw new IllegalStateException(e);
100         }
101       }
102     }
103 
104     return parameters;
105   }
106 
107   /**
108    * Get the supported OAuth parameters. The default implementation supports only the OAuth core parameters.
109    *
110    * @return The OAuth core parameters.
111    */
112   protected Set<String> getSupportedOAuthParameters() {
113     return this.supportedOAuthParameters;
114   }
115 
116   // Inherited.
117   public String getSignatureBaseString(HttpServletRequest request) {
118     SortedMap<String, SortedSet<String>> significantParameters = loadSignificantParametersForSignatureBaseString(request);
119 
120     //now concatenate them into a single query string according to the spec.
121     StringBuilder queryString = new StringBuilder();
122     Iterator<Map.Entry<String, SortedSet<String>>> paramIt = significantParameters.entrySet().iterator();
123     while (paramIt.hasNext()) {
124       Map.Entry<String, SortedSet<String>> sortedParameter = paramIt.next();
125       Iterator<String> valueIt = sortedParameter.getValue().iterator();
126       while (valueIt.hasNext()) {
127         String parameterValue = valueIt.next();
128         queryString.append(sortedParameter.getKey()).append('=').append(parameterValue);
129         if (paramIt.hasNext() || valueIt.hasNext()) {
130           queryString.append('&');
131         }
132       }
133     }
134 
135     String url = getBaseUrl(request);
136     if (url == null) {
137       //if no URL is configured, then we'll attempt to reconstruct the URL.  This may be inaccurate.
138       url = request.getRequestURL().toString();
139     }
140     url = normalizeUrl(url);
141     url = oauthEncode(url);
142 
143     String method = request.getMethod().toUpperCase();
144     return new StringBuilder(method).append('&').append(url).append('&').append(oauthEncode(queryString.toString())).toString();
145   }
146 
147   /**
148    * Normalize the URL for use in the signature. The OAuth spec says the URL protocol and host are to be lower-case,
149    * and the query and fragments are to be stripped.
150    *
151    * @param url The URL.
152    * @return The URL normalized for use in the signature.
153    */
154   protected String normalizeUrl(String url) {
155     try {
156       URL requestURL = new URL(url);
157       StringBuilder normalized = new StringBuilder(requestURL.getProtocol().toLowerCase()).append("://").append(requestURL.getHost().toLowerCase());
158       if ((requestURL.getPort() >= 0) && (requestURL.getPort() != requestURL.getDefaultPort())) {
159         normalized.append(":").append(requestURL.getPort());
160       }
161       normalized.append(requestURL.getPath());
162       return normalized.toString();
163     }
164     catch (MalformedURLException e) {
165       throw new IllegalStateException("Illegal URL for calculating the OAuth signature.", e);
166     }
167   }
168 
169   /**
170    * Loads the significant parameters (name-to-value map) that are to be used to calculate the signature base string.
171    * The parameters will be encoded, per the spec section 9.1.
172    *
173    * @param request The request.
174    * @return The significan parameters.
175    */
176   protected SortedMap<String, SortedSet<String>> loadSignificantParametersForSignatureBaseString(HttpServletRequest request) {
177     //first collect the relevant parameters...
178     SortedMap<String, SortedSet<String>> significantParameters = new TreeMap<String, SortedSet<String>>();
179     //first pull from the request...
180     Enumeration parameterNames = request.getParameterNames();
181     while (parameterNames.hasMoreElements()) {
182       String parameterName = (String) parameterNames.nextElement();
183       String[] values = request.getParameterValues(parameterName);
184       if (values == null) {
185         values = new String[]{ "" };
186       }
187 
188       parameterName = oauthEncode(parameterName);
189       for (String parameterValue : values) {
190         if (parameterValue == null) {
191           parameterValue = "";
192         }
193 
194         parameterValue = oauthEncode(parameterValue);
195         SortedSet<String> significantValues = significantParameters.get(parameterName);
196         if (significantValues == null) {
197           significantValues = new TreeSet<String>();
198           significantParameters.put(parameterName, significantValues);
199         }
200         significantValues.add(parameterValue);
201       }
202     }
203 
204     //then take into account the header parameter values...
205     Map<String, String> oauthParams = parseParameters(request);
206     oauthParams.remove("realm"); //remove the realm
207     Set<String> parsedParams = oauthParams.keySet();
208     for (String parameterName : parsedParams) {
209       String parameterValue = oauthParams.get(parameterName);
210       if (parameterValue == null) {
211         parameterValue = "";
212       }
213 
214       parameterName = oauthEncode(parameterName);
215       parameterValue = oauthEncode(parameterValue);
216       SortedSet<String> significantValues = significantParameters.get(parameterName);
217       if (significantValues == null) {
218         significantValues = new TreeSet<String>();
219         significantParameters.put(parameterName, significantValues);
220       }
221       significantValues.add(parameterValue);
222     }
223 
224     //remove the oauth signature parameter value.
225     significantParameters.remove(OAuthConsumerParameter.oauth_signature.toString());
226     return significantParameters;
227   }
228 
229   /**
230    * The configured base URL for this OAuth provider for the given HttpServletRequest. Default implementation return getBaseUrl() + request URI.
231    *
232    * @param request The HttpServletRequest currently processed
233    * @return The configured base URL for this OAuth provider with respect to the supplied HttpServletRequest.
234    */
235   protected String getBaseUrl(HttpServletRequest request) {
236     String baseUrl = getBaseUrl();
237     if (baseUrl != null) {
238       StringBuilder builder = new StringBuilder(baseUrl);
239       String path = request.getRequestURI();
240       if (path != null && !"".equals(path)) {
241         if (!baseUrl.endsWith("/") && !path.startsWith("/")) {
242           builder.append('/');
243         }
244         builder.append(path);
245       }
246       baseUrl = builder.toString();
247     }
248     return baseUrl;
249   }
250 
251   /**
252    * The configured base URL for this OAuth provider.
253    *
254    * @return The configured base URL for this OAuth provider.
255    */
256   public String getBaseUrl() {
257     return baseUrl;
258   }
259 
260   /**
261    * The configured base URL for the OAuth provider.
262    *
263    * @param baseUrl The configured base URL for the OAuth provider.
264    */
265   public void setBaseUrl(String baseUrl) {
266     this.baseUrl = baseUrl;
267   }
268 }