Skip to content

Commit 0bf50df

Browse files
committed
Fox OAuth 1.0 star encoding, close AsyncHttpClient#1216
1 parent df829dc commit 0bf50df

File tree

2 files changed

+148
-172
lines changed

2 files changed

+148
-172
lines changed

client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java

Lines changed: 79 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
/*
2-
* Copyright 2010 Ning, Inc.
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
33
*
4-
* This program is licensed to you under the Apache License, version 2.0
5-
* (the "License"); you may not use this file except in compliance with the
6-
* License. You may obtain a copy of the License at:
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12-
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13-
* License for the specific language governing permissions and limitations
14-
* under the License.
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
158
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
1613
*/
1714
package org.asynchttpclient.oauth;
1815

@@ -24,6 +21,7 @@
2421
import java.util.Arrays;
2522
import java.util.List;
2623
import java.util.concurrent.ThreadLocalRandom;
24+
import java.util.regex.Pattern;
2725

2826
import org.asynchttpclient.Param;
2927
import org.asynchttpclient.Request;
@@ -35,12 +33,9 @@
3533
import org.asynchttpclient.util.Utf8UrlEncoder;
3634

3735
/**
38-
* Simple OAuth signature calculator that can used for constructing client signatures
39-
* for accessing services that use OAuth for authorization.
40-
* <br>
41-
* Supports most common signature inclusion and calculation methods: HMAC-SHA1 for
42-
* calculation, and Header inclusion as inclusion method. Nonce generation uses
43-
* simple random numbers with base64 encoding.
36+
* Simple OAuth signature calculator that can used for constructing client signatures for accessing services that use OAuth for authorization. <br>
37+
* Supports most common signature inclusion and calculation methods: HMAC-SHA1 for calculation, and Header inclusion as inclusion method. Nonce generation uses simple random
38+
* numbers with base64 encoding.
4439
*
4540
* @author tatu ([email protected])
4641
*/
@@ -72,7 +67,7 @@ protected byte[] initialValue() {
7267

7368
/**
7469
* @param consumerAuth Consumer key to use for signature calculation
75-
* @param userAuth Request/access token to use for signature calculation
70+
* @param userAuth Request/access token to use for signature calculation
7671
*/
7772
public OAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth) {
7873
mac = new ThreadSafeHMAC(consumerAuth, userAuth);
@@ -84,47 +79,16 @@ public OAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth)
8479
public void calculateAndAddSignature(Request request, RequestBuilderBase<?> requestBuilder) {
8580
String nonce = generateNonce();
8681
long timestamp = generateTimestamp();
87-
String signature = calculateSignature(request.getMethod(), request.getUri(), timestamp, nonce, request.getFormParams(), request.getQueryParams());
82+
String signature = calculateSignature(request, timestamp, nonce);
8883
String headerValue = constructAuthHeader(signature, nonce, timestamp);
8984
requestBuilder.setHeader(HEADER_AUTHORIZATION, headerValue);
9085
}
9186

92-
private String baseUrl(Uri uri) {
93-
/* 07-Oct-2010, tatu: URL may contain default port number; if so, need to extract
94-
* from base URL.
95-
*/
96-
String scheme = uri.getScheme();
97-
98-
StringBuilder sb = StringUtils.stringBuilder();
99-
sb.append(scheme).append("://").append(uri.getHost());
100-
101-
int port = uri.getPort();
102-
if (scheme.equals("http")) {
103-
if (port == 80)
104-
port = -1;
105-
} else if (scheme.equals("https")) {
106-
if (port == 443)
107-
port = -1;
108-
}
109-
110-
if (port != -1)
111-
sb.append(':').append(port);
112-
113-
if (isNonEmpty(uri.getPath()))
114-
sb.append(uri.getPath());
115-
116-
return sb.toString();
117-
}
118-
11987
private String encodedParams(long oauthTimestamp, String nonce, List<Param> formParams, List<Param> queryParams) {
12088
/**
121-
* List of all query and form parameters added to this request; needed
122-
* for calculating request signature
89+
* List of all query and form parameters added to this request; needed for calculating request signature
12390
*/
124-
int allParametersSize = 5
125-
+ (userAuth.getKey() != null ? 1 : 0)
126-
+ (formParams != null ? formParams.size() : 0)
127-
+ (queryParams != null ? queryParams.size() : 0);
91+
int allParametersSize = 5 + (userAuth.getKey() != null ? 1 : 0) + (formParams != null ? formParams.size() : 0) + (queryParams != null ? queryParams.size() : 0);
12892
OAuthParameterSet allParameters = new OAuthParameterSet(allParametersSize);
12993

13094
// start with standard OAuth parameters we need
@@ -145,47 +109,72 @@ private String encodedParams(long oauthTimestamp, String nonce, List<Param> form
145109
}
146110
if (queryParams != null) {
147111
for (Param param : queryParams) {
148-
// queryParams are already encoded
149-
allParameters.add(param.getName(), param.getValue());
112+
// queryParams are already form-url-encoded
113+
// but OAuth1 uses RFC3986_UNRESERVED_CHARS so * and + have to be encoded
114+
allParameters.add(percentEncodeAlreadyFormUrlEncoded(param.getName()), percentEncodeAlreadyFormUrlEncoded(param.getValue()));
150115
}
151116
}
152117
return allParameters.sortAndConcat();
153118
}
154119

