Skip to content

Commit 7557164

Browse files
committed
Support Short Live Token in DAuth state flow.
1 parent 5751983 commit 7557164

File tree

11 files changed

+488
-148
lines changed

11 files changed

+488
-148
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.dropbox.core.examples.android;
2+
3+
import com.dropbox.core.DbxRequestConfig;
4+
import com.dropbox.core.http.OkHttp3Requestor;
5+
6+
public class DbxRequestConfigFactory {
7+
private static DbxRequestConfig sDbxRequestConfig;
8+
9+
public static DbxRequestConfig getRequestConfig() {
10+
if (sDbxRequestConfig == null) {
11+
sDbxRequestConfig = DbxRequestConfig.newBuilder("examples-v2-demo")
12+
.withHttpRequestor(new OkHttp3Requestor(OkHttp3Requestor.defaultOkHttpClient()))
13+
.build();
14+
}
15+
return sDbxRequestConfig;
16+
}
17+
}

examples/android/src/main/java/com/dropbox/core/examples/android/DropboxActivity.java

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,54 @@
33
import android.content.SharedPreferences;
44
import android.support.v7.app.AppCompatActivity;
55

6+
import android.util.Log;
67
import com.dropbox.core.android.Auth;
8+
import com.dropbox.core.json.JsonReadException;
9+
import com.dropbox.core.oauth.DbxCredential;
710

811

