Skip to content

Commit f01d861

Browse files
committed
OAuthSignatureCalculator mustn't re-encodes query params, close AsyncHttpClient#921
1 parent 8ee46ad commit f01d861

File tree

2 files changed

+112
-38
lines changed

2 files changed

+112
-38
lines changed

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

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,34 +128,33 @@ private String encodedParams(long oauthTimestamp, String nonce, List<Param> form
128128
OAuthParameterSet allParameters = new OAuthParameterSet(allParametersSize);
129129

130130
// start with standard OAuth parameters we need
131-
allParameters.add(KEY_OAUTH_CONSUMER_KEY, consumerAuth.getKey());
131+
allParameters.add(KEY_OAUTH_CONSUMER_KEY, Utf8UrlEncoder.encodeQueryElement(consumerAuth.getKey()));
132132
allParameters.add(KEY_OAUTH_NONCE, nonce);
133133
allParameters.add(KEY_OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD);
134134
allParameters.add(KEY_OAUTH_TIMESTAMP, String.valueOf(oauthTimestamp));
135135
if (userAuth.getKey() != null) {
136-
allParameters.add(KEY_OAUTH_TOKEN, userAuth.getKey());
136+
allParameters.add(KEY_OAUTH_TOKEN, Utf8UrlEncoder.encodeQueryElement(userAuth.getKey()));
137137
}
138138
allParameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0);
139139

140140
if (formParams != null) {
141141
for (Param param : formParams) {
142-
allParameters.add(param.getName(), param.getValue());
142+
// formParams are not already encoded
143+
allParameters.add(Utf8UrlEncoder.encodeQueryElement(param.getName()), Utf8UrlEncoder.encodeQueryElement(param.getValue()));
143144
}
144145
}
145146
if (queryParams != null) {
146147
for (Param param : queryParams) {
148+
// queryParams are already encoded
147149
allParameters.add(param.getName(), param.getValue());
148150
}
149151
}
150152
return allParameters.sortAndConcat();
151153
}
152154

