Author: scottbw
Date: Mon Feb 24 19:04:26 2014
New Revision: 1571398

URL: http://svn.apache.org/r1571398
Log:
Added a helper to the connector for making signed API requests

Added:
    
wookie/trunk/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/SignedApiRequest.java

Added: 
wookie/trunk/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/SignedApiRequest.java
URL: 
http://svn.apache.org/viewvc/wookie/trunk/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/SignedApiRequest.java?rev=1571398&view=auto
==============================================================================
--- 
wookie/trunk/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/SignedApiRequest.java
 (added)
+++ 
wookie/trunk/wookie-connector/java/src/main/java/org/apache/wookie/connector/framework/SignedApiRequest.java
 Mon Feb 24 19:04:26 2014
@@ -0,0 +1,418 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wookie.connector.framework;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.SignatureException;
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+import java.net.HttpURLConnection;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+public class SignedApiRequest {
+
+       protected static final int TEST_SERVER_PORT = 8080;
+       protected static final String TEST_SERVER_HOST = "localhost";   
+       
+       private static final String BOUNDARY = "*****";
+       private static final String LINE_END = "\r\n";
+       private static final String TWO_HYPHENS = "--";
+
+       private String method;
+       private String url;
+       private String host;
+       private String uri;
+       private HashMap<String, String> params;
+       private int statusCode;
+       private InputStream stream;
+       private File file;
+       private String accepts;
+       private String entity;
+       private String key;
+       private String secret;
+
+       /**
+        * Construct a new request
+        * @param method the HTTP method, e.g. POST
+        * @param url the URL to request
+        */
+       public SignedApiRequest(String method, String url, String key, String 
secret){
+               this.method = method;
+               this.key = "TEST";
+               this.secret = "[email protected]";
+               this.url = url;
+               params = new HashMap<String, String>();
+       }
+       
+       public static SignedApiRequest GET(String url, String key, String 
secret){
+               return new SignedApiRequest("GET", url, key, secret);
+       }
+       public static SignedApiRequest POST(String url, String key, String 
secret){
+               return new SignedApiRequest("POST", url, key, secret);
+       }
+       public static SignedApiRequest PUT(String url, String key, String 
secret){
+               return new SignedApiRequest("PUT", url, key, secret);
+       }
+       public static SignedApiRequest DELETE(String url, String key, String 
secret){
+               return new SignedApiRequest("DELETE", url, key, secret);
+       }
+       
+
+       /**
+        * Sets the content type to accept for the response, e.g 
application/json, text/xml
+        * @param type the content type
+        */
+       public void setAccepts(String type){
+               this.accepts = type;
+       }
+       
+       public void setFile(File file){
+               this.file = file;
+       }
+
+       /**
+        * Add a parameter to the request; this is turned into a querystring or 
params depending
+        * on the method used
+        * @param name
+        * @param value
+        */
+       public void addParameter(String name, String value){
+               this.params.put(name, value);
+       }
+
+       /**
+        * Execute the request.   
+        * @param sign if true, add a valid HMAC signature
+        * @param auth if true, add HTTP BASIC credentials
+        * @throws HttpException
+        * @throws IOException
+        */
+       public void execute()  throws IOException, WookieConnectorException{
+
+               //
+               // Add timestamp and nonce to request
+               //
+               addParameter("timestamp", dateFormat.format(new 
Date(System.currentTimeMillis())));
+               addParameter("nonce", UUID.randomUUID().toString());
+
+               //
+               // Append query string
+               //
+               this.url += getParamsAsQueryString();
+               
+               //
+               // Double encode spaces in URLs; this is to handle some 
slightly odd DELETE methods.
+               //
+               this.url = this.url.replace(" ", "%20");
+               
+               URL url = new URL(this.url);
+               this.host = url.getHost()+ ":" + url.getPort();
+               this.uri = url.getPath();
+                       
+
+               //
+               // Create connection
+               //
+               HttpURLConnection connection = (HttpURLConnection) 
url.openConnection();
+               connection.setRequestMethod(method);
+
+               //
+               // Set signature header
+               //
+               connection.addRequestProperty ("Authorization", getAuthz());
+
+               //
+               // Set accepted content type
+               //
+               if (accepts != null) connection.addRequestProperty("Accept", 
accepts);
+               
+               //
+               // Set input to true
+               //
+               connection.setDoInput(true);
+
+               //
+               // Write file to data output stream if not null
+               //
+               if (file != null){
+                       
+                       int bytesRead, bytesAvailable, bufferSize;
+                       byte[] buffer;
+                       int maxBufferSize = 1 * 1024 * 1024;
+                       
+                       FileInputStream fileInputStream = new 
FileInputStream(this.file);
+                       
+                       connection.setDoOutput(true);
+                       connection.setRequestProperty("Connection", 
"Keep-Alive");
+                       connection.setRequestProperty("Content-Type", 
"multipart/form-data;boundary=" + BOUNDARY);
+                       DataOutputStream dos = new 
DataOutputStream(connection.getOutputStream());
+
+                       dos.writeBytes(TWO_HYPHENS + BOUNDARY + LINE_END);
+                       dos.writeBytes("Content-Disposition: form-data; 
name=\"upload\";"
+                                       + " filename=\"" + 
this.file.getAbsolutePath() + "\"" + LINE_END);
+                       dos.writeBytes(LINE_END);
+                       
+                       // create a buffer of maximum size
+
+                       bytesAvailable = fileInputStream.available();
+                       bufferSize = Math.min(bytesAvailable, maxBufferSize);
+                       buffer = new byte[bufferSize];
+
+                       // read file and write it into form...
+
+                       bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+
+                       while (bytesRead > 0) {
+                               dos.write(buffer, 0, bufferSize);
+                               bytesAvailable = fileInputStream.available();
+                               bufferSize = Math.min(bytesAvailable, 
maxBufferSize);
+                               bytesRead = fileInputStream.read(buffer, 0, 
bufferSize);
+                       }
+
+                       //
+                       // send multipart form data necesssary after file 
data...
+                       //
+                       dos.writeBytes(LINE_END);
+                       dos.writeBytes(TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + 
LINE_END);
+
+                       //
+                       // close streams
+                       //
+                       fileInputStream.close();
+                       dos.flush();
+                       dos.close();
+               }
+               
+               //
+               // Write body content
+               //
+               if (this.entity != null){
+                       //this.entity =  URLEncoder.encode( this.entity, 
"UTF-8");
+                       connection.setDoOutput(true);
+                       connection.setRequestProperty("Content-Type", 
"text/plain");
+                       connection.setRequestProperty("Content-Length", 
Integer.toString(this.entity.length()) );
+                       DataOutputStream stream = new 
DataOutputStream(connection.getOutputStream());
+                       stream.write(this.entity.getBytes());
+                       stream.flush();
+                       stream.close();
+               }
+
+               //
+               // Get the result
+               //
+
+               this.statusCode = connection.getResponseCode();
+               try {
+                       this.stream = connection.getInputStream();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       this.stream = null;
+               }
+       }
+
+       /**
+        * Get the response as a string
+        * @return
+        * @throws IOException 
+        */
+       public String getResponseBodyAsString() throws IOException{
+               StringWriter writer = new StringWriter();
+               char buff[] = new char[1024];
+               BufferedReader reader = new BufferedReader(new 
InputStreamReader(this.stream, "UTF-8"));
+               int n;
+               while ((n = reader.read(buff)) != -1 ) {
+                       writer.write(buff,0,n);
+               }
+               writer.close();
+               return writer.toString();
+       }
+
+       /**
+        * Creates a HMAC signature for the request
+        * @return
+        */
+       private String getAuthz(){
+               String query = getCanonicalParameters(params);
+               String reqString =getCanonicalRequest(method, host, uri, query);
+               try {
+                       String signature = key + " " + getHmac(reqString, 
secret);
+                       return signature;
+               } catch (SignatureException e) {
+                       e.printStackTrace();
+                       return null;
+               }
+       }
+
+       /**
+        * Returns the parameters as a querystring
+        */
+       private String getParamsAsQueryString(){
+               String query = "?";
+               for (String name: this.params.keySet()){
+                       if (query.length() > 1) query += '&';
+                       try {
+                               String value = this.params.get(name);
+                               if (name.equals("timestamp")) value = 
URLEncoder.encode(value, "UTF-8");
+                               query += name + "=" + value;
+                       } catch (UnsupportedEncodingException e) {
+                               e.printStackTrace();
+                       }
+               }
+               return query;
+       }
+
+       /**
+        * Gets the status code returned after executing the request
+        * @return
+        */
+       public int getStatusCode(){
+               return statusCode;
+       }
+
+       /**
+        * Get the response as a stream
+        * @return the response stream
+        */
+       public InputStream getResponseBodyAsStream(){
+               return stream;
+       }
+
+       /**
+        * Set the request entity (body)
+        * @param entity
+        */
+       public void setRequestEntity(String entity) {
+               this.entity = entity;
+       }
+       
+       /**
+        * Date formatter for ISO datetime
+        */
+       private static DateFormat dateFormat = new 
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"){ 
+               private static final long serialVersionUID = 
7465240007718011363L;
+               public Date parse(String source,ParsePosition pos) {  
+               return 
super.parse(source.replaceFirst(":(?=[0-9]{2}$)",""),pos);
+           }
+       };
+       
+       /**
+        * The hashing algorithm to use
+        */
+       private static final String HMAC_ALGORITHM = "HmacSHA256";
+       
+       /**
+        * Computes a HMAC signature.
+        * @param data The data to be signed.
+        * @param key The signing key.
+        * @return The HMAC signature.
+        * @throws java.security.SignatureException when signature generation 
fails
+        */
+       private String getHmac(String data, String key)
+       throws java.security.SignatureException
+       {
+               String hmac64;
+               try {
+                       // get an hmac_sha1 key from the raw key bytes
+                       SecretKeySpec signingKey = new 
SecretKeySpec(key.getBytes(), HMAC_ALGORITHM);
+
+                       // get an hmac_sha1 Mac instance and initialize with 
the signing key
+                       Mac mac = Mac.getInstance(HMAC_ALGORITHM);
+                       mac.init(signingKey);
+
+                       // compute the hmac on input data bytes
+                       byte[] hmac = mac.doFinal(data.getBytes());
+
+                       // base64-encode the hmac
+                       hmac64 = encodeBase64String(hmac);
+
+               } catch (Exception e) {
+                       throw new SignatureException("Failed to generate HMAC : 
" + e.getMessage());
+               }
+               return hmac64;
+       }
+       
+       /**
+        * Sorts parameters and returns a querystring in canonical form usable 
for signing
+        * @param parameterMap
+        * @return a String with all parameters sorted and represented correctly
+        */
+       private String getCanonicalParameters(@SuppressWarnings("rawtypes") Map 
parameterMap){
+               String query = "?";
+               ArrayList<String> parameterNames = new ArrayList<String>();
+               @SuppressWarnings("rawtypes")
+               Iterator it = parameterMap.keySet().iterator();
+               while (it.hasNext()) parameterNames.add((String) it.next());
+               Collections.sort(parameterNames);
+               for (String name:parameterNames){
+                       if (!query.equals("?")) query += "&";
+                       Object value = parameterMap.get(name);
+                       if (value instanceof String[]){
+                               query += name.toLowerCase() + "=" + 
((String[])value)[0].toLowerCase();
+                       } else {
+                               query += name.toLowerCase() 
+"="+((String)value).toLowerCase();                 
+                       }
+
+               }
+               return query;
+       }
+       
+       /**
+        * Gets a canonical request to sign
+        * @param verb  the HTTP verb, e.g. POST
+        * @param host  the host, e.g. wookie.apache.org
+        * @param uri   the URI, e.g. /wookie
+        * @param query the canonical parameters for the request - see 
getCanonicalParameters
+        * @return the canonical string representation of the request
+        */
+       private String getCanonicalRequest(String verb, String host, String 
uri, String query){
+               String canonical = "";
+               verb = verb.toUpperCase();
+               host = host.toLowerCase();
+               uri = uri.toLowerCase();
+               query = query.toLowerCase();
+               canonical = verb + "\n" + host + "\n" + uri + "\n" + query;
+               return canonical;
+       }
+       
+       private String encodeBase64String (byte[] inputBytes ) {
+               return DatatypeConverter.printBase64Binary(inputBytes);
+       }
+
+
+
+}
+
+


Reply via email to