/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
*
* 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.streams.twitter.api;
import org.apache.streams.twitter.TwitterOAuthConfiguration;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Encoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Handles request signing to api.twitter.com
*/
public class TwitterOAuthRequestInterceptor implements HttpRequestInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(TwitterOAuthRequestInterceptor.class);
private static final String oauth_signature_encoding = "UTF-8";
private static final String oauth_signature_method = "HMAC-SHA1";
private static final String oauth_version = "1.0";
private static final BASE64Encoder base64Encoder = new BASE64Encoder();
TwitterOAuthConfiguration oAuthConfiguration;
public TwitterOAuthRequestInterceptor(TwitterOAuthConfiguration oAuthConfiguration) {
this.oAuthConfiguration = oAuthConfiguration;
}
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
String oauth_nonce = generateNonce();
String oauth_timestamp = generateTimestamp();
Map<String,String> oauthParamMap = new HashMap<>();
oauthParamMap.put("oauth_consumer_key", oAuthConfiguration.getConsumerKey());
oauthParamMap.put("oauth_nonce", oauth_nonce);
oauthParamMap.put("oauth_signature_method", oauth_signature_method);
oauthParamMap.put("oauth_timestamp", oauth_timestamp);
oauthParamMap.put("oauth_token", oAuthConfiguration.getAccessToken());
oauthParamMap.put("oauth_version", oauth_version);
String request_host = ((HttpRequestWrapper)httpRequest).getTarget().toString().replace(":443","");
String request_path = httpRequest.getRequestLine().getUri().substring(0, httpRequest.getRequestLine().getUri().indexOf('?'));
String request_param_line = httpRequest.getRequestLine().getUri().substring(httpRequest.getRequestLine().getUri().indexOf('?')+1);
String[] request_params = URLDecoder.decode(request_param_line).split("&");
Map<String,String> allParamsMap = new HashMap<>(oauthParamMap);
for( String request_param : request_params ) {
String key = request_param.substring(0, request_param.indexOf('='));
String value = request_param.substring(request_param.indexOf('=')+1, request_param.length());
allParamsMap.put(key, value);
}
if( ((HttpRequestWrapper) httpRequest).getOriginal() instanceof HttpPost) {
String body = EntityUtils.toString(((HttpPost)((HttpRequestWrapper) httpRequest).getOriginal()).getEntity());
String[] body_params = body.split(",");
for( String body_param : body_params ) {
body_param = URLDecoder.decode(body_param);
String key = body_param.substring(0, body_param.indexOf('='));
String value = body_param.substring(body_param.indexOf('=')+1, body_param.length());
allParamsMap.put(key, value);
}
}
allParamsMap = encodeMap(allParamsMap);
String signature_parameter_string = generateSignatureParameterString(allParamsMap);
String signature_base_string = generateSignatureBaseString(((HttpRequestWrapper) httpRequest).getMethod(), request_host+request_path, signature_parameter_string);
String signing_key = encode(oAuthConfiguration.getConsumerSecret()) + "&" + encode(oAuthConfiguration.getAccessTokenSecret());
String oauth_signature;
try {
oauth_signature = computeSignature(signature_base_string, signing_key);
} catch (GeneralSecurityException e) {
LOGGER.warn("GeneralSecurityException", e);
return;
}
oauthParamMap.put("oauth_signature", oauth_signature);
String authorization_header_string = generateAuthorizationHeaderString(oauthParamMap);
httpRequest.setHeader("Authorization", authorization_header_string);
}
public String generateTimestamp() {
Calendar tempcal = Calendar.getInstance();
long ts = tempcal.getTimeInMillis();// get current time in milliseconds
String oauth_timestamp = (new Long(ts/1000)).toString();
return oauth_timestamp;
}
public String generateNonce() {
String uuid_string = UUID.randomUUID().toString();
uuid_string = uuid_string.replaceAll("-", "");
String oauth_nonce = base64Encoder.encode(uuid_string.getBytes());
return oauth_nonce;
}
public static Map<String, String> encodeMap(Map<String, String> map) {
Map<String,String> newMap = new HashMap<>();
for( String key : map.keySet() ) {
String value = map.get(key);
newMap.put(encode(key), encode(value));
}
return newMap;
}
public static String generateAuthorizationHeaderString(Map<String,String> oauthParamMap) {
SortedSet<String> sortedKeys = new TreeSet<>(oauthParamMap.keySet());
StringJoiner stringJoiner = new StringJoiner(", ");
for( String key : sortedKeys ) {
stringJoiner.add(encode(key)+"="+"\""+encode(oauthParamMap.get(key))+"\"");
}
String authorization_header_string = new StringBuilder()
.append("OAuth ")
.append(stringJoiner.toString())
.toString();
return authorization_header_string;
}
public static String generateSignatureBaseString(String method, String request_url, String signature_parameter_string) {
String signature_base_string = new StringBuilder()
.append(method)
.append("&")
.append(encode(request_url))
.append("&")
.append(encode(signature_parameter_string))
.toString();
return signature_base_string;
}
public static String generateSignatureParameterString(Map<String, String> allParamsMap) {
SortedSet<String> sortedKeys = new TreeSet<>(allParamsMap.keySet());
StringJoiner stringJoiner = new StringJoiner("&");
for( String key : sortedKeys ) {
stringJoiner.add(key+"="+allParamsMap.get(key));
}
return stringJoiner.toString();
}
public static String encode(String value)
{
String encoded = null;
try {
encoded = URLEncoder.encode(value, oauth_signature_encoding);
} catch (UnsupportedEncodingException ignore) {
}
StringBuilder buf = new StringBuilder(encoded.length());
char focus;
for (int i = 0; i < encoded.length(); i++) {
focus = encoded.charAt(i);
if (focus == '*') {
buf.append("%2A");
} else if (focus == '+') {
buf.append("%20");
} else if (focus == '%' && (i + 1) < encoded.length()
&& encoded.charAt(i + 1) == '7' && encoded.charAt(i + 2) == 'E') {
buf.append('~');
i += 2;
} else {
buf.append(focus);
}
}
return buf.toString();
}
public static String computeSignature(String baseString, String keyString) throws GeneralSecurityException, UnsupportedEncodingException
{
SecretKey secretKey = null;
byte[] keyBytes = keyString.getBytes();
secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] text = baseString.getBytes();
return new String(base64Encoder.encode(mac.doFinal(text))).trim();
}
}