153-
/**
154-
* Method for calculating OAuth signature using HMAC/SHA-1 method.
155-
*/
156-
public String calculateSignature(String method, Uri uri, long oauthTimestamp, String nonce,
155+
StringBuilder signatureBaseString(String method, Uri uri, long oauthTimestamp, String nonce,
157156
List<Param> formParams, List<Param> queryParams) {
158-
157+
159158
// beware: must generate first as we're using pooled StringBuilder
160159
String baseUrl = baseUrl(uri);
161160
String encodedParams = encodedParams(oauthTimestamp, nonce, formParams, queryParams);
@@ -169,6 +168,16 @@ public String calculateSignature(String method, Uri uri, long oauthTimestamp, St
169168
// and all that needs to be URL encoded (... again!)
170169
sb.append('&');
171170
Utf8UrlEncoder.encodeAndAppendQueryElement(sb, encodedParams);
171+
return sb;
172+
}
173+
174+
/**
175+
* Method for calculating OAuth signature using HMAC/SHA-1 method.
176+
*/
177+
public String calculateSignature(String method, Uri uri, long oauthTimestamp, String nonce,
178+
List<Param> formParams, List<Param> queryParams) {
179+
180+
StringBuilder sb = signatureBaseString(method, uri, oauthTimestamp, nonce, formParams, queryParams);
172181

173182
ByteBuffer rawBase = StringUtils.charSequence2ByteBuffer(sb, UTF_8);
174183
byte[] rawSignature = mac.digest(rawBase);
@@ -202,18 +211,18 @@ private String constructAuthHeader(String signature, String nonce, long oauthTim
202211
return sb.toString();
203212
}
204213

205-
protected synchronized String generateNonce() {
214+
protected long generateTimestamp() {
215+
return System.currentTimeMillis() / 1000L;
216+
}
217+
218+
protected String generateNonce() {
206219
byte[] nonceBuffer = NONCE_BUFFER.get();
207220
ThreadLocalRandom.current().nextBytes(nonceBuffer);
208221
// let's use base64 encoding over hex, slightly more compact than hex or decimals
209222
return Base64.encode(nonceBuffer);
210223
// return String.valueOf(Math.abs(random.nextLong()));
211224
}
212225

213-
protected long generateTimestamp() {
214-
return System.currentTimeMillis() / 1000L;
215-
}
216-
217226
/**
218227
* Container for parameters used for calculating OAuth signature.
219228
* About the only confusing aspect is that of whether entries are to be sorted
@@ -230,8 +239,7 @@ public OAuthParameterSet(int size) {
230239
}
231240

232241
public OAuthParameterSet add(String key, String value) {
233-
Parameter p = new Parameter(Utf8UrlEncoder.encodeQueryElement(key), Utf8UrlEncoder.encodeQueryElement(value));
234-
allParameters.add(p);
242+
allParameters.add(new Parameter(key, value));
235243
return this;
236244
}
237245

api/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.java

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
/**
3535
* Tests the OAuth signature behavior.
3636
*
37-
* See <a href="https://oauth.googlecode.com/svn/code/javascript/example/signature.html">Signature Tester</a> for an
38-
* online oauth signature checker.
37+
* See <a href=
38+
* "https://oauth.googlecode.com/svn/code/javascript/example/signature.html"
39+
* >Signature Tester</a> for an online oauth signature checker.
3940
*
4041
*/
4142
public class OAuthSignatureCalculatorTest {
@@ -52,16 +53,16 @@ public class OAuthSignatureCalculatorTest {
5253
final static long TIMESTAMP = 1191242096;
5354

5455
private static class StaticOAuthSignatureCalculator extends OAuthSignatureCalculator {
55-
56+
5657
private final long timestamp;
5758
private final String nonce;
58-
59+
5960
public StaticOAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth, long timestamp, String nonce) {
6061
super(consumerAuth, userAuth);
61-
this.timestamp = timestamp;
62+
this.timestamp = timestamp;
6263
this.nonce = nonce;
6364
}
64-
65+
6566
@Override
6667
protected long generateTimestamp() {
6768
return timestamp;
@@ -72,7 +73,64 @@ protected String generateNonce() {
7273
return nonce;
7374
}
7475
}
75-
76+
77+
// sample from RFC https://tools.ietf.org/html/rfc5849#section-3.4.1
78+
private void testSignatureBaseString(Request request) {
79+
ConsumerKey consumer = new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET);
80+
RequestToken user = new RequestToken("kkk9d7dh3k39sjv7", TOKEN_SECRET);
81+
OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user);
82+
83+
String signatureBaseString = calc.signatureBaseString(//
84+
request.getMethod(),//
85+
request.getUri(),//
86+
137131201,//
87+
"7d8f3e4a",//
88+
request.getFormParams(),//
89+
request.getQueryParams()).toString();
90+
91+
assertEquals(signatureBaseString, "POST&" //
92+
+ "http%3A%2F%2Fexample.com%2Frequest" //
93+
+ "&a2%3Dr%2520b%26"//
94+
+ "a3%3D2%2520q%26" + "a3%3Da%26"//
95+
+ "b5%3D%253D%25253D%26"//
96+
+ "c%2540%3D%26"//
97+
+ "c2%3D%26"//
98+
+ "oauth_consumer_key%3D9djdj82h48djs9d2%26"//
99+
+ "oauth_nonce%3D7d8f3e4a%26"//
100+
+ "oauth_signature_method%3DHMAC-SHA1%26"//
101+
+ "oauth_timestamp%3D137131201%26"//
102+
+ "oauth_token%3Dkkk9d7dh3k39sjv7%26"//
103+
+ "oauth_version%3D1.0");
104+
}
105+
106+
@Test(groups = "fast")
107+
public void testSignatureBaseStringWithProperlyEncodedUri() {
108+
109+
Request request = new RequestBuilder("POST")//
110+
.setUrl("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b")//
111+
.addFormParam("c2", "")//
112+
.addFormParam("a3", "2 q")//
113+
.build();
114+
115+
testSignatureBaseString(request);
116+
}
117+
118+
@Test(groups = "fast")
119+
public void testSignatureBaseStringWithRawUri() {
120+
121+
// note: @ is legal so don't decode it into %40 because it won't be
122+
// encoded back
123+
// note: we don't know how to fix a = that should have been encoded as
124+
// %3D but who would be stupid enough to do that?
125+
Request request = new RequestBuilder("POST")//
126+
.setUrl("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r b")//
127+
.addFormParam("c2", "")//
128+
.addFormParam("a3", "2 q")//
129+
.build();
130+
131+
testSignatureBaseString(request);
132+
}
133+
76134
// based on the reference test case from
77135
// http://oauth.pbwiki.com/TestCases
78136
@Test(groups = "fast")
@@ -99,16 +157,20 @@ public void testPostCalculateSignature() {
99157
formParams.add(new Param("file", "vacation.jpg"));
100158
formParams.add(new Param("size", "original"));
101159
String url = "http://photos.example.net/photos";
102-
final Request req = new RequestBuilder("POST")
103-
.setUri(Uri.create(url))
104-
.setFormParams(formParams)
105-
.setSignatureCalculator(calc).build();
160+
final Request req = new RequestBuilder("POST")//
161+
.setUri(Uri.create(url))//
162+
.setFormParams(formParams)//
163+
.setSignatureCalculator(calc)//
164+
.build();
106165

107166
// From the signature tester, POST should look like:
108-
// normalized parameters: file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
109-
// signature base string: POST&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
167+
// normalized parameters:
168+
// file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
169+
// signature base string:
170+
// POST&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
110171
// signature: wPkvxykrw+BTdCcGqKr+3I+PsiM=
111-
// header: OAuth realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D"
172+
// header: OAuth
173+
// realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D"
112174

113175
String authHeader = req.getHeaders().get("Authorization").get(0);
114176
Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader);
@@ -135,19 +197,23 @@ public void testGetWithRequestBuilder() {
135197
queryParams.add(new Param("size", "original"));
136198
String url = "http://photos.example.net/photos";
137199

138-
final Request req = new RequestBuilder("GET")
139-
.setUri(Uri.create(url))
140-
.setQueryParams(queryParams)
141-
.setSignatureCalculator(calc).build();
200+
final Request req = new RequestBuilder("GET")//
201+
.setUri(Uri.create(url))//
202+
.setQueryParams(queryParams)//
203+
.setSignatureCalculator(calc)//
204+
.build();
142205

143206
final List<Param> params = req.getQueryParams();
144207
assertEquals(params.size(), 2);
145-
208+
146209
// From the signature tester, the URL should look like:
147-
//normalized parameters: file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
148-
//signature base string: GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
149-
//signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM=
150-
//Authorization header: OAuth realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D"
210+
// normalized parameters:
211+
// file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
212+
// signature base string:
213+
// GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
214+
// signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM=
215+
// Authorization header: OAuth
216+
// realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D"
151217

152218
String authHeader = req.getHeaders().get("Authorization").get(0);
153219
Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader);

0 commit comments

Comments
 (0)