155-
StringBuilder signatureBaseString(String method, Uri uri, long oauthTimestamp, String nonce,
156-
List<Param> formParams, List<Param> queryParams) {
157-
120+
private String baseUrl(Uri uri) {
121+
/*
122+
* 07-Oct-2010, tatu: URL may contain default port number; if so, need to remove from base URL.
123+
*/
124+
String scheme = uri.getScheme();
125+
126+
StringBuilder sb = StringUtils.stringBuilder();
127+
sb.append(scheme).append("://").append(uri.getHost());
128+
129+
int port = uri.getPort();
130+
if (scheme.equals("http")) {
131+
if (port == 80)
132+
port = -1;
133+
} else if (scheme.equals("https")) {
134+
if (port == 443)
135+
port = -1;
136+
}
137+
138+
if (port != -1)
139+
sb.append(':').append(port);
140+
141+
if (isNonEmpty(uri.getPath()))
142+
sb.append(uri.getPath());
143+
144+
return sb.toString();
145+
}
146+
147+
private static final Pattern STAR_CHAR_PATTERN = Pattern.compile("*", Pattern.LITERAL);
148+
private static final Pattern PLUS_CHAR_PATTERN = Pattern.compile("+", Pattern.LITERAL);
149+
private static final Pattern ENCODED_TILDE_PATTERN = Pattern.compile("%7E", Pattern.LITERAL);
150+
151+
private String percentEncodeAlreadyFormUrlEncoded(String s) {
152+
s = STAR_CHAR_PATTERN.matcher(s).replaceAll("%2A");
153+
s = PLUS_CHAR_PATTERN.matcher(s).replaceAll("%20");
154+
s = ENCODED_TILDE_PATTERN.matcher(s).replaceAll("~");
155+
return s;
156+
}
157+
158+
StringBuilder signatureBaseString(Request request, long oauthTimestamp, String nonce) {
159+
158160
// beware: must generate first as we're using pooled StringBuilder
159-
String baseUrl = baseUrl(uri);
160-
String encodedParams = encodedParams(oauthTimestamp, nonce, formParams, queryParams);
161+
String baseUrl = baseUrl(request.getUri());
162+
String encodedParams = encodedParams(oauthTimestamp, nonce, request.getFormParams(), request.getQueryParams());
161163

162164
StringBuilder sb = StringUtils.stringBuilder();
163-
sb.append(method); // POST / GET etc (nothing to URL encode)
165+
sb.append(request.getMethod()); // POST / GET etc (nothing to URL encode)
164166
sb.append('&');
165167
Utf8UrlEncoder.encodeAndAppendQueryElement(sb, baseUrl);
166168

167-
168169
// and all that needs to be URL encoded (... again!)
169170
sb.append('&');
170171
Utf8UrlEncoder.encodeAndAppendQueryElement(sb, encodedParams);
171172
return sb;
172173
}
173-
174-
/**
175-
* Method for calculating OAuth signature using HMAC/SHA-1 method.
176-
*
177-
* @param method the request methode
178-
* @param uri the request Uri
179-
* @param oauthTimestamp the timestamp
180-
* @param nonce the nonce
181-
* @param formParams the formParams
182-
* @param queryParams the query params
183-
* @return the signature
184-
*/
185-
public String calculateSignature(String method, Uri uri, long oauthTimestamp, String nonce,
186-
List<Param> formParams, List<Param> queryParams) {
187174

188-
StringBuilder sb = signatureBaseString(method, uri, oauthTimestamp, nonce, formParams, queryParams);
175+
String calculateSignature(Request request, long oauthTimestamp, String nonce) {
176+
177+
StringBuilder sb = signatureBaseString(request, oauthTimestamp, nonce);
189178

190179
ByteBuffer rawBase = StringUtils.charSequence2ByteBuffer(sb, UTF_8);
191180
byte[] rawSignature = mac.digest(rawBase);
@@ -225,16 +214,13 @@ protected String generateNonce() {
225214
ThreadLocalRandom.current().nextBytes(nonceBuffer);
226215
// let's use base64 encoding over hex, slightly more compact than hex or decimals
227216
return Base64.encode(nonceBuffer);
228-
// return String.valueOf(Math.abs(random.nextLong()));
217+
// return String.valueOf(Math.abs(random.nextLong()));
229218
}
230219

231220
/**
232-
* Container for parameters used for calculating OAuth signature.
233-
* About the only confusing aspect is that of whether entries are to be sorted
234-
* before encoded or vice versa: if my reading is correct, encoding is to occur
235-
* first, then sorting; although this should rarely matter (since sorting is primary
236-
* by key, which usually has nothing to encode)... of course, rarely means that
237-
* when it would occur it'd be harder to track down.
221+
* Container for parameters used for calculating OAuth signature. About the only confusing aspect is that of whether entries are to be sorted before encoded or vice versa: if
222+
* my reading is correct, encoding is to occur first, then sorting; although this should rarely matter (since sorting is primary by key, which usually has nothing to encode)...
223+
* of course, rarely means that when it would occur it'd be harder to track down.
238224
*/
239225
final static class OAuthParameterSet {
240226
private final ArrayList<Parameter> allParameters;
@@ -269,6 +255,7 @@ public String sortAndConcat() {
269255
* Helper class for sorting query and form parameters that we need
270256
*/
271257
final static class Parameter implements Comparable<Parameter> {
258+
272259
private final String key, value;
273260

274261
public Parameter(String key, String value) {
@@ -300,13 +287,17 @@ public String toString() {
300287

301288
@Override
302289
public boolean equals(Object o) {
303-
if (this == o) return true;
304-
if (o == null || getClass() != o.getClass()) return false;
290+
if (this == o)
291+
return true;
292+
if (o == null || getClass() != o.getClass())
293+
return false;
305294

306295
Parameter parameter = (Parameter) o;
307296

308-
if (!key.equals(parameter.key)) return false;
309-
if (!value.equals(parameter.value)) return false;
297+
if (!key.equals(parameter.key))
298+
return false;
299+
if (!value.equals(parameter.value))
300+
return false;
310301

311302
return true;
312303
}

0 commit comments

Comments
 (0)