Skip to content

Commit 8a1ccb0

Browse files
committed
Support Scope in OAuth flow and authentication substate.
1 parent cc2a624 commit 8a1ccb0

File tree

10 files changed

+217
-29
lines changed

10 files changed

+217
-29
lines changed

examples/authorize-beta/src/main/java/com/dropbox/core/examples/authorize_beta/Main.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public static void main(String[] args) throws IOException {
104104
System.out.println("- Access Token: " + authFinish.getAccessToken());
105105
System.out.println("- Expires At: " + authFinish.getExpiresAt());
106106
System.out.println("- Refresh Token: " + authFinish.getRefreshToken());
107+
System.out.println("- Scope: " + authFinish.getScope());
107108

108109
// Save auth information the new DbxCredential instance. It also contains app_key and
109110
// app_secret which is required to do refresh call.

src/main/java/com/dropbox/core/DbxAuthFinish.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class DbxAuthFinish {
2626
private final String teamId;
2727
private final /*@Nullable*/String urlState;
2828
private long issueTime;
29+
private final String scope;
2930

3031
/**
3132
* @param accessToken OAuth access token
@@ -55,6 +56,30 @@ public DbxAuthFinish(String accessToken, String userId, String accountId, String
5556
*/
5657
public DbxAuthFinish(String accessToken, Long expiresIn, String refreshToken, String userId,
5758
String teamId, String accountId, /*@Nullable*/String urlState) {
59+
this(accessToken, expiresIn, refreshToken, userId, teamId, accountId, urlState, null);
60+
}
61+
62+
/**
63+
*
64+
* <b>Beta</b>: This feature is not available to all developers. Please do NOT use it unless you are
65+
* early access partner of this feature. The function signature is subjected to change
66+
* in next minor version release.
67+
*
68+
* @param accessToken OAuth access token.
69+
* @param expiresIn Duration time of accessToken in second.
70+
* @param refreshToken A token used to obtain new accessToken.
71+
* @param userId Dropbox user ID of user that authorized this app.
72+
* @param teamId Dropbox team ID of team that authorized this app.
73+
* @param accountId Obfusticated user or team id. Keep it safe.
74+
* @param urlState State data passed in to {@link DbxWebAuth#start} or {@code null} if no state
75+
* was passed
76+
* @param scope A list of scope returned by Dropbox server. Each scope correspond to a group of
77+
* API endpoints. To call one API endpoint you have to obtains the scope first otherwise you
78+
* will get HTTP 401.
79+
*/
80+
public DbxAuthFinish(String accessToken, Long expiresIn, String refreshToken, String userId,
81+
String teamId, String accountId, /*@Nullable*/String urlState, String
82+
scope) {
5883
this.accessToken = accessToken;
5984
this.expiresIn = expiresIn;
6085
this.refreshToken = refreshToken;
@@ -63,6 +88,7 @@ public DbxAuthFinish(String accessToken, Long expiresIn, String refreshToken, St
6388
this.teamId = teamId;
6489
this.urlState = urlState;
6590
this.issueTime = System.currentTimeMillis();
91+
this.scope = scope;
6692
}
6793

6894
/**
@@ -138,6 +164,20 @@ public String getTeamId() {
138164
return teamId;
139165
}
140166

167+
/**
168+
*
169+
* <b>Beta</b>: This feature is not available to all developers. Please do NOT use it unless you are
170+
* early access partner of this feature. The function signature is subjected to change
171+
* in next minor version release.
172+
*
173+
* Return the <em>scopes</em> of current OAuth flow. Each scope correspond to a group of
174+
* API endpoints. To call one API endpoint you have to obtains the scope first otherwise you
175+
* will get HTTP 401.
176+
*/
177+
public String getScope() {
178+
return scope;
179+
}
180+
141181
/**
142182
* Returns the state data you passed in to {@link DbxWebAuth#start}. If you didn't pass
143183
* anything in, or you used {@link DbxWebAuthNoRedirect}, this will be {@code null}.
@@ -168,7 +208,7 @@ DbxAuthFinish withUrlState(/*@Nullable*/ String urlState) {
168208
}
169209

170210
DbxAuthFinish result = new DbxAuthFinish(accessToken, expiresIn, refreshToken, userId,
171-
teamId, accountId, urlState);
211+
teamId, accountId, urlState, scope);
172212
result.setIssueTime(issueTime);
173213

174214
return result;
@@ -189,6 +229,7 @@ public DbxAuthFinish read(JsonParser parser) throws IOException, JsonReadExcepti
189229
String accountId = null;
190230
String teamId = null;
191231
String state = null;
232+
String scope = null;
192233

193234
while (parser.getCurrentToken() == JsonToken.FIELD_NAME) {
194235
String fieldName = parser.getCurrentName();
@@ -219,6 +260,9 @@ else if (fieldName.equals("team_id")) {
219260
else if (fieldName.equals("state")) {
220261
state = JsonReader.StringReader.readField(parser, fieldName, state);
221262
}
263+
else if (fieldName.equals("scope")) {
264+
scope = JsonReader.StringReader.readField(parser, fieldName, scope);
265+
}
222266
else {
223267
// Unknown field. Skip over it.
224268
JsonReader.skipValue(parser);
@@ -243,7 +287,7 @@ else if (fieldName.equals("state")) {
243287
}
244288

245289
return new DbxAuthFinish(accessToken, expiresIn, refreshToken, userId, teamId,
246-
accountId, state);
290+
accountId, state, scope);
247291
}
248292
};
249293

src/main/java/com/dropbox/core/DbxRequestUtil.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Random;
1414

1515
import com.dropbox.core.stone.StoneSerializer;
16+
import com.dropbox.core.v2.auth.AuthError;
1617
import com.fasterxml.jackson.core.JsonParseException;
1718
import com.fasterxml.jackson.core.JsonProcessingException;
1819

@@ -337,7 +338,14 @@ public static DbxException unexpectedStatus(HttpRequestor.Response response, Str
337338
break;
338339
case 401:
339340
message = DbxRequestUtil.messageFromResponse(response, requestId);
340-
networkError = new InvalidAccessTokenException(requestId, message);
341+
try {
342+
ApiErrorResponse<AuthError> authErrorReponse = new ApiErrorResponse
343+
.Serializer<AuthError>(AuthError.Serializer.INSTANCE).deserialize(message);
344+
AuthError authError = authErrorReponse.getError();
345+
networkError = new InvalidAccessTokenException(requestId, message, authError);
346+
} catch (JsonParseException ex) {
347+
throw new BadResponseException(requestId, "Bad JSON: " + ex.getMessage(), ex);
348+
}
341349
break;
342350
case 403:
343351
try {

src/main/java/com/dropbox/core/DbxWebAuth.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ String authorizeImpl(Request request, Map<String, String> pkceParams) {
287287
params.put("token_access_type", request.tokenAccessType.toString());
288288
}
289289

290+
if (request.scope != null) {
291+
params.put("scope", request.scope);
292+
}
293+
290294
if (pkceParams != null) {
291295
for (String key: pkceParams.keySet()) {
292296
params.put(key, pkceParams.get(key));
@@ -659,6 +663,7 @@ public static final class Request {
659663
private final Boolean disableSignup;
660664
private final DbxSessionStore sessionStore;
661665
private final TokenAccessType tokenAccessType;
666+
private final String scope;
662667

663668

664669
private Request(String redirectUri,
@@ -667,14 +672,16 @@ private Request(String redirectUri,
667672
Boolean forceReapprove,
668673
Boolean disableSignup,
669674
DbxSessionStore sessionStore,
670-
TokenAccessType tokenAccessType) {
675+
TokenAccessType tokenAccessType,
676+
String scope) {
671677
this.redirectUri = redirectUri;
672678
this.state = state;
673679
this.requireRole = requireRole;
674680
this.forceReapprove = forceReapprove;
675681
this.disableSignup = disableSignup;
676682
this.sessionStore = sessionStore;
677683
this.tokenAccessType = tokenAccessType;
684+
this.scope = scope;
678685
}
679686

680687
/**
@@ -690,7 +697,9 @@ public Builder copy() {
690697
forceReapprove,
691698
disableSignup,
692699
sessionStore,
693-
tokenAccessType);
700+
tokenAccessType,
701+
scope
702+
);
694703
}
695704

696705
/**
@@ -713,9 +722,10 @@ public static final class Builder {
713722
private Boolean disableSignup;
714723
private DbxSessionStore sessionStore;
715724
private TokenAccessType tokenAccessType;
725+
private String scope;
716726

717727
private Builder() {
718-
this(null, null, null, null, null, null, null);
728+
this(null, null, null, null, null, null, null, null);
719729
}
720730

721731
private Builder(String redirectUri,
@@ -724,14 +734,16 @@ private Builder(String redirectUri,
724734
Boolean forceReapprove,
725735
Boolean disableSignup,
726736
DbxSessionStore sessionStore,
727-
TokenAccessType tokenAccessType) {
737+
TokenAccessType tokenAccessType,
738+
String scope) {
728739
this.redirectUri = redirectUri;
729740
this.state = state;
730741
this.requireRole = requireRole;
731742
this.forceReapprove = forceReapprove;
732743
this.disableSignup = disableSignup;
733744
this.sessionStore = sessionStore;
734745
this.tokenAccessType = tokenAccessType;
746+
this.scope = scope;
735747
}
736748

737749
/**
@@ -864,6 +876,10 @@ public Builder withDisableSignup(Boolean disableSignup) {
864876
}
865877

866878
/**
879+
* <b>Beta</b>: This feature is not available to all developers. Please do NOT use it unless you are
880+
* early access partner of this feature. The function signature is subjected to change
881+
* in next minor version release.
882+
*
867883
* Whether or not to include refresh token in {@link DbxAuthFinish}
868884
*
869885
* For {@link TokenAccessType#ONLINE}, auth result only contains short live token.
@@ -882,6 +898,21 @@ public Builder withTokenAccessType(TokenAccessType tokenAccessType) {
882898
return this;
883899
}
884900

901+
/**
902+
*
903+
* <b>Beta</b>: This feature is not available to all developers. Please do NOT use it unless you are
904+
* early access partner of this feature. The function signature is subjected to change
905+
* in next minor version release.
906+
*
907+
* @param scope A list of scope returned by Dropbox server. Each scope correspond to a group of
908+
* API endpoints. To call one API endpoint you have to obtains the scope first otherwise you
909+
* will get HTTP 401.
910+
*/
911+
public Builder withScope(String scope) {
912+
this.scope = scope;
913+
return this;
914+
}
915+
885916
/**
886917
* Returns a new OAuth {@link Request} that can be used in
887918
* {@link DbxWebAuth#DbxWebAuth(DbxRequestConfig,DbxAppInfo)} to authorize a user.
@@ -903,7 +934,8 @@ public Request build() {
903934
forceReapprove,
904935
disableSignup,
905936
sessionStore,
906-
tokenAccessType);
937+
tokenAccessType,
938+
scope);
907939
}
908940
}
909941
}

src/main/java/com/dropbox/core/InvalidAccessTokenException.java

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

3+
import com.dropbox.core.v2.auth.AuthError;
4+
35
/**
46
* Gets thrown when the access token you're using to make API calls is invalid.
57
*
@@ -13,8 +15,14 @@
1315
*/
1416
public class InvalidAccessTokenException extends DbxException {
1517
private static final long serialVersionUID = 0;
18+
private AuthError authError;
1619

17-
public InvalidAccessTokenException(String requestId, String message) {
20+
public InvalidAccessTokenException(String requestId, String message, AuthError authError) {
1821
super(requestId, message);
22+
this.authError = authError;
23+
}
24+
25+
public AuthError getAuthError() {
26+
return this.authError;
1927
}
2028
}

src/main/java/com/dropbox/core/v2/DbxRawClientV2.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -356,21 +356,14 @@ private <T> T executeRetriableWithRefresh(int maxRetries, RetriableExecution<T>
356356
throw ex;
357357
}
358358

359-
try {
360-
AuthError authError = DbxRequestUtil.readJsonFromErrorMessage(AuthError.Serializer
361-
.INSTANCE, ex.getMessage(), ex.getRequestId());
359+
AuthError authError = ex.getAuthError();
362360

363-
if (AuthError.EXPIRED_ACCESS_TOKEN.equals(authError) && canRefreshAccessToken()) {
364-
// retry with new access token.
365-
refreshAccessToken();
366-
return executeRetriable(maxRetries, execution);
367-
} else {
368-
// Doesn't need refresh.
369-
throw ex;
370-
}
371-
} catch (JsonParseException newEx) {
372-
// server returns unexpect string, or developers http requestor doesn't correctly
373-
// handle server's string. Give up.
361+
if (AuthError.EXPIRED_ACCESS_TOKEN.equals(authError) && canRefreshAccessToken()) {
362+
// retry with new access token.
363+
refreshAccessToken();
364+
return executeRetriable(maxRetries, execution);
365+
} else {
366+
// Doesn't need refresh.
374367
throw ex;
375368
}
376369
}

src/test/java/com/dropbox/core/DbxAuthFinishTest.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class DbxAuthFinishTest extends DbxOAuthTestBase {
2727
@Test
2828
public void testDbxAuthFinishLegacyToken() throws Exception{
2929
DbxAuthFinish expected = new DbxAuthFinish(
30-
"test-access-token", null, null, "test-user-id", null, "test", null
30+
"test-access-token", null, null, "test-user-id", null, "test", null, null
3131
);
3232

3333
ByteArrayInputStream responseStream = new ByteArrayInputStream(
@@ -55,7 +55,7 @@ public void testDbxAuthFinishLegacyToken() throws Exception{
5555
public void testDbxAuthFinishOnline() throws Exception{
5656
long now = System.currentTimeMillis();
5757
DbxAuthFinish expected = new DbxAuthFinish(
58-
"test-access-token", 3600L, null, "test-user-id", null, "test", null
58+
"test-access-token", 3600L, null, "test-user-id", null, "test", null, null
5959
);
6060
expected.setIssueTime(now);
6161

@@ -88,7 +88,8 @@ public void testDbxAuthFinishOnline() throws Exception{
8888
public void testDbxAuthFinishOffline() throws Exception{
8989
long now = System.currentTimeMillis();
9090
DbxAuthFinish expected = new DbxAuthFinish(
91-
"test-access-token", 3600L, "test_refresh_token", "test-user-id", null, "test", null
91+
"test-access-token", 3600L, "test_refresh_token", "test-user-id", null, "test",
92+
null, null
9293
);
9394
expected.setIssueTime(now);
9495

@@ -118,10 +119,48 @@ public void testDbxAuthFinishOffline() throws Exception{
118119
assertEquals(actual.getUrlState(), expected.getUrlState());
119120
}
120121

122+
@Test
123+
public void testDbxAuthFinishScope() throws Exception{
124+
long now = System.currentTimeMillis();
125+
DbxAuthFinish expected = new DbxAuthFinish(
126+
"test-access-token", 3600L, "test_refresh_token", "test-user-id", null, "test",
127+
null, "account_info.read"
128+
);
129+
expected.setIssueTime(now);
130+
131+
ByteArrayInputStream responseStream = new ByteArrayInputStream(
132+
(
133+
"{" +
134+
"\"token_type\":\"Bearer\"" +
135+
",\"access_token\":\"" + expected.getAccessToken() + "\"" +
136+
",\"expires_in\":" + 3600 +
137+
",\"refresh_token\":\"" + expected.getRefreshToken() + "\"" +
138+
",\"uid\":\"" + expected.getUserId() + "\"" +
139+
",\"account_id\":\"" + expected.getAccountId() + "\"" +
140+
",\"scope\":\"" + expected.getScope() + "\"" +
141+
"}"
142+
).getBytes("UTF-8")
143+
);
144+
145+
146+
147+
DbxAuthFinish actual = DbxAuthFinish.Reader.readFully(responseStream);
148+
actual.setIssueTime(now);
149+
assertEquals(actual.getAccessToken(), expected.getAccessToken());
150+
assertEquals(actual.getAccountId(), expected.getAccountId());
151+
assertEquals(actual.getRefreshToken(), expected.getRefreshToken());
152+
assertEquals(actual.getExpiresAt(), expected.getExpiresAt());
153+
assertEquals(actual.getTeamId(), expected.getTeamId());
154+
assertEquals(actual.getUserId(), expected.getUserId());
155+
assertEquals(actual.getUrlState(), expected.getUrlState());
156+
assertEquals(actual.getScope(), expected.getScope());
157+
}
158+
121159
@Test
122160
public void testDbxAuthFinishWithUrlState() throws Exception {
123161
DbxAuthFinish expected = new DbxAuthFinish(
124-
"test-access-token", 3600L, "test_refresh_token", "test-user-id", null, "test", null
162+
"test-access-token", 3600L, "test_refresh_token", "test-user-id", null, "test",
163+
null, null
125164
);
126165

127166
DbxAuthFinish actual = expected.withUrlState("testState");
@@ -154,7 +193,7 @@ public void testFinishWithState() throws Exception {
154193
assertNotNull(sessionStore.get());
155194

156195
DbxAuthFinish expected = new DbxAuthFinish(
157-
"test-access-token", null, null, "test-user-id", null, "test", state
196+
"test-access-token", null, null, "test-user-id", null, "test", state, null
158197
);
159198
ByteArrayOutputStream body = new ByteArrayOutputStream();
160199
ByteArrayInputStream responseStream = new ByteArrayInputStream(

0 commit comments

Comments
 (0)