com.xing.api.Oauth1SigningInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for com.xing.api.Oauth1SigningInterceptor.java

Source

/*
 * Copyright (C) 2015 XING AG (http://xing.com/)
 * Copyright (C) 2015 Jake Wharton
 *
 * 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 com.xing.api;

import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map.Entry;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Pattern;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import okio.Buffer;
import okio.ByteString;

/**
 * Request {@link Interceptor} that handel's Oauth1 request signing.
 */
final class Oauth1SigningInterceptor implements Interceptor {
    private static final Pattern CHARACTER_PATTERN = Pattern.compile("\\W");
    private static final int NUANCE_BYTES = 32;
    private static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
    private static final String OAUTH_NONCE = "oauth_nonce";
    private static final String OAUTH_SIGNATURE = "oauth_signature";
    private static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
    private static final String OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1";
    private static final String OAUTH_TIMESTAMP = "oauth_timestamp";
    private static final String OAUTH_ACCESS_TOKEN = "oauth_token";
    private static final String OAUTH_VERSION = "oauth_version";
    private static final String OAUTH_VERSION_VALUE = "1.0";

    private final String consumerKey;
    private final String consumerSecret;
    private final String accessToken;
    private final String accessSecret;
    private final Random random;
    private final Clock clock;

    private Oauth1SigningInterceptor(String consumerKey, String consumerSecret, String accessToken,
            String accessSecret, Random random, Clock clock) {
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
        this.accessToken = accessToken;
        this.accessSecret = accessSecret;
        this.random = random;
        this.clock = clock;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        return chain.proceed(signRequest(chain.request()));
    }

    public Request signRequest(Request request) throws IOException {
        byte[] nonce = new byte[NUANCE_BYTES];
        random.nextBytes(nonce);
        String oauthNonce = CHARACTER_PATTERN.matcher(ByteString.of(nonce).base64()).replaceAll("");
        String oauthTimestamp = clock.millis();

        String consumerKeyValue = UrlEscapeUtils.escape(consumerKey);
        String accessTokenValue = UrlEscapeUtils.escape(accessToken);

        SortedMap<String, String> parameters = new TreeMap<>();
        parameters.put(OAUTH_CONSUMER_KEY, consumerKeyValue);
        parameters.put(OAUTH_ACCESS_TOKEN, accessTokenValue);
        parameters.put(OAUTH_NONCE, oauthNonce);
        parameters.put(OAUTH_TIMESTAMP, oauthTimestamp);
        parameters.put(OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD_VALUE);
        parameters.put(OAUTH_VERSION, OAUTH_VERSION_VALUE);

        HttpUrl url = request.httpUrl();
        for (int i = 0; i < url.querySize(); i++) {
            parameters.put(UrlEscapeUtils.escape(url.queryParameterName(i)),
                    UrlEscapeUtils.escape(url.queryParameterValue(i)));
        }

        Buffer body = new Buffer();
        RequestBody requestBody = request.body();
        if (requestBody != null) {
            requestBody.writeTo(body);
        }

        while (!body.exhausted()) {
            long keyEnd = body.indexOf((byte) '=');
            if (keyEnd == -1) {
                throw new IllegalStateException("Key with no value: " + body.readUtf8());
            }
            String key = body.readUtf8(keyEnd);
            body.skip(1); // Equals.

            long valueEnd = body.indexOf((byte) '&');
            String value = valueEnd == -1 ? body.readUtf8() : body.readUtf8(valueEnd);
            if (valueEnd != -1) {
                body.skip(1); // Ampersand.
            }

            parameters.put(key, value);
        }

        Buffer base = new Buffer();
        String method = request.method();
        base.writeUtf8(method);
        base.writeByte('&');
        base.writeUtf8(UrlEscapeUtils.escape(request.httpUrl().newBuilder().query(null).build().toString()));
        base.writeByte('&');

        boolean first = true;
        for (Entry<String, String> entry : parameters.entrySet()) {
            if (!first) {
                base.writeUtf8(UrlEscapeUtils.escape("&"));
            }
            first = false;
            base.writeUtf8(UrlEscapeUtils.escape(entry.getKey()));
            base.writeUtf8(UrlEscapeUtils.escape("="));
            base.writeUtf8(UrlEscapeUtils.escape(entry.getValue()));
        }

        String signingKey = UrlEscapeUtils.escape(consumerSecret) + '&' + UrlEscapeUtils.escape(accessSecret);
        SecretKeySpec keySpec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");
        Mac mac;
        try {
            mac = Mac.getInstance("HmacSHA1");
            mac.init(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new IllegalStateException(e);
        }
        byte[] result = mac.doFinal(base.readByteArray());
        String signature = ByteString.of(result).base64();

        String authorization = "OAuth " //
                + OAUTH_CONSUMER_KEY + "=\"" + consumerKeyValue + "\", " //
                + OAUTH_NONCE + "=\"" + oauthNonce + "\", " //
                + OAUTH_SIGNATURE + "=\"" + UrlEscapeUtils.escape(signature) + "\", " //
                + OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", " //
                + OAUTH_TIMESTAMP + "=\"" + oauthTimestamp + "\", " //
                + OAUTH_ACCESS_TOKEN + "=\"" + accessTokenValue + "\", " //
                + OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + '"';

        return request.newBuilder().addHeader("Authorization", authorization).build();
    }

    /** Simple builder class, to simplify interceptor initialization. */
    public static final class Builder {
        private String consumerKey;
        private String consumerSecret;
        private String accessToken;
        private String accessSecret;
        private Random random = new SecureRandom();
        private Clock clock = new Clock();

        public Builder consumerKey(String consumerKey) {
            this.consumerKey = Utils.checkNotNull(consumerKey, "consumerKey == null");
            return this;
        }

        public Builder consumerSecret(String consumerSecret) {
            this.consumerSecret = Utils.checkNotNull(consumerSecret, "consumerSecret == null");
            return this;
        }

        public Builder accessToken(String accessToken) {
            this.accessToken = Utils.checkNotNull(accessToken, "accessToken == null");
            return this;
        }

        public Builder accessSecret(String accessSecret) {
            this.accessSecret = Utils.checkNotNull(accessSecret, "accessSecret == null");
            return this;
        }

        public Builder random(Random random) {
            this.random = Utils.checkNotNull(random, "random == null");
            return this;
        }

        /** Set clock, this is required mainly for testing (Or when we will have full Java 8 support). */
        public Builder clock(Clock clock) {
            this.clock = Utils.checkNotNull(clock, "clock == null");
            return this;
        }

        public Oauth1SigningInterceptor build() {
            Utils.stateNotNull(consumerKey, "consumerKey not set");
            Utils.stateNotNull(consumerSecret, "consumerSecret not set");
            Utils.stateNotNull(accessToken, "accessToken not set");
            Utils.stateNotNull(accessSecret, "accessSecret not set");
            return new Oauth1SigningInterceptor(consumerKey, consumerSecret, accessToken, accessSecret, random,
                    clock);
        }
    }

    /** Simple clock like class, to allow time mocking. */
    @SuppressWarnings({ "MethodMayBeStatic", "MagicNumber" }) // Required for mocking.
    static class Clock {
        /** Returns the current time in milliseconds divided by 1K. */
        public String millis() {
            return Long.toString(System.currentTimeMillis() / 1000L);
        }
    }
}