com.mastfrog.acteur.twitter.TwitterSign.java Source code

Java tutorial

Introduction

Here is the source code for com.mastfrog.acteur.twitter.TwitterSign.java

Source

package com.mastfrog.acteur.twitter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.net.MediaType;
import com.mastfrog.acteur.auth.OAuthPlugin.RemoteUserInfo;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_consumer_key;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_signature;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_signature_method;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_timestamp;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_token;
import static com.mastfrog.acteur.twitter.TwitterSign.OAuthHeaders.oauth_version;
import com.mastfrog.acteur.headers.Headers;
import com.mastfrog.acteur.headers.Method;
import com.mastfrog.netty.http.client.HttpClient;
import com.mastfrog.netty.http.client.ResponseHandler;
import com.mastfrog.netty.http.client.StateType;
import com.mastfrog.url.Host;
import com.mastfrog.url.Protocols;
import com.mastfrog.url.URL;
import com.mastfrog.util.Exceptions;
import com.mastfrog.util.thread.Receiver;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.CharsetUtil;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
//import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.joda.time.DateTimeUtils;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.User;
import twitter4j.auth.AccessToken;
import twitter4j.auth.OAuthAuthorization;
import twitter4j.internal.http.BASE64Encoder;
import twitter4j.internal.http.HttpParameter;
import twitter4j.internal.http.RequestMethod;

/**
 * *
 * ______ _____ ___ ______ _____ _ _ _____ _____ | ___ \ ___|/ _ \| _ \ |_ _| |
 * | |_ _/ ___| | |_/ / |__ / /_\ \ | | | | | | |_| | | | \ `--. | /| __|| _ | |
 * | | | | | _ | | | `--. \ | |\ \| |___| | | | |/ / | | | | | |_| |_/\__/ / \_|
 * \_\____/\_| |_/___/ \_/ \_| |_/\___/\____/
 * ---------------------------------------------------------------------- While
 * you're struggling to get this working, I highly recommend three things:
 *
 * 1. First, use HTTP, not HTTPS so you can see what you're doing, then switch
 * back to HTTPS once it's working 2. Use Fiddler or Wireshark to see your
 * actual requests and the Twitter responses 3. Use the example data from the
 * following address. Get that working first as a baseline, then use your own
 * credentials: https://dev.twitter.com/docs/auth/implementing-sign-twitter
 *
 *
 * // REQUIRED LIBRARIES // Apache commons codec // Apache HTTP Core // JSON
 *
 */
// Taken from here - license unknown: https://github.com/cyrus7580/twitter_api_examples
// once you've got the generic request token, send the user to the authorization page. They grant access and either
// a) are shown a pin number
// b) sent to the callback url with information
// In either case, turn the authorization code into a twitter access token for that user.
// My example here is uses a pin and oauth_token (from the previous request token call)
// INPUT: pin, generic request token
// OUTPUT: if successful, twitter API will return access_token, access_token_secret, screen_name and user_id
public class TwitterSign {

    private final String callbackUrl;
    private final String twitter_consumer_key;
    private final String twitter_consumer_secret;
    private final HttpClient client;

    TwitterSign(String twitter_consumer_key, String twitter_consumer_secret, String callbackUrl,
            HttpClient client) {
        this.twitter_consumer_key = twitter_consumer_key;
        this.twitter_consumer_secret = twitter_consumer_secret;
        this.callbackUrl = callbackUrl;
        this.client = client;
    }

    private static final class ResponseLatch extends Receiver<Void> {

        private final CountDownLatch latch = new CountDownLatch(1);

        @Override
        public void receive(Void object) {
            latch.countDown();
        }
    }

    private static class RH extends ResponseHandler<String> {

        private volatile String result;
        private volatile Throwable throwable;
        private volatile String error;
        private volatile HttpHeaders headers;
        private volatile HttpResponseStatus status;

        RH() {
            super(String.class);
        }

        @Override
        protected void receive(HttpResponseStatus status, HttpHeaders headers, String result) {
            this.result = result;
            this.headers = headers;
            this.status = status;
        }

