1 package com.moiseum.wolnelektury.connection.interceptors;
4 * Created by Piotr Ostrowski on 06.06.2018.
7 * Copyright (C) 2015 Jake Wharton
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 import android.support.annotation.NonNull;
23 import android.util.Log;
25 import com.google.gson.Gson;
26 import com.google.gson.JsonSyntaxException;
27 import com.moiseum.wolnelektury.connection.models.OAuthTokenModel;
29 import java.io.IOException;
30 import java.net.URLEncoder;
32 import java.util.Random;
33 import java.util.SortedMap;
34 import java.util.TreeMap;
36 import okhttp3.HttpUrl;
37 import okhttp3.Interceptor;
38 import okhttp3.MediaType;
39 import okhttp3.Request;
40 import okhttp3.RequestBody;
41 import okhttp3.Response;
42 import okhttp3.ResponseBody;
44 import okio.ByteString;
46 public final class OAuthSigningInterceptor implements Interceptor {
47 private static final String TAG = OAuthSigningInterceptor.class.getSimpleName();
48 private static final String REQUEST_TOKEN_HEADER = "Token-Requested";
49 private static final String AUTH_REQUIRED_HEADER = "Authentication-Required";
50 private static final String AUTHORIZATION_HEADER = "Authorization";
52 private static final String OAUTH_REALM = "realm=\"API\", ";
53 private static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
54 private static final String OAUTH_NONCE = "oauth_nonce";
55 private static final String OAUTH_SIGNATURE = "oauth_signature";
56 private static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
57 private static final String OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1";
58 private static final String OAUTH_TIMESTAMP = "oauth_timestamp";
59 private static final String OAUTH_ACCESS_TOKEN = "oauth_token";
60 private static final String OAUTH_VERSION = "oauth_version";
61 private static final String OAUTH_VERSION_VALUE = "1.0";
62 private static final long ONE_SECOND = 1000;
64 private final String consumerKey;
65 private final String consumerSecret;
66 private final Random random;
67 private String accessToken;
68 private String accessSecret;
70 public OAuthSigningInterceptor(String consumerKey, String consumerSecret, Random random) {
71 this.consumerKey = consumerKey;
72 this.consumerSecret = consumerSecret;
76 public void setToken(String accessToken, String accessSecret) {
77 this.accessToken = accessToken;
78 this.accessSecret = accessSecret;
82 public Response intercept(Chain chain) throws IOException {
83 if (chain.request().header(REQUEST_TOKEN_HEADER) != null) {
84 return handleRequestTokenRequest(chain);
85 } else if (chain.request().header(AUTH_REQUIRED_HEADER) != null || isSignedIn()) {
86 return chain.proceed(signRequest(chain.request()));
88 return chain.proceed(chain.request());
92 private boolean isSignedIn() {
93 return accessSecret != null && accessToken != null;
96 private Response handleRequestTokenRequest(Chain chain) throws IOException {
97 Response tokenResponse = chain.proceed(requestTokenRequest(chain.request()));
98 if (tokenResponse.isSuccessful() && tokenResponse.code() == 200 && tokenResponse.body() != null) {
99 String jsonResponse = paramJson(tokenResponse.body().string());
101 Gson gson = new Gson();
102 OAuthTokenModel tokenModel = gson.fromJson(jsonResponse, OAuthTokenModel.class);
103 accessToken = tokenModel.getToken();
104 accessSecret = tokenModel.getTokenSecret();
105 return tokenResponse.newBuilder().body(ResponseBody.create(MediaType.parse("application/json"), jsonResponse)).build();
106 } catch (JsonSyntaxException e) {
107 Log.v(TAG, "Failed to parse Oauth Request Token response.", e);
110 return tokenResponse;
113 private Request signRequest(Request request) throws IOException {
114 if (accessToken == null || accessSecret == null) {
115 Log.e(TAG, "Missing authentication tokens, passing request unsigned.");
119 SortedMap<String, String> parameters = getOAuthParams(request.url(), request.body());
121 String baseUrl = request.url().newBuilder().query(null).build().toString();
122 ByteString baseString = getBaseString(request.method(), baseUrl, parameters);
123 String signingKey = utf8(consumerSecret) + "&" + (accessSecret != null ? utf8(accessSecret) : "");
124 String signature = baseString.hmacSha1(ByteString.of(signingKey.getBytes())).base64();
126 String authorization = "OAuth " + OAUTH_REALM
127 + OAUTH_CONSUMER_KEY + "=\"" + parameters.get(OAUTH_CONSUMER_KEY) + "\", "
128 + OAUTH_NONCE + "=\"" + parameters.get(OAUTH_NONCE) + "\", "
129 + OAUTH_SIGNATURE + "=\"" + signature + "\", "
130 + OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", "
131 + OAUTH_TIMESTAMP + "=\"" + parameters.get(OAUTH_TIMESTAMP) + "\", "
132 + OAUTH_ACCESS_TOKEN + "=\"" + accessToken + "\", "
133 + OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + "\"";
135 return request.newBuilder()
136 .addHeader(AUTHORIZATION_HEADER, authorization)
140 private Request requestTokenRequest(Request request) throws IOException {
141 SortedMap<String, String> parameters = getOAuthParams(request.url(), request.body());
143 String baseUrl = request.url().newBuilder().query(null).build().toString();
144 ByteString baseString = getBaseString(request.method(), baseUrl, parameters);
145 String signingKey = utf8(consumerSecret) + "&" + (accessSecret != null ? utf8(accessSecret) : "");
146 String signature = baseString.hmacSha1(ByteString.of(signingKey.getBytes())).base64();
148 HttpUrl.Builder urlBuilder = request.url().newBuilder()
149 .addQueryParameter(OAUTH_CONSUMER_KEY, parameters.get(OAUTH_CONSUMER_KEY))
150 .addQueryParameter(OAUTH_NONCE, parameters.get(OAUTH_NONCE))
151 .addQueryParameter(OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD_VALUE)
152 .addQueryParameter(OAUTH_TIMESTAMP, parameters.get(OAUTH_TIMESTAMP))
153 .addQueryParameter(OAUTH_VERSION, OAUTH_VERSION_VALUE)
154 .addQueryParameter(OAUTH_SIGNATURE, signature);
155 if (accessToken != null) {
156 urlBuilder.addQueryParameter(OAUTH_ACCESS_TOKEN, accessToken);
158 HttpUrl requestUrl = urlBuilder.build();
160 return request.newBuilder().url(requestUrl).build();
163 private SortedMap<String, String> getOAuthParams(HttpUrl url, RequestBody requestBody) throws IOException {
164 byte[] nonce = new byte[32];
165 random.nextBytes(nonce);
167 String oauthNonce = ByteString.of(nonce).base64().replaceAll("\\W", "");
168 String oauthTimestamp = String.valueOf(System.currentTimeMillis() / ONE_SECOND);
170 SortedMap<String, String> parameters = new TreeMap<>();
171 parameters.put(OAUTH_CONSUMER_KEY, utf8(consumerKey));
172 parameters.put(OAUTH_NONCE, oauthNonce);
173 parameters.put(OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD_VALUE);
174 parameters.put(OAUTH_TIMESTAMP, oauthTimestamp);
175 parameters.put(OAUTH_VERSION, OAUTH_VERSION_VALUE);
176 if (accessToken != null) {
177 parameters.put(OAUTH_ACCESS_TOKEN, accessToken);
180 // Adding query params
181 for (int i = 0; i < url.querySize(); i++) {
182 parameters.put(utf8(url.queryParameterName(i)), utf8(url.queryParameterValue(i)));
185 // Adding body params
186 if (requestBody != null) {
187 Buffer body = new Buffer();
188 requestBody.writeTo(body);
190 while (!body.exhausted()) {
191 long keyEnd = body.indexOf((byte) '=');
193 throw new IllegalStateException("Key with no value: " + body.readUtf8());
195 String key = body.readUtf8(keyEnd);
196 body.skip(1); // Equals.
198 long valueEnd = body.indexOf((byte) '&');
199 String value = valueEnd == -1 ? body.readUtf8() : body.readUtf8(valueEnd);
200 if (valueEnd != -1) {
201 body.skip(1); // Ampersand.
204 parameters.put(key, value);
212 private ByteString getBaseString(String method, String baseUrl, SortedMap<String, String> parameters) throws IOException {
213 Buffer base = new Buffer();
214 base.writeUtf8(method);
216 base.writeUtf8(utf8(baseUrl));
219 boolean first = true;
220 for (Map.Entry<String, String> entry : parameters.entrySet()) {
222 base.writeUtf8(utf8("&"));
225 base.writeUtf8(utf8(entry.getKey()));
226 base.writeUtf8(utf8("="));
227 base.writeUtf8(utf8(entry.getValue()));
229 return ByteString.of(base.readByteArray());
232 private String utf8(String escapedString) throws IOException {
233 return URLEncoder.encode(escapedString, "UTF-8")
236 .replace("%7E", "~");
239 private String paramJson(String paramIn) {
240 paramIn = paramIn.replaceAll("=", "\":\"");
241 paramIn = paramIn.replaceAll("&", "\",\"");
242 return "{\"" + paramIn + "\"}";