912
/**
1013
* Base class for Activities that require auth tokens
1114
* Will redirect to auth flow if needed
1215
*/
1316
public abstract class DropboxActivity extends AppCompatActivity {
17+
public final static boolean USE_SLT = true;
1418

1519
@Override
1620
protected void onResume() {
1721
super.onResume();
1822

1923
SharedPreferences prefs = getSharedPreferences("dropbox-sample", MODE_PRIVATE);
20-
String accessToken = prefs.getString("access-token", null);
21-
if (accessToken == null) {
22-
accessToken = Auth.getOAuth2Token();
23-
if (accessToken != null) {
24-
prefs.edit().putString("access-token", accessToken).apply();
25-
initAndLoadData(accessToken);
24+
25+
if (USE_SLT) {
26+
String serailizedCredental = prefs.getString("credential", null);
27+
28+
if (serailizedCredental == null) {
29+
DbxCredential credential = Auth.getDbxCredential();
30+
31+
if (credential != null) {
32+
prefs.edit().putString("credential", credential.toString()).apply();
33+
initAndLoadData(credential);
34+
}
35+
} else {
36+
try {
37+
DbxCredential credential = DbxCredential.Reader.readFully(serailizedCredental);
38+
initAndLoadData(credential);
39+
} catch (JsonReadException e) {
40+
throw new IllegalStateException("Credential data corrupted: " + e.getMessage());
41+
}
2642
}
2743
} else {
28-
initAndLoadData(accessToken);
44+
String accessToken = prefs.getString("access-token", null);
45+
if (accessToken == null) {
46+
accessToken = Auth.getOAuth2Token();
47+
if (accessToken != null) {
48+
prefs.edit().putString("access-token", accessToken).apply();
49+
initAndLoadData(accessToken);
50+
}
51+
} else {
52+
initAndLoadData(accessToken);
53+
}
2954
}
3055

3156
String uid = Auth.getUid();
@@ -41,11 +66,21 @@ private void initAndLoadData(String accessToken) {
4166
loadData();
4267
}
4368

69+
private void initAndLoadData(DbxCredential dbxCredential) {
70+
DropboxClientFactory.init(dbxCredential);
71+
PicassoClient.init(getApplicationContext(), DropboxClientFactory.getClient());
72+
loadData();
73+
}
74+
4475
protected abstract void loadData();
4576

4677
protected boolean hasToken() {
4778
SharedPreferences prefs = getSharedPreferences("dropbox-sample", MODE_PRIVATE);
48-
String accessToken = prefs.getString("access-token", null);
49-
return accessToken != null;
79+
if (USE_SLT) {
80+
return prefs.getString("credential", null) != null;
81+
} else {
82+
String accessToken = prefs.getString("access-token", null);
83+
return accessToken != null;
84+
}
5085
}
5186
}

examples/android/src/main/java/com/dropbox/core/examples/android/DropboxClientFactory.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.dropbox.core.examples.android;
22

3-
import com.dropbox.core.DbxHost;
4-
import com.dropbox.core.DbxRequestConfig;
5-
import com.dropbox.core.http.OkHttp3Requestor;
3+
import com.dropbox.core.oauth.DbxCredential;
64
import com.dropbox.core.v2.DbxClientV2;
75

86
/**
@@ -14,11 +12,14 @@ public class DropboxClientFactory {
1412

1513
public static void init(String accessToken) {
1614
if (sDbxClient == null) {
17-
DbxRequestConfig requestConfig = DbxRequestConfig.newBuilder("examples-v2-demo")
18-
.withHttpRequestor(new OkHttp3Requestor(OkHttp3Requestor.defaultOkHttpClient()))
19-
.build();
15+
sDbxClient = new DbxClientV2(DbxRequestConfigFactory.getRequestConfig(), accessToken);
16+
}
17+
}
2018

21-
sDbxClient = new DbxClientV2(requestConfig, accessToken);
19+
public static void init(DbxCredential credential) {
20+
credential = new DbxCredential(credential.getAccessToken(), -1L, credential.getRefreshToken(), credential.getAppKey());
21+
if (sDbxClient == null) {
22+
sDbxClient = new DbxClientV2(DbxRequestConfigFactory.getRequestConfig(), credential);
2223
}
2324
}
2425

examples/android/src/main/java/com/dropbox/core/examples/android/UserActivity.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ protected void onCreate(Bundle savedInstanceState) {
3232
loginButton.setOnClickListener(new View.OnClickListener() {
3333
@Override
3434
public void onClick(View v) {
35-
Auth.startOAuth2Authentication(UserActivity.this, getString(R.string.app_key));
35+
if (DropboxActivity.USE_SLT) {
36+
Auth.startOAuth2PKCE(UserActivity.this, getString(R.string.app_key), DbxRequestConfigFactory.getRequestConfig());
37+
} else {
38+
Auth.startOAuth2Authentication(UserActivity.this, getString(R.string.app_key));
39+
}
3640
}
3741
});
3842

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.dropbox.core;
2+
3+
import com.dropbox.core.http.HttpRequestor;
4+
import com.dropbox.core.util.LangUtil;
5+
import com.dropbox.core.v2.DbxRawClientV2;
6+
7+
import java.io.Serializable;
8+
import java.io.UnsupportedEncodingException;
9+
import java.security.MessageDigest;
10+
import java.security.NoSuchAlgorithmException;
11+
import java.security.SecureRandom;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
15+
import static com.dropbox.core.util.StringUtil.urlSafeBase64Encode;
16+
17+
/**
18+
* This class should be lib/jar private. We make it public so that Android related code can use it.
19+
*
20+
* <b>Beta</b>: This feature is not available to all developers. Please do NOT use it unless you are
21+
* early access partner of this feature. The function signature is subjected to change
22+
* in next minor version release.
23+
*
24+
* This class does code verifier and code challenge generation in Proof Key for Code Exchange(PKCE).
25+
* @see <a href="https://tools.ietf.org/html/rfc7636">https://tools.ietf.org/html/rfc7636</a>
26+
*/
27+
public class DbxPKCEManager implements Serializable {
28+
public static final long serialVersionUID = 0;
29+
public static final String CODE_CHALLENGE_METHODS = "S256";
30+
public static final int CODE_VERIFIER_SIZE = 128;
31+
32+
private static final SecureRandom RAND = new SecureRandom();
33+
private static final String CODE_VERIFIER_CHAR_SET =
34+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
35+
36+
private String codeVerifier;
37+
private String codeChallenge;
38+
39+
/**
40+
* This class has state. Each instance has a randomly generated codeVerifier in it. Just
41+
* like we shouldn't re-use the same code verifier in PKCE, we shouldn't re-use the same
42+
* DbxPKCEManager instance in different OAuth flow.
43+
*/
44+
public DbxPKCEManager() {
45+
this.codeVerifier = generateCodeVerifier();
46+
this.codeChallenge = generateCodeChallenge();
47+
}
48+
49+
String generateCodeVerifier() {
50+
StringBuilder sb = new StringBuilder();
51+
52+
for (int i = 0; i < CODE_VERIFIER_SIZE; i++) {
53+
sb.append(CODE_VERIFIER_CHAR_SET.charAt(RAND.nextInt(CODE_VERIFIER_CHAR_SET.length())));
54+
}
55+
56+
return sb.toString();
57+
}
58+
59+
String generateCodeChallenge() {
60+
return generateCodeChallenge(this.codeVerifier);
61+
}
62+
63+
static String generateCodeChallenge(String codeVerifier) {
64+
try {
65+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
66+
byte[] signiture = digest.digest(codeVerifier.getBytes("US-ASCII"));
67+
return urlSafeBase64Encode(signiture).replaceAll("=+$", ""); // remove trailing equal
68+
} catch (NoSuchAlgorithmException e) {
69+
throw LangUtil.mkAssert("Impossible", e);
70+
} catch (UnsupportedEncodingException e) {
71+
throw LangUtil.mkAssert("Impossible", e);
72+
}
73+
}
74+
75+
/**
76+
* @return The randomly generate code verifier in this instance.
77+
*/
78+
public String getCodeVerifier() {
79+
return this.codeVerifier;
80+
}
81+
82+
/**
83+
* @return The code challenge, which is a hashed code verifier.
84+
*/
85+
public String getCodeChallenge() {
86+
return this.codeChallenge;
87+
}
88+
89+
/**
90+
* Make oauth2/token request to exchange code for oauth2 access token. Client secret is not
91+
* requried.
92+
* @param requestConfig Default attributes to use for each request.
93+
* @param oauth2Code OAuth2 code defined in OAuth2 code flow.
94+
* @param appKey Client Key
95+
* @param redirectUri The same redirect_uri that's used in preivous oauth2/authorize call.
96+
* @param host Only used for testing when you don't want to make request against production.
97+
* @return OAuth2 result, including oauth2 access token, and optionally expiration time and
98+
* refresh token.
99+
* @throws DbxException If reqeust is invalid, or code expired, or server error.
100+
*/
101+
public DbxAuthFinish makeTokenRequest(DbxRequestConfig requestConfig,
102+
String oauth2Code,
103+
String appKey,
104+
String redirectUri,
105+
DbxHost host) throws DbxException {
106+
Map<String, String> params = new HashMap<String, String>();
107+
params.put("grant_type", "authorization_code");
108+
params.put("code", oauth2Code);
109+
params.put("locale", requestConfig.getUserLocale());
110+
params.put("client_id", appKey);
111+
params.put("code_verifier", this.codeVerifier);
112+
113+
if (redirectUri != null) {
114+
params.put("redirect_uri", redirectUri);
115+
}
116+
117+
return DbxRequestUtil.doPostNoAuth(
118+
requestConfig,
119+
DbxRawClientV2.USER_AGENT_ID,
120+
host.getApi(),
121+
"oauth2/token",
122+
DbxRequestUtil.toParamsArray(params),
123+
null,
124+
new DbxRequestUtil.ResponseHandler<DbxAuthFinish>() {
125+
@Override
126+
public DbxAuthFinish handle(HttpRequestor.Response response) throws DbxException {
127+
if (response.getStatusCode() != 200) {
128+
throw DbxRequestUtil.unexpectedStatus(response);
129+
}
130+
return DbxRequestUtil.readJsonFromResponse(DbxAuthFinish.Reader, response);
131+
}
132+
}
133+
);
134+
}
135+
}

0 commit comments

Comments
 (0)