        @Override
        protected void onErrorResponse(HttpResponseStatus status, HttpHeaders headers, String content) {
            this.status = status;
            this.headers = headers;
            this.error = content;
        }

        @Override
        protected void onError(Throwable err) {
            this.throwable = err;
        }

        public String getResponse() throws IOException {
            if (throwable != null) {
                Exceptions.chuck(throwable);
            }
            if (error != null) {
                throw new IOException(toString());
            }
            return result;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('@')
                    .append(System.identityHashCode(this)).append(" ");
            if (status != null) {
                sb.append(status).append("\n");
            }
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entries()) {
                    sb.append(e.getKey()).append(": ").append(e.getValue()).append("\n");
                }
            }
            if (result != null) {
                sb.append(result).append("\n");
            }
            if (error != null) {
                sb.append(result).append("\n");
            }
            return sb.toString();
        }
    }

    public String encode(String value) throws UnsupportedEncodingException {
        String encoded = null;
        encoded = URLEncoder.encode(value, "UTF-8");
        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();
    }

    private 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(Base64.getEncoder().encode(mac.doFinal(text))).trim();
        return new String(Base64.encodeBase64(mac.doFinal(text))).trim();
    }

    // the first step in the twitter oauth flow is to get a request token with a call to api.twitter.com/oauth/request_token
    // INPUT: nothing
    // OUTPUT: if successful, twitter API will return oauth_token, oauth_token_secret and oauth_token_confirmed
    public OAuthResult startTwitterAuthentication(String oauth_nonce)
            throws IOException, InterruptedException, GeneralSecurityException {

        // this particular request uses POST
        String get_or_post = "POST";

        // I think this is the signature method used for all Twitter API calls
        String oauth_signature_method = "HMAC-SHA1";

        // get the timestamp
        long ts = DateTimeUtils.currentTimeMillis();
        String oauth_timestamp = (new Long(ts / 1000)).toString(); // then divide by 1000 to get seconds

        // assemble the proper parameter string, which must be in alphabetical order, using your consumer key
        String parameter_string = "oauth_consumer_key=" + twitter_consumer_key + "&oauth_nonce=" + oauth_nonce
                + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp
                + "&oauth_version=1.0";

        // specify the proper twitter API endpoint at which to direct this request
        String twitter_endpoint = "https://api.twitter.com/oauth/request_token";
        String twitter_endpoint_host = "api.twitter.com";
        String twitter_endpoint_path = "/oauth/request_token";

        // assemble the string to be signed. It is METHOD & percent-encoded endpoint & percent-encoded parameter string
        // Java's native URLEncoder.encode function will not work. It is the wrong RFC specification (which does "+" where "%20" should be)...
        // the encode() function included in this class compensates to conform to RFC 3986 (which twitter requires)
        String signature_base_string = get_or_post + "&" + encode(twitter_endpoint) + "&"
                + encode(parameter_string);

        // now that we've got the string we want to sign (see directly above) HmacSHA1 hash it against the consumer secret
        String oauth_signature = "";
        oauth_signature = computeSignature(signature_base_string, twitter_consumer_secret + "&"); // note the & at the end. Normally the user access_token would go here, but we don't know it yet for request_token

        // each request to the twitter API 1.1 requires an "Authorization: BLAH" header. The following is what BLAH should look like
        String authorization_header_string = "OAuth oauth_consumer_key=\"" + twitter_consumer_key
                + "\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"" + oauth_timestamp
                + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_version=\"1.0\",oauth_signature=\""
                + encode(oauth_signature) + "\"";

        String oauth_token = "";
        String oauth_token_secret = "";
        String oauth_callback_confirmed = "";

        // initialize the HTTPS connection
        // for HTTP, use this instead of the above.
        // Socket socket = new Socket(host.getHostName(), host.getPort());
        // conn.bind(socket, params);
        ResponseLatch receiver = new ResponseLatch();
        RH rh = new RH();

        client.post()
                .setBody("", MediaType.parse("application/x-www-form-urlencoded").withCharset(CharsetUtil.UTF_8))
                .addHeader(Headers.stringHeader("Authorization"), authorization_header_string)
                .setURL(URL.builder().setProtocol(Protocols.HTTPS).setHost(Host.parse(twitter_endpoint_host))
                        .setPath(twitter_endpoint_path).create().toString())
                .on(StateType.Closed, receiver).execute(rh);

        receiver.latch.await(1, TimeUnit.MINUTES);
        String responseBody = rh.getResponse();
        StringTokenizer st = new StringTokenizer(responseBody, "&");
        String currenttoken = "";
        while (st.hasMoreTokens()) {
            currenttoken = st.nextToken();
            if (currenttoken.startsWith("oauth_token=")) {
                oauth_token = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else if (currenttoken.startsWith("oauth_token_secret=")) {
                oauth_token_secret = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else if (currenttoken.startsWith("oauth_callback_confirmed=")) {
                oauth_callback_confirmed = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else {
                System.out.println("Warning... twitter returned a key we weren't looking for: " + currenttoken);
            }
        }
        return new OAuthResult(oauth_token, oauth_token_secret, oauth_callback_confirmed);
    }

    public AuthorizationResponse getTwitterAccessTokenFromAuthorizationCode(String pin, String oauth_token,
            String oauth_nonce) throws IOException, InterruptedException, GeneralSecurityException {

        // this particular request uses POST
        String get_or_post = "POST";

        // I think this is the signature method used for all Twitter API calls
        String oauth_signature_method = "HMAC-SHA1";

        // get the timestamp
        Calendar tempcal = Calendar.getInstance();
        long ts = tempcal.getTimeInMillis();// get current time in milliseconds
        String oauth_timestamp = (new Long(ts / 1000)).toString(); // then divide by 1000 to get seconds

        // the parameter string must be in alphabetical order
        String parameter_string = "oauth_consumer_key=" + twitter_consumer_key + "&oauth_nonce=" + oauth_nonce
                + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp
                + "&oauth_token=" + encode(oauth_token) + "&oauth_version=1.0";

        String twitter_endpoint = "https://api.twitter.com/oauth/access_token";
        String twitter_endpoint_host = "api.twitter.com";
        String twitter_endpoint_path = "/oauth/access_token";
        String signature_base_string = get_or_post + "&" + encode(twitter_endpoint) + "&"
                + encode(parameter_string);

        String oauth_signature = computeSignature(signature_base_string, twitter_consumer_secret + "&"); // note the & at the end. Normally the user access_token would go here, but we don't know it yet

        String authorization_header_string = "OAuth oauth_consumer_key=\"" + twitter_consumer_key
                + "\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"" + oauth_timestamp
                + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_version=\"1.0\",oauth_signature=\""
                + encode(oauth_signature) + "\",oauth_token=\"" + encode(oauth_token) + "\"";
        // System.out.println("authorization_header_string=" + authorization_header_string);

        String access_token = "";
        String access_token_secret = "";
        String user_id = "";
        String screen_name = "";

        RH rh = new RH();
        ResponseLatch latch = new ResponseLatch();

        URL url = URL.builder(Protocols.HTTPS).setHost(Host.parse(twitter_endpoint_host))
                .setPath(twitter_endpoint_path).create();

        client.post().setURL(url).addHeader(Headers.stringHeader("Authorization"), authorization_header_string)
                .setBody("oauth_verifier=" + encode(pin),
                        MediaType.parse("application/x-www-form-urlencoded").withCharset(CharsetUtil.UTF_8))
                .on(StateType.Closed, latch).on(StateType.Timeout, latch).execute(rh);
        latch.latch.await(1, TimeUnit.MINUTES);

        String responseBody = rh.getResponse();

        StringTokenizer st = new StringTokenizer(responseBody, "&");
        String currenttoken = "";
        while (st.hasMoreTokens()) {
            currenttoken = st.nextToken();
            if (currenttoken.startsWith("oauth_token=")) {
                access_token = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else if (currenttoken.startsWith("oauth_token_secret=")) {
                access_token_secret = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else if (currenttoken.startsWith("user_id=")) {
                user_id = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else if (currenttoken.startsWith("screen_name=")) {
                screen_name = currenttoken.substring(currenttoken.indexOf("=") + 1);
            } else {
                System.out.println("Warning... twitter returned a key we weren't looking for: " + currenttoken);
                // something else. The 4 values above are the only ones twitter should return, so this case would be weird.
                // skip
            }
        }
        if ("".equals(access_token) || "".equals(access_token_secret)) {
            throw new IOException("Missing information in " + responseBody);
        }
        return new AuthorizationResponse(access_token, access_token_secret);
    }

    public String getAuthorizationHeader(Method method, String twitter_endpoint, String oauth_token,
            String oauth_nonce) throws UnsupportedEncodingException, GeneralSecurityException {
        // this particular request uses POST
        String get_or_post = method.toString();

        // I think this is the signature method used for all Twitter API calls
        String oauth_signature_method = "HMAC-SHA1";

        // get the timestamp
        Calendar tempcal = Calendar.getInstance();
        long ts = tempcal.getTimeInMillis();// get current time in milliseconds
        String oauth_timestamp = (new Long(ts / 1000)).toString(); // then divide by 1000 to get seconds

        // the parameter string must be in alphabetical order
        String parameter_string = "oauth_consumer_key=" + twitter_consumer_key + "&oauth_nonce=" + oauth_nonce
                + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp
                + "&oauth_token=" + encode(oauth_token) + "&oauth_version=1.0";

        String signature_base_string = get_or_post + "&" + twitter_endpoint + "&" + encode(parameter_string);

        //        String oauth_signature  = computeSignature(signature_base_string, twitter_consumer_secret + "&");  // note the & at the end. Normally the user access_token would go here, but we don't know it yet
        String oauth_signature = computeSignature(signature_base_string, twitter_consumer_secret + "&"); // note the & at the end. Normally the user access_token would go here, but we don't know it yet

        /*
         OAuth oauth_consumer_key="cI5QfItOsHq08gRBWGFzmg",
         oauth_nonce="621132bc40c29af5a519f0ba9b0c7ffa",
         oauth_signature="dupbxKL9Zqu22a5NF3wxME2fPTE%3D",
         oauth_signature_method="HMAC-SHA1",
         oauth_timestamp="1369522015",
         oauth_token="260043244-SluDCyRqH0SzIjCXpRTbvkSiNCDZhjuwXmaNiB1h",
         oauth_version="1.0"
         */
        String authorization_header_string = "OAuth oauth_consumer_key=\"" + twitter_consumer_key + "\", "
                + "oauth_nonce=\"" + oauth_nonce + "\", " + "oauth_signature=\"" + encode(oauth_signature) + "\", "
                + "oauth_signature_method=\"HMAC-SHA1\", " + "oauth_timestamp=\"" + oauth_timestamp + "\", "
                + "oauth_token=\"" + encode(oauth_token) + "\", " + "oauth_version=\"1.0" + "\"";

        return authorization_header_string;
    }

    static enum OAuthHeaders {

        oauth_consumer_key(0), oauth_nonce(3), oauth_signature(6), oauth_signature_method(1), oauth_timestamp(
                2), oauth_token(5), oauth_version(4);
        private final int order;

        OAuthHeaders(int order) {
            // After hours of testing, the *only* difference between a failing
            // and succeeding query was the order of parameters - signatures
            // and *everything* else was identical.  So this parameter order is
            // magical
            this.order = order;
        }

        static int orderOf(String s) {
            for (OAuthHeaders a : values()) {
                if (a.toString().equals(s)) {
                    return a.order;
                }
            }
            return Integer.MAX_VALUE;
        }

        public static List<String> sortKeys(Map<String, ?> m) {
            List<String> result = new ArrayList<String>(m.keySet());
            Collections.sort(result, new MagicOrderComparator());
            return result;
        }

        private static final class MagicOrderComparator implements Comparator<String> {

            @Override
            public int compare(String o1, String o2) {
                Integer oa = orderOf(o1);
                Integer ob = orderOf(o2);
                return oa.compareTo(ob);
            }

        }

    }

    private class SigBuilder {

        private final Map<String, String> pairs = new HashMap<>();

        SigBuilder() {
            add(oauth_version, "1.0").add(oauth_timestamp, "" + (DateTimeUtils.currentTimeMillis() / 1000))
                    .add(oauth_consumer_key, twitter_consumer_key).add(oauth_signature_method, "HMAC-SHA1");
        }

        public SigBuilder setToken(String token) {
            return add(oauth_token, token);
        }

        public SigBuilder add(String key, String val) {
            pairs.put(key, val);
            return this;
        }

        public SigBuilder add(OAuthHeaders hdr, String value) {
            pairs.put(hdr.toString(), value);
            return this;
        }

        private List<String> sortedKeys(Map<String, String> pairs) {
            List<String> result = new ArrayList<>(pairs.keySet());
            Collections.sort(result);
            return result;
        }

        public String buildSignature(Method mth, String endpoint, AuthorizationResponse auth)
                throws UnsupportedEncodingException, GeneralSecurityException {
            endpoint = URLDecoder.decode(endpoint, "UTF-8");
            endpoint = encode(endpoint).replace("%5f", "_");
            StringBuilder sb = new StringBuilder(mth.name()).append('&').append(endpoint).append('&');
            StringBuilder content = new StringBuilder();
            boolean first = true;
            for (String key : sortedKeys(pairs)) {
                if (!first) {
                    // appended above so it will not be encoded as %26 - the
                    // rest of the &'s should be - go figure.
                    content.append('&');
                } else {
                    first = false;
                }
                content.append(key).append('=').append(pairs.get(key));
            }
            sb.append(encode(content.toString()));
            String oauth_signature = generateSignature(sb.toString(), auth);
            return oauth_signature;
        }

        public String toHeader(Method mth, String endpoint, AuthorizationResponse auth)
                throws UnsupportedEncodingException, GeneralSecurityException {
            String sig = buildSignature(mth, endpoint, auth);
            Map<String, String> m = new HashMap<>(pairs);
            m.put(oauth_signature.toString(), encode(sig));
            StringBuilder result = new StringBuilder();
            for (String key : OAuthHeaders.sortKeys(m)) {
                String val = m.get(key);
                if (result.length() != 0) {
                    result.append(",");
                }
                result.append(key).append('=').append('"').append(val).append('"');
            }
            result.insert(0, "OAuth ");

            return result.toString();
        }
    }

    @SuppressWarnings("unchecked")
    RemoteUserInfo zgetUserInfo(String oauth_nonce, TwitterOAuthPlugin.TwitterToken credential,
            AuthorizationResponse auth)
            throws UnsupportedEncodingException, GeneralSecurityException, InterruptedException, IOException {
        System.setProperty("twitter4j.http.useSSL", "false");

        Twitter twitter = TwitterFactory.getSingleton();
        try {
            // Idiotic - shutdown does not clear state
            twitter.setOAuthConsumer(twitter_consumer_key, twitter_consumer_secret);
        } catch (Exception e) {
            e.printStackTrace();
        }
        twitter.setOAuthAccessToken(new AccessToken(auth.accessToken, auth.accessTokenSecret));
        OAuthAuthorization a = new OAuthAuthorization(twitter.getConfiguration());
        a.setOAuthConsumer(twitter_consumer_key, twitter_consumer_secret);

        URL url = URL.builder(Protocols.HTTPS)
                //        URL url = URL.builder(Protocols.HTTP) // XXX NOT UNENCRYPTED!  JUST FOR DEBUGING
                .setHost(Host.parse("api.twitter.com")).setPath("1.1/account/verify_credentials.json").create();

        String hdr = new SigBuilder().add(oauth_token, auth.accessToken).add(OAuthHeaders.oauth_nonce, oauth_nonce)
                .toHeader(Method.GET, url.getPathAndQuery(), auth); // XXX encode URL?

        String franken = a.xgenerateAuthorizationHeader(oauth_nonce, "GET", "/1.1/account/verify_credentials.json",
                new HttpParameter[0], new twitter4j.auth.AccessToken(auth.accessToken, auth.accessTokenSecret));

        OAuthAuthorization oa = new OAuthAuthorization(twitter.getConfiguration());
        oa.setOAuthConsumer(twitter_consumer_key, twitter_consumer_secret);
        oa.setOAuthAccessToken(new AccessToken(auth.accessToken, auth.accessTokenSecret));

        twitter4j.internal.http.HttpRequest r = new twitter4j.internal.http.HttpRequest(RequestMethod.GET,
                "https://api.twitter.com/twitter4j.internal.http.HttpRequest", new HttpParameter[0], oa,
                Collections.<String, String>emptyMap());

        ResponseLatch latch = new ResponseLatch();
        RH rh = new RH();
        client.get().setURL(url).addHeader(Headers.stringHeader("Authorization"), hdr)
                .addHeader(Headers.stringHeader("X-Twitter-Client-URL"),
                        "http://twitter4j.org/en/twitter4j-3.0.4-SNAPSHOT.xml")
                .addHeader(Headers.stringHeader("X-Twitter-Client"), "Twitter4J")
                .addHeader(Headers.stringHeader("Accept-Encoding"), "gzip")
                .addHeader(Headers.stringHeader("X-Twitter-Client-Version"), "3.0.4-SNAPSHOT")
                .addHeader(Headers.stringHeader("Accept"), "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
                //                .addHeader(Headers.stringHeader("Accept"), "*/*")
                //                .addHeader(Headers.stringHeader("Connection"), "keep-alive")
                .noDateHeader()
                //                .noConnectionHeader()
                //                .addHeader(Headers.stringHeader("Content-Type"), "application/x-www-form-urlencoded")
                //                .setBody("screen_name=kablosna", MediaType.PLAIN_TEXT_UTF_8)
                .on(StateType.Closed, latch).on(StateType.Timeout, latch).execute(rh);
        rh.await(1, TimeUnit.MINUTES);
        latch.latch.await(1, TimeUnit.MINUTES);

        String responseBody = rh.getResponse();

        RUI rui = new RUI();
        if (responseBody == null) {
            //            System.out.println("NULL RESPONSE BODY.");
            throw new IOException(rh.toString());
        }
        rui.putAll(new ObjectMapper().readValue(responseBody, Map.class));
        return rui;
    }

    RemoteUserInfo getUserInfo(String oauth_nonce, TwitterOAuthPlugin.TwitterToken credential,
            AuthorizationResponse auth)
            throws UnsupportedEncodingException, GeneralSecurityException, InterruptedException, IOException {
        //        System.setProperty("twitter4j.loggerFactory", "twitter4j.internal.logging.StdOutLogger");
        System.setProperty("twitter4j.debug", "true");
        System.setProperty("twitter4j.http.useSSL", "false");

        Twitter twitter = TwitterFactory.getSingleton();
        try {
            // Idiotic 
            twitter.setOAuthConsumer(twitter_consumer_key, twitter_consumer_secret);
        } catch (Exception e) {
            e.printStackTrace();
        }
        twitter.setOAuthAccessToken(new AccessToken(auth.accessToken, auth.accessTokenSecret));
        try {
            User user = twitter.verifyCredentials();

            RUI rui = new RUI();
            rui.put("displayName", user.getName());
            rui.put("name", user.getScreenName() + "@api.twitter.com");
            rui.put("screen_name", user.getScreenName());

            rui.put("picture", user.getProfileImageURLHttps());
            rui.put("pictureLarge", user.getBiggerProfileImageURLHttps());
            rui.put("id", user.getId());
            return rui;
        } catch (TwitterException ex) {
            throw new IOException(ex);
        } finally {
            twitter.shutdown();
        }
    }

    @SuppressWarnings("unchecked")
    RemoteUserInfo ygetUserInfo(String oauth_nonce, TwitterOAuthPlugin.TwitterToken credential,
            AuthorizationResponse auth)
            throws UnsupportedEncodingException, GeneralSecurityException, InterruptedException, IOException {
        URL url = URL.builder(Protocols.HTTPS).setHost(Host.parse("api.twitter.com"))
                .setPath("1.1/account/verify_credentials.json").create();

        String hdr = new SigBuilder().add(oauth_token, auth.accessToken).add(OAuthHeaders.oauth_nonce, oauth_nonce)
                .toHeader(Method.GET, url.toString(), auth); // XXX encode URL?

        ResponseLatch latch = new ResponseLatch();
        RH rh = new RH();
        client.get().setURL(url).addHeader(Headers.stringHeader("Authorization"), hdr)
                .addHeader(Headers.stringHeader("Accept"), "*/*")
                .addHeader(Headers.stringHeader("Content-Type"), "application/x-www-form-urlencoded")
                //                .setBody("screen_name=kablosna", MediaType.PLAIN_TEXT_UTF_8)
                .on(StateType.Closed, latch).on(StateType.Timeout, latch).execute(rh);
        rh.await(1, TimeUnit.MINUTES);
        latch.latch.await(1, TimeUnit.MINUTES);

        String responseBody = rh.getResponse();

        RUI rui = new RUI();
        if (responseBody == null) {
            //            System.out.println("NULL RESPONSE BODY.");
            throw new IOException(rh.toString());
        }
        rui.putAll(new ObjectMapper().readValue(responseBody, Map.class));
        return rui;
    }

    @SuppressWarnings("unchecked")
    RemoteUserInfo xgetUserInfo(String oauth_nonce, TwitterOAuthPlugin.TwitterToken credential,
            AuthorizationResponse auth)
            throws UnsupportedEncodingException, GeneralSecurityException, InterruptedException, IOException {

        URL url = URL.builder(Protocols.HTTPS).setHost(Host.parse("api.twitter.com"))
                .setPath("1.1/account/verify_credentials.json").create();

        String authorization_header_string = getAuthorizationHeader(Method.GET,
                "https%3A%2F%2Fapi.twitter.com%2F1.1%2Faccount%2Fverify_credentials.json", auth.accessToken,
                oauth_nonce);

        ResponseLatch latch = new ResponseLatch();
        RH rh = new RH();

        client.get().setURL(url).addHeader(Headers.stringHeader("Authorization"), authorization_header_string)
                .addHeader(Headers.stringHeader("Accept"), "*/*")
                .addHeader(Headers.stringHeader("Content-Type"), "application/x-www-form-urlencoded")
                //                .setBody("screen_name=kablosna", MediaType.PLAIN_TEXT_UTF_8)
                .on(StateType.Closed, latch).on(StateType.Timeout, latch).execute(rh);
        rh.await(1, TimeUnit.MINUTES);
        latch.latch.await(1, TimeUnit.MINUTES);

        String responseBody = rh.getResponse();

        RUI rui = new RUI();
        if (responseBody == null) {
            //            System.out.println("NULL RESPONSE BODY.");
            throw new IOException(rh.toString());
        }
        rui.putAll(new ObjectMapper().readValue(responseBody, Map.class));

        //        String url = "https://api.twitter.com/1.1/users/show.json?user_id=" + screenName + "&screen_name="
        //                + screenName;
        //        System.out.println("Fetch uer info for " + screenName);
        return rui;
    }

    private static final String ALGORITHM = "HmacSHA1";

    String generateSignature(String data, AuthorizationResponse token)
            throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] byteHMAC = null;
        Mac mac = Mac.getInstance(ALGORITHM);
        SecretKeySpec spec;
        if (token == null) {
            String signature = HttpParameter.encode(twitter_consumer_secret) + "&";
            spec = new SecretKeySpec(signature.getBytes(), ALGORITHM);
        } else {
            String signature = HttpParameter.encode(twitter_consumer_secret) + "&"
                    + HttpParameter.encode(token.accessTokenSecret);
            spec = new SecretKeySpec(signature.getBytes(), ALGORITHM);
        }
        mac.init(spec);
        byteHMAC = mac.doFinal(data.getBytes());
        String sig = BASE64Encoder.encode(byteHMAC);
        return sig;
    }

    static class RUI extends HashMap<String, Object> implements RemoteUserInfo {

        @Override
        public String userName() {
            return (String) get("name");
        }

        @Override
        public String displayName() {
            return (String) get("displayName");
        }

        @Override
        public Object get(String key) {
            return super.get(key);
        }
    }
}