Skip to content

Commit e01064d

Browse files
juherrkullfar
authored andcommitted
Manage OAuth2 error response for access token request (scribejava#696)
1 parent 9fd62ea commit e01064d

11 files changed

Lines changed: 221 additions & 58 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.github.scribejava.core.exceptions;
2+
3+
import com.github.scribejava.core.model.OAuth2ErrorResponse;
4+
5+
public class OAuthRequestException extends OAuthException {
6+
7+
private final OAuth2ErrorResponse error;
8+
9+
public OAuthRequestException(String message, OAuth2ErrorResponse error) {
10+
super(message);
11+
this.error = error;
12+
}
13+
14+
public OAuth2ErrorResponse getError() {
15+
return error;
16+
}
17+
}

scribejava-core/src/main/java/com/github/scribejava/core/extractors/AbstractOAuth1TokenExtractor.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.github.scribejava.core.extractors;
22

3+
import java.io.IOException;
34
import java.util.regex.Matcher;
45
import java.util.regex.Pattern;
56
import com.github.scribejava.core.exceptions.OAuthException;
67
import com.github.scribejava.core.model.OAuth1Token;
8+
import com.github.scribejava.core.model.Response;
79
import com.github.scribejava.core.utils.OAuthEncoder;
810
import com.github.scribejava.core.utils.Preconditions;
911

@@ -23,12 +25,13 @@ public abstract class AbstractOAuth1TokenExtractor<T extends OAuth1Token> implem
2325
* {@inheritDoc}
2426
*/
2527
@Override
26-
public T extract(String response) {
27-
Preconditions.checkEmptyString(response,
28+
public T extract(Response response) throws IOException {
29+
final String body = response.getBody();
30+
Preconditions.checkEmptyString(body,
2831
"Response body is incorrect. Can't extract a token from an empty string");
29-
final String token = extract(response, Pattern.compile(OAUTH_TOKEN_REGEXP));
30-
final String secret = extract(response, Pattern.compile(OAUTH_TOKEN_SECRET_REGEXP));
31-
return createToken(token, secret, response);
32+
final String token = extract(body, Pattern.compile(OAUTH_TOKEN_REGEXP));
33+
final String secret = extract(body, Pattern.compile(OAUTH_TOKEN_SECRET_REGEXP));
34+
return createToken(token, secret, body);
3235
}
3336

3437
private String extract(String response, Pattern p) {

scribejava-core/src/main/java/com/github/scribejava/core/extractors/OAuth2AccessTokenExtractor.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.github.scribejava.core.extractors;
22

3+
import java.io.IOException;
34
import java.util.regex.Matcher;
45
import java.util.regex.Pattern;
56
import com.github.scribejava.core.exceptions.OAuthException;
67
import com.github.scribejava.core.model.OAuth2AccessToken;
8+
import com.github.scribejava.core.model.Response;
79
import com.github.scribejava.core.utils.OAuthEncoder;
810
import com.github.scribejava.core.utils.Preconditions;
911

@@ -34,23 +36,24 @@ public static OAuth2AccessTokenExtractor instance() {
3436
* {@inheritDoc}
3537
*/
3638
@Override
37-
public OAuth2AccessToken extract(String response) {
38-
Preconditions.checkEmptyString(response,
39+
public OAuth2AccessToken extract(Response response) throws IOException {
40+
final String body = response.getBody();
41+
Preconditions.checkEmptyString(body,
3942
"Response body is incorrect. Can't extract a token from an empty string");
4043

41-
final String accessToken = extractParameter(response, ACCESS_TOKEN_REGEX, true);
42-
final String tokenType = extractParameter(response, TOKEN_TYPE_REGEX, false);
43-
final String expiresInString = extractParameter(response, EXPIRES_IN_REGEX, false);
44+
final String accessToken = extractParameter(body, ACCESS_TOKEN_REGEX, true);
45+
final String tokenType = extractParameter(body, TOKEN_TYPE_REGEX, false);
46+
final String expiresInString = extractParameter(body, EXPIRES_IN_REGEX, false);
4447
Integer expiresIn;
4548
try {
4649
expiresIn = expiresInString == null ? null : Integer.valueOf(expiresInString);
4750
} catch (NumberFormatException nfe) {
4851
expiresIn = null;
4952
}
50-
final String refreshToken = extractParameter(response, REFRESH_TOKEN_REGEX, false);
51-
final String scope = extractParameter(response, SCOPE_REGEX, false);
53+
final String refreshToken = extractParameter(body, REFRESH_TOKEN_REGEX, false);
54+
final String scope = extractParameter(body, SCOPE_REGEX, false);
5255

53-
return new OAuth2AccessToken(accessToken, tokenType, expiresIn, refreshToken, scope, response);
56+
return new OAuth2AccessToken(accessToken, tokenType, expiresIn, refreshToken, scope, body);
5457
}
5558

5659
private static String extractParameter(String response, String regex, boolean required) throws OAuthException {

scribejava-core/src/main/java/com/github/scribejava/core/extractors/OAuth2AccessTokenJsonExtractor.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.github.scribejava.core.extractors;
22

3+
import java.io.IOException;
4+
import java.net.URI;
35
import java.util.regex.Matcher;
46
import java.util.regex.Pattern;
57
import com.github.scribejava.core.exceptions.OAuthException;
68
import com.github.scribejava.core.model.OAuth2AccessToken;
9+
import com.github.scribejava.core.model.OAuth2ErrorResponse;
10+
import com.github.scribejava.core.model.Response;
711
import com.github.scribejava.core.utils.Preconditions;
812

913
/**
@@ -16,6 +20,9 @@ public class OAuth2AccessTokenJsonExtractor implements TokenExtractor<OAuth2Acce
1620
private static final String EXPIRES_IN_REGEX = "\"expires_in\"\\s*:\\s*\"?(\\d*?)\"?\\D";
1721
private static final String REFRESH_TOKEN_REGEX = "\"refresh_token\"\\s*:\\s*\"(\\S*?)\"";
1822
private static final String SCOPE_REGEX = "\"scope\"\\s*:\\s*\"(\\S*?)\"";
23+
private static final String ERROR_REGEX = "\"error\"\\s*:\\s*\"(\\S*?)\"";
24+
private static final String ERROR_DESCRIPTION_REGEX = "\"error_description\"\\s*:\\s*\"([^\"]*?)\"";
25+
private static final String ERROR_URI_REGEX = "\"error_uri\"\\s*:\\s*\"(\\S*?)\"";
1926

2027
protected OAuth2AccessTokenJsonExtractor() {
2128
}
@@ -30,10 +37,34 @@ public static OAuth2AccessTokenJsonExtractor instance() {
3037
}
3138

3239
@Override
33-
public OAuth2AccessToken extract(String response) {
34-
Preconditions.checkEmptyString(response,
40+
public OAuth2AccessToken extract(Response response) throws IOException {
41+
final String body = response.getBody();
42+
Preconditions.checkEmptyString(body,
3543
"Response body is incorrect. Can't extract a token from an empty string");
3644

45+
if (response.getCode() != 200) {
46+
generateError(response.getBody());
47+
}
48+
return createToken(body);
49+
}
50+
51+
// Related documentation: https://tools.ietf.org/html/rfc6749#section-5.2
52+
private static void generateError(String response) {
53+
final String errorInString = extractParameter(response, ERROR_REGEX, true);
54+
final String errorDescription = extractParameter(response, ERROR_DESCRIPTION_REGEX, false);
55+
final String errorUriInString = extractParameter(response, ERROR_URI_REGEX, false);
56+
URI errorUri;
57+
try {
58+
errorUri = errorUriInString == null ? null : URI.create(errorUriInString);
59+
} catch (IllegalArgumentException iae) {
60+
errorUri = null;
61+
}
62+
63+
throw new OAuth2ErrorResponse(OAuth2ErrorResponse.OAuthError.valueOf(errorInString), errorDescription,
64+
errorUri, response);
65+
}
66+
67+
private OAuth2AccessToken createToken(String response) {
3768
final String accessToken = extractParameter(response, ACCESS_TOKEN_REGEX, true);
3869
final String tokenType = extractParameter(response, TOKEN_TYPE_REGEX, false);
3970
final String expiresInString = extractParameter(response, EXPIRES_IN_REGEX, false);

scribejava-core/src/main/java/com/github/scribejava/core/extractors/TokenExtractor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.github.scribejava.core.extractors;
22

3+
import com.github.scribejava.core.exceptions.OAuthException;
4+
import com.github.scribejava.core.model.Response;
35
import com.github.scribejava.core.model.Token;
46

7+
import java.io.IOException;
8+
59
/**
610
* Simple command object that extracts a concrete {@link Token} from a String
711
* @param <T> concrete type of Token
@@ -14,5 +18,5 @@ public interface TokenExtractor<T extends Token> {
1418
* @param response the contents of the response
1519
* @return OAuth access token
1620
*/
17-
T extract(String response);
21+
T extract(Response response) throws IOException, OAuthException;
1822
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.github.scribejava.core.model;
2+
3+
import com.github.scribejava.core.exceptions.OAuthException;
4+
5+
import java.net.URI;
6+
7+
/**
8+
* Representing <a href="https://tools.ietf.org/html/rfc6749#section-5.2">"5.2. Error Response"</a>
9+
*/
10+
public class OAuth2ErrorResponse extends OAuthException {
11+
12+
public enum OAuthError {
13+
invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope
14+
}
15+
16+
private final OAuthError error;
17+
private final String errorDescription;
18+
private final URI errorUri;
19+
private final String rawResponse;
20+
21+
public OAuth2ErrorResponse(OAuthError error, String errorDescription, URI errorUri, String rawResponse) {
22+
super(generateMessage(error, errorDescription, errorUri, rawResponse));
23+
if (error == null) {
24+
throw new IllegalArgumentException("error must not be null");
25+
}
26+
this.error = error;
27+
this.errorDescription = errorDescription;
28+
this.errorUri = errorUri;
29+
this.rawResponse = rawResponse;
30+
}
31+
32+
private static String generateMessage(OAuthError error, String errorDescription, URI errorUri, String rawResponse) {
33+
return rawResponse;
34+
}
35+
36+
public OAuthError getError() {
37+
return error;
38+
}
39+
40+
public String getErrorDescription() {
41+
return errorDescription;
42+
}
43+
44+
public URI getErrorUri() {
45+
return errorUri;
46+
}
47+
48+
public String getRawResponse() {
49+
return rawResponse;
50+
}
51+
}

scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public final OAuth1RequestToken getRequestToken() throws IOException {
4848

4949
config.log("response status code: " + response.getCode());
5050
config.log("response body: " + body);
51-
return api.getRequestTokenExtractor().extract(body);
51+
return api.getRequestTokenExtractor().extract(response);
5252
}
5353

5454
public final Future<OAuth1RequestToken> getRequestTokenAsync(
@@ -61,7 +61,7 @@ public final Future<OAuth1RequestToken> getRequestTokenAsync(
6161
return request.sendAsync(callback, new OAuthRequestAsync.ResponseConverter<OAuth1RequestToken>() {
6262
@Override
6363
public OAuth1RequestToken convert(Response response) throws IOException {
64-
return getApi().getRequestTokenExtractor().extract(response.getBody());
64+
return getApi().getRequestTokenExtractor().extract(response);
6565
}
6666
});
6767
}
@@ -97,7 +97,7 @@ public final OAuth1AccessToken getAccessToken(OAuth1RequestToken requestToken, S
9797
final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint(), this);
9898
prepareAccessTokenRequest(request, requestToken, oauthVerifier);
9999
final Response response = request.send();
100-
return api.getAccessTokenExtractor().extract(response.getBody());
100+
return api.getAccessTokenExtractor().extract(response);
101101
}
102102

103103
/**
@@ -119,7 +119,7 @@ public final Future<OAuth1AccessToken> getAccessTokenAsync(OAuth1RequestToken re
119119
return request.sendAsync(callback, new OAuthRequestAsync.ResponseConverter<OAuth1AccessToken>() {
120120
@Override
121121
public OAuth1AccessToken convert(Response response) throws IOException {
122-
return getApi().getAccessTokenExtractor().extract(response.getBody());
122+
return getApi().getAccessTokenExtractor().extract(response);
123123
}
124124
});
125125
}

scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth20Service.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public OAuth20Service(DefaultApi20 api, OAuthConfig config) {
3434

3535
//sync version, protected to facilitate mocking
3636
protected OAuth2AccessToken sendAccessTokenRequestSync(OAuthRequest request) throws IOException {
37-
return api.getAccessTokenExtractor().extract(request.send().getBody());
37+
return api.getAccessTokenExtractor().extract(request.send());
3838
}
3939

4040
//async version, protected to facilitate mocking
@@ -44,7 +44,7 @@ protected Future<OAuth2AccessToken> sendAccessTokenRequestAsync(OAuthRequestAsyn
4444
return request.sendAsync(callback, new OAuthRequestAsync.ResponseConverter<OAuth2AccessToken>() {
4545
@Override
4646
public OAuth2AccessToken convert(Response response) throws IOException {
47-
return getApi().getAccessTokenExtractor().extract(response.getBody());
47+
return getApi().getAccessTokenExtractor().extract(response);
4848
}
4949
});
5050
}
Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.github.scribejava.core.extractors;
22

3+
import com.github.scribejava.core.model.Response;
34
import org.junit.Before;
45
import org.junit.Test;
56
import com.github.scribejava.core.exceptions.OAuthException;
67
import com.github.scribejava.core.model.OAuth1Token;
8+
9+
import java.io.IOException;
10+
import java.util.Collections;
11+
712
import static org.junit.Assert.assertEquals;
813

914
public class OAuth1AccessTokenExtractorTest {
@@ -16,58 +21,63 @@ public void setUp() {
1621
}
1722

1823
@Test
19-
public void shouldExtractTokenFromOAuthStandardResponse() {
24+
public void shouldExtractTokenFromOAuthStandardResponse() throws IOException {
2025
final String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03";
21-
final OAuth1Token extracted = extractor.extract(response);
26+
final OAuth1Token extracted = extractor.extract(ok(response));
2227
assertEquals("hh5s93j4hdidpola", extracted.getToken());
2328
assertEquals("hdhd0244k9j7ao03", extracted.getTokenSecret());
2429
}
2530

2631
@Test
27-
public void shouldExtractTokenFromInvertedOAuthStandardResponse() {
32+
public void shouldExtractTokenFromInvertedOAuthStandardResponse() throws IOException {
2833
final String response = "oauth_token_secret=hh5s93j4hdidpola&oauth_token=hdhd0244k9j7ao03";
29-
final OAuth1Token extracted = extractor.extract(response);
34+
final OAuth1Token extracted = extractor.extract(ok(response));
3035
assertEquals("hh5s93j4hdidpola", extracted.getTokenSecret());
3136
assertEquals("hdhd0244k9j7ao03", extracted.getToken());
3237
}
3338

3439
@Test
35-
public void shouldExtractTokenFromResponseWithCallbackConfirmed() {
40+
public void shouldExtractTokenFromResponseWithCallbackConfirmed() throws IOException {
3641
final String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03"
3742
+ "&callback_confirmed=true";
38-
final OAuth1Token extracted = extractor.extract(response);
43+
final OAuth1Token extracted = extractor.extract(ok(response));
3944
assertEquals("hh5s93j4hdidpola", extracted.getToken());
4045
assertEquals("hdhd0244k9j7ao03", extracted.getTokenSecret());
4146
}
4247

4348
@Test
44-
public void shouldExtractTokenWithEmptySecret() {
49+
public void shouldExtractTokenWithEmptySecret() throws IOException {
4550
final String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=";
46-
final OAuth1Token extracted = extractor.extract(response);
51+
final OAuth1Token extracted = extractor.extract(ok(response));
4752
assertEquals("hh5s93j4hdidpola", extracted.getToken());
4853
assertEquals("", extracted.getTokenSecret());
4954
}
5055

5156
@Test(expected = OAuthException.class)
52-
public void shouldThrowExceptionIfTokenIsAbsent() {
57+
public void shouldThrowExceptionIfTokenIsAbsent() throws IOException {
5358
final String response = "oauth_secret=hh5s93j4hdidpola&callback_confirmed=true";
54-
extractor.extract(response);
59+
extractor.extract(ok(response));
5560
}
5661

5762
@Test(expected = OAuthException.class)
58-
public void shouldThrowExceptionIfSecretIsAbsent() {
63+
public void shouldThrowExceptionIfSecretIsAbsent() throws IOException {
5964
final String response = "oauth_token=hh5s93j4hdidpola&callback_confirmed=true";
60-
extractor.extract(response);
65+
extractor.extract(ok(response));
6166
}
6267

6368
@Test(expected = IllegalArgumentException.class)
64-
public void shouldThrowExceptionIfResponseIsNull() {
65-
extractor.extract(null);
69+
public void shouldThrowExceptionIfResponseIsNull() throws IOException {
70+
extractor.extract(ok(null));
6671
}
6772

6873
@Test(expected = IllegalArgumentException.class)
69-
public void shouldThrowExceptionIfResponseIsEmptyString() {
74+
public void shouldThrowExceptionIfResponseIsEmptyString() throws IOException {
7075
final String response = "";
71-
extractor.extract(response);
76+
extractor.extract(ok(response));
77+
}
78+
79+
private static Response ok(String body) {
80+
return new Response(200, /* message */ null, /* headers */ Collections.<String, String>emptyMap(),
81+
body, /* stream */ null);
7282
}
7383
}

0 commit comments

Comments
 (0)