View Javadoc

1   /**
2    *       Copyright 2010 Newcastle University
3    *
4    *          http://research.ncl.ac.uk/smart/
5    *
6    * Licensed to the Apache Software Foundation (ASF) under one or more
7    * contributor license agreements.  See the NOTICE file distributed with
8    * this work for additional information regarding copyright ownership.
9    * The ASF licenses this file to You under the Apache License, Version 2.0
10   * (the "License"); you may not use this file except in compliance with
11   * the License.  You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   */
21  
22  package org.apache.amber.oauth2.common.utils;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.io.UnsupportedEncodingException;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.net.URLDecoder;
32  import java.net.URLEncoder;
33  import java.util.Collection;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.StringTokenizer;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  import javax.servlet.http.HttpServletRequest;
44  
45  import org.apache.amber.oauth2.common.OAuth;
46  import org.apache.amber.oauth2.common.error.OAuthError;
47  import org.apache.amber.oauth2.common.exception.OAuthProblemException;
48  import org.apache.amber.oauth2.common.exception.OAuthSystemException;
49  
50  /**
51   * Common OAuth Utils class.
52   * <p/>
53   * Some methods based on the Utils class from OAuth V1.0a library available at:
54   * http://oauth.googlecode.com/svn/code/java/core/
55   *
56   *
57   *
58   *
59   */
60  public final class OAuthUtils {
61  
62      private static final String ENCODING = "UTF-8";
63      private static final String PARAMETER_SEPARATOR = "&";
64      private static final String NAME_VALUE_SEPARATOR = "=";
65  
66      public static final String AUTH_SCHEME = OAuth.OAUTH_HEADER_NAME;
67  
68      private static final Pattern OAUTH_HEADER = Pattern.compile("\\s*(\\w*)\\s+(.*)");
69      private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\"");
70  
71      public static final String MULTIPART = "multipart/";
72  
73      private static final String DEFAULT_CONTENT_CHARSET = ENCODING;
74  
75      /**
76       * Translates parameters into <code>application/x-www-form-urlencoded</code> String
77       *
78       * @param parameters parameters to encode
79       * @param encoding   The name of a supported
80       *                   <a href="../lang/package-summary.html#charenc">character
81       *                   encoding</a>.
82       * @return Translated string
83       */
84      public static String format(
85          final Collection<? extends Map.Entry<String, Object>> parameters,
86          final String encoding) {
87          final StringBuilder result = new StringBuilder();
88          for (final Map.Entry<String, Object> parameter : parameters) {
89              String value = parameter.getValue() == null? null : String.valueOf(parameter.getValue());
90              if (!OAuthUtils.isEmpty(parameter.getKey())
91                  && !OAuthUtils.isEmpty(value)) {
92                  final String encodedName = encode(parameter.getKey(), encoding);
93                  final String encodedValue = value != null ? encode(value, encoding) : "";
94                  if (result.length() > 0) {
95                      result.append(PARAMETER_SEPARATOR);
96                  }
97                  result.append(encodedName);
98                  result.append(NAME_VALUE_SEPARATOR);
99                  result.append(encodedValue);
100             }
101         }
102         return result.toString();
103     }
104 
105     private static String encode(final String content, final String encoding) {
106         try {
107             return URLEncoder.encode(content,
108                 encoding != null ? encoding : "UTF-8");
109         } catch (UnsupportedEncodingException problem) {
110             throw new IllegalArgumentException(problem);
111         }
112     }
113 
114     /**
115      * Read data from Input Stream and save it as a String.
116      *
117      * @param is InputStream to be read
118      * @return String that was read from the stream
119      */
120     public static String saveStreamAsString(InputStream is) throws IOException {
121         return toString(is, ENCODING);
122     }
123 
124     /**
125      * Get the entity content as a String, using the provided default character set
126      * if none is found in the entity.
127      * If defaultCharset is null, the default "UTF-8" is used.
128      *
129      * @param is             input stream to be saved as string
130      * @param defaultCharset character set to be applied if none found in the entity
131      * @return the entity content as a String
132      * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE
133      * @throws IOException              if an error occurs reading the input stream
134      */
135     public static String toString(
136         final InputStream is, final String defaultCharset) throws IOException {
137         if (is == null) {
138             throw new IllegalArgumentException("InputStream may not be null");
139         }
140 
141         String charset = defaultCharset;
142         if (charset == null) {
143             charset = DEFAULT_CONTENT_CHARSET;
144         }
145         Reader reader = new InputStreamReader(is, charset);
146         StringBuilder sb = new StringBuilder();
147         int l;
148         try {
149             char[] tmp = new char[4096];
150             while ((l = reader.read(tmp)) != -1) {
151                 sb.append(tmp, 0, l);
152             }
153         } finally {
154             reader.close();
155         }
156         return sb.toString();
157     }
158 
159     /**
160      * Creates invalid_request exception with given message
161      *
162      * @param message error message
163      * @return OAuthException
164      */
165     public static OAuthProblemException handleOAuthProblemException(String message) {
166         return OAuthProblemException.error(OAuthError.TokenResponse.INVALID_REQUEST)
167             .description(message);
168     }
169 
170     /**
171      * Creates OAuthProblemException that contains set of missing oauth parameters
172      *
173      * @param missingParams missing oauth parameters
174      * @return OAuthProblemException with user friendly message about missing oauth parameters
175      */
176 
177     public static OAuthProblemException handleMissingParameters(Set<String> missingParams) {
178         StringBuffer sb = new StringBuffer("Missing parameters: ");
179         if (!OAuthUtils.isEmpty(missingParams)) {
180             for (String missingParam : missingParams) {
181                 sb.append(missingParam).append(" ");
182             }
183         }
184         return handleOAuthProblemException(sb.toString().trim());
185     }
186 
187     public static OAuthProblemException handleBadContentTypeException(String expectedContentType) {
188         StringBuilder errorMsg = new StringBuilder("Bad request content type. Expecting: ").append(
189             expectedContentType);
190         return handleOAuthProblemException(errorMsg.toString());
191     }
192 
193     public static OAuthProblemException handleNotAllowedParametersOAuthException(
194         List<String> notAllowedParams) {
195         StringBuffer sb = new StringBuffer("Not allowed parameters: ");
196         if (notAllowedParams != null) {
197             for (String notAllowed : notAllowedParams) {
198                 sb.append(notAllowed).append(" ");
199             }
200         }
201         return handleOAuthProblemException(sb.toString().trim());
202     }
203 
204     /**
205      * Parse a form-urlencoded document.
206      */
207     public static Map<String, Object> decodeForm(String form) {
208         Map<String, Object> params = new HashMap<String, Object>();
209         if (!OAuthUtils.isEmpty(form)) {
210             for (String nvp : form.split("\\&")) {
211                 int equals = nvp.indexOf('=');
212                 String name;
213                 String value;
214                 if (equals < 0) {
215                     name = decodePercent(nvp);
216                     value = null;
217                 } else {
218                     name = decodePercent(nvp.substring(0, equals));
219                     value = decodePercent(nvp.substring(equals + 1));
220                 }
221                 params.put(name, value);
222             }
223         }
224         return params;
225     }
226 
227     /**
228      * Return true if the given Content-Type header means FORM_ENCODED.
229      */
230     public static boolean isFormEncoded(String contentType) {
231         if (contentType == null) {
232             return false;
233         }
234         int semi = contentType.indexOf(";");
235         if (semi >= 0) {
236             contentType = contentType.substring(0, semi);
237         }
238         return OAuth.ContentType.URL_ENCODED.equalsIgnoreCase(contentType.trim());
239     }
240 
241     public static String decodePercent(String s) {
242         try {
243             return URLDecoder.decode(s, ENCODING);
244             // This implements http://oauth.pbwiki.com/FlexibleDecoding
245         } catch (java.io.UnsupportedEncodingException wow) {
246             throw new RuntimeException(wow.getMessage(), wow);
247         }
248     }
249 
250     /**
251      * Construct a &-separated list of the given values, percentEncoded.
252      */
253     public static String percentEncode(Iterable values) {
254         StringBuilder p = new StringBuilder();
255         for (Object v : values) {
256             String stringValue = toString(v);
257             if (!isEmpty(stringValue)) {
258                 if (p.length() > 0) {
259                     p.append("&");
260                 }
261                 p.append(OAuthUtils.percentEncode(toString(v)));
262             }
263         }
264         return p.toString();
265     }
266 
267     public static String percentEncode(String s) {
268         if (s == null) {
269             return "";
270         }
271         try {
272             return URLEncoder.encode(s, ENCODING)
273                 // OAuth encodes some characters differently:
274                 .replace("+", "%20").replace("*", "%2A")
275                 .replace("%7E", "~");
276             // This could be done faster with more hand-crafted code.
277         } catch (UnsupportedEncodingException wow) {
278             throw new RuntimeException(wow.getMessage(), wow);
279         }
280     }
281 
282     private static final String toString(Object from) {
283         return (from == null) ? null : from.toString();
284     }
285 
286     private static boolean isEmpty(Set<String> missingParams) {
287         if (missingParams == null || missingParams.size() == 0) {
288             return true;
289         }
290         return false;
291     }
292 
293     public static <T> T instantiateClass(Class<T> clazz) throws OAuthSystemException {
294         try {
295             return (T)clazz.newInstance();
296         } catch (Exception e) {
297             throw new OAuthSystemException(e);
298         }
299     }
300 
301     public static Object instantiateClassWithParameters(Class clazz, Class[] paramsTypes,
302                                                         Object[] paramValues) throws OAuthSystemException {
303 
304         try {
305             if (paramsTypes != null && paramValues != null) {
306                 if (!(paramsTypes.length == paramValues.length)) {
307                     throw new IllegalArgumentException("Number of types and values must be equal");
308                 }
309 
310                 if (paramsTypes.length == 0 && paramValues.length == 0) {
311                     return clazz.newInstance();
312                 }
313                 Constructor clazzConstructor = clazz.getConstructor(paramsTypes);
314                 return clazzConstructor.newInstance(paramValues);
315             }
316             return clazz.newInstance();
317 
318         } catch (NoSuchMethodException e) {
319             throw new OAuthSystemException(e);
320         } catch (InstantiationException e) {
321             throw new OAuthSystemException(e);
322         } catch (IllegalAccessException e) {
323             throw new OAuthSystemException(e);
324         } catch (InvocationTargetException e) {
325             throw new OAuthSystemException(e);
326         }
327 
328     }
329 
330 
331     public static String getAuthHeaderField(String authHeader) {
332 
333         if (authHeader != null) {
334             Matcher m = OAUTH_HEADER.matcher(authHeader);
335             if (m.matches()) {
336                 if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) {
337                     return m.group(2);
338                 }
339             }
340         }
341         return null;
342     }
343 
344     public static Map<String, String> decodeOAuthHeader(String header) {
345         Map<String, String> headerValues = new HashMap<String, String>();
346         if (header != null) {
347             Matcher m = OAUTH_HEADER.matcher(header);
348             if (m.matches()) {
349                 if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) {
350                     for (String nvp : m.group(2).split("\\s*,\\s*")) {
351                         m = NVP.matcher(nvp);
352                         if (m.matches()) {
353                             String name = decodePercent(m.group(1));
354                             String value = decodePercent(m.group(2));
355                             headerValues.put(name, value);
356                         }
357                     }
358                 }
359             }
360         }
361         return headerValues;
362     }
363 
364     // todo: implement method to decode header form (with no challenge)
365 
366     /**
367      * Construct a WWW-Authenticate header
368      */
369     public static String encodeOAuthHeader(Map<String, Object> entries) {
370         StringBuffer sb = new StringBuffer();
371         sb.append(OAuth.OAUTH_HEADER_NAME).append(" ");
372         for (Map.Entry<String, Object> entry : entries.entrySet()) {
373             String value = entry.getValue() == null? null: String.valueOf(entry.getValue());
374             if (!OAuthUtils.isEmpty(entry.getKey()) && !OAuthUtils.isEmpty(value)) {
375                 sb.append(entry.getKey());
376                 sb.append("=\"");
377                 sb.append(value);
378                 sb.append("\",");
379             }
380         }
381 
382         return sb.substring(0, sb.length() - 1);
383     }
384     
385     /**
386      * Construct an Authorization Bearer header
387      */
388     public static String encodeAuthorizationBearerHeader(Map<String, Object> entries) {
389         StringBuffer sb = new StringBuffer();
390         sb.append(OAuth.OAUTH_HEADER_NAME).append(" ");
391         for (Map.Entry<String, Object> entry : entries.entrySet()) {
392             String value = entry.getValue() == null? null: String.valueOf(entry.getValue());
393             if (!OAuthUtils.isEmpty(entry.getKey()) && !OAuthUtils.isEmpty(value)) {
394                 sb.append(value);
395             }
396         }
397 
398         return sb.toString();
399     }
400 
401     public static boolean isEmpty(String value) {
402         return value == null || "".equals(value);
403     }
404 
405     public static boolean hasEmptyValues(String[] array) {
406         if (array == null || array.length == 0) {
407             return true;
408         }
409         for (String s : array) {
410             if (isEmpty(s)) {
411                 return true;
412             }
413         }
414         return false;
415     }
416 
417     public static String getAuthzMethod(String header) {
418         if (header != null) {
419             Matcher m = OAUTH_HEADER.matcher(header);
420             if (m.matches()) {
421                 return m.group(1);
422 
423             }
424         }
425         return null;
426     }
427 
428     public static Set<String> decodeScopes(String s) {
429         Set<String> scopes = new HashSet<String>();
430         if (!OAuthUtils.isEmpty(s)) {
431             StringTokenizer tokenizer = new StringTokenizer(s, " ");
432 
433             while (tokenizer.hasMoreElements()) {
434                 scopes.add(tokenizer.nextToken());
435             }
436         }
437         return scopes;
438 
439     }
440 
441     public static String encodeScopes(Set<String> s) {
442         StringBuffer scopes = new StringBuffer();
443         for (String scope : s) {
444             scopes.append(scope).append(" ");
445         }
446         return scopes.toString().trim();
447 
448     }
449 
450     public static boolean isMultipart(HttpServletRequest request) {
451 
452         if (!"post".equals(request.getMethod().toLowerCase())) {
453             return false;
454         }
455         String contentType = request.getContentType();
456         if (contentType == null) {
457             return false;
458         }
459         if (contentType.toLowerCase().startsWith(MULTIPART)) {
460             return true;
461         }
462         return false;
463     }
464 
465 
466     public static boolean hasContentType(String requestContentType, String requiredContentType) {
467         if (OAuthUtils.isEmpty(requiredContentType) || OAuthUtils.isEmpty(requestContentType)) {
468             return false;
469         }
470         StringTokenizer tokenizer = new StringTokenizer(requestContentType, ";");
471         while (tokenizer.hasMoreTokens()) {
472             if (requiredContentType.equals(tokenizer.nextToken())) {
473                 return true;
474             }
475         }
476 
477         return false;
478     }
479 
480 }
481 
482