From eb9184d7fb2827df3b5f7564b3bd7ec2c0ccaf51 Mon Sep 17 00:00:00 2001 From: Victor Andreasson Date: Sun, 31 Mar 2024 23:57:35 +0200 Subject: [PATCH] Dropbox repos: Use the new "refresh token" OAuth flow As indicated here: https://www.dropboxforum.com/t5/Dropbox-API-Support-Feedback/Java-SDK-issues-with-short-lived-token/m-p/508693/highlight/true#M25109. - Store the DbxCredential object in JSON form in app preferences. - Store requestConfig as a property, since it is now needed in many places. - Write a mock credential to preferences when preparing for Dropbox-related tests. - Remove unused updating of Dropbox token in SyncTest. - Clean up some unused imports. All tests in the "repos" directory are now passing. --- app/build.gradle | 2 +- .../orgzly/android/repos/DropboxRepoTest.java | 8 +- .../com/orgzly/android/repos/SyncTest.java | 4 - .../orgzly/android/prefs/AppPreferences.java | 8 +- .../orgzly/android/repos/DropboxClient.java | 82 ++++++++----------- .../ui/repo/dropbox/DropboxRepoActivity.kt | 48 ----------- app/src/main/res/values/prefs_keys.xml | 2 +- 7 files changed, 48 insertions(+), 106 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f003455db..904576739 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,7 +57,7 @@ android { debug { buildConfigField "boolean", "LOG_DEBUG", "true" - buildConfigField "String", "DROPBOX_TOKEN", gradle.ext.appProperties.getProperty("dropbox.token", '""') + buildConfigField "String", "DROPBOX_REFRESH_TOKEN", gradle.ext.appProperties.getProperty("dropbox.refresh_token", '""') } } diff --git a/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java b/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java index 599d04d8e..f7a34b768 100644 --- a/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java +++ b/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java @@ -7,6 +7,7 @@ import com.orgzly.android.prefs.AppPreferences; import com.orgzly.android.util.MiscUtils; +import org.json.JSONObject; import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -26,7 +27,12 @@ public void setUp() throws Exception { super.setUp(); Assume.assumeTrue(BuildConfig.IS_DROPBOX_ENABLED); - AppPreferences.dropboxToken(context, BuildConfig.DROPBOX_TOKEN); + JSONObject mockSerializedDbxCredential = new JSONObject(); + mockSerializedDbxCredential.put("access_token", "dummy"); + mockSerializedDbxCredential.put("expires_at", System.currentTimeMillis()); + mockSerializedDbxCredential.put("refresh_token", BuildConfig.DROPBOX_REFRESH_TOKEN); + mockSerializedDbxCredential.put("app_key", BuildConfig.DROPBOX_APP_KEY); + AppPreferences.dropboxSerializedCredential(context, mockSerializedDbxCredential.toString()); } @Test diff --git a/app/src/androidTest/java/com/orgzly/android/repos/SyncTest.java b/app/src/androidTest/java/com/orgzly/android/repos/SyncTest.java index 84dac51ae..d0189fc40 100644 --- a/app/src/androidTest/java/com/orgzly/android/repos/SyncTest.java +++ b/app/src/androidTest/java/com/orgzly/android/repos/SyncTest.java @@ -2,7 +2,6 @@ import android.net.Uri; -import com.orgzly.BuildConfig; import com.orgzly.android.BookName; import com.orgzly.android.LocalStorage; import com.orgzly.android.OrgzlyTest; @@ -10,7 +9,6 @@ import com.orgzly.android.db.entity.BookView; import com.orgzly.android.db.entity.NoteView; import com.orgzly.android.db.entity.Repo; -import com.orgzly.android.prefs.AppPreferences; import com.orgzly.android.sync.BookNamesake; import com.orgzly.android.sync.BookSyncStatus; import com.orgzly.android.util.EncodingDetect; @@ -38,8 +36,6 @@ public class SyncTest extends OrgzlyTest { @Before public void setUp() throws Exception { super.setUp(); - - AppPreferences.dropboxToken(context, BuildConfig.DROPBOX_TOKEN); } @Test diff --git a/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java b/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java index 54fc6208a..632961742 100644 --- a/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java +++ b/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java @@ -892,13 +892,13 @@ public static boolean highlightEditedRichText(Context context) { * Dropbox token. */ - public static String dropboxToken(Context context) { - String key = context.getResources().getString(R.string.pref_key_dropbox_token); + public static String dropboxSerializedCredential(Context context) { + String key = context.getResources().getString(R.string.pref_key_dropbox_credential); return getStateSharedPreferences(context).getString(key, null); } - public static void dropboxToken(Context context, String value) { - String key = context.getResources().getString(R.string.pref_key_dropbox_token); + public static void dropboxSerializedCredential(Context context, String value) { + String key = context.getResources().getString(R.string.pref_key_dropbox_credential); SharedPreferences.Editor editor = getStateSharedPreferences(context).edit(); if (value == null) { editor.remove(key); diff --git a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java index b69949ba2..e0fdbe082 100644 --- a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java +++ b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java @@ -7,6 +7,8 @@ import com.dropbox.core.DbxException; import com.dropbox.core.DbxRequestConfig; import com.dropbox.core.android.Auth; +import com.dropbox.core.json.JsonReadException; +import com.dropbox.core.oauth.DbxCredential; import com.dropbox.core.v2.DbxClientV2; import com.dropbox.core.v2.files.FileMetadata; import com.dropbox.core.v2.files.FolderMetadata; @@ -45,7 +47,8 @@ public class DropboxClient { final private Context mContext; final private long repoId; - + final private DbxRequestConfig requestConfig; + private DbxCredential credential; private DbxClientV2 dbxClient; private boolean tryLinking = false; @@ -55,14 +58,31 @@ public DropboxClient(Context context, long id) { repoId = id; + requestConfig = getRequestConfig(); + createClient(); } - private void createClient() { - String accessToken = getToken(); + private DbxRequestConfig getRequestConfig() { + String userLocale = Locale.getDefault().toString(); + String clientId = String.format("%s/%s", + BuildConfig.APPLICATION_ID, BuildConfig.VERSION_NAME); + return DbxRequestConfig + .newBuilder(clientId) + .withUserLocale(userLocale) + .build(); + } - if (accessToken != null) { - dbxClient = getDbxClient(accessToken); + private void createClient() { + String serializedCredential = AppPreferences.dropboxSerializedCredential(mContext); + if (serializedCredential != null && serializedCredential.length() > 0) { + try { + credential = + DbxCredential.Reader.readFully(serializedCredential); + } catch (JsonReadException e) { + throw new RuntimeException(e); + } + dbxClient = new DbxClientV2(requestConfig, credential); } } @@ -78,65 +98,33 @@ private void linkedOrThrow() throws IOException { public void unlink() { dbxClient = null; - deleteToken(); + deleteCredential(); tryLinking = false; } public void beginAuthentication(Activity activity) { tryLinking = true; - Auth.startOAuth2Authentication(activity, BuildConfig.DROPBOX_APP_KEY); + Auth.startOAuth2PKCE(activity, BuildConfig.DROPBOX_APP_KEY, requestConfig); } public boolean finishAuthentication() { if (dbxClient == null && tryLinking) { - String accessToken = getToken(); - - if (accessToken == null) { - accessToken = Auth.getOAuth2Token(); - - if (accessToken != null) { - saveToken(accessToken); - } - } - - if (accessToken != null) { - dbxClient = getDbxClient(accessToken); + credential = Auth.getDbxCredential(); + if (credential != null) { + saveCredential(); + createClient(); return true; } } - return false; } - private DbxClientV2 getDbxClient(String accessToken) { - String userLocale = Locale.getDefault().toString(); - - String clientId = String.format("%s/%s", - BuildConfig.APPLICATION_ID, BuildConfig.VERSION_NAME); - - DbxRequestConfig requestConfig = DbxRequestConfig - .newBuilder(clientId) - .withUserLocale(userLocale) - .build(); - - return new DbxClientV2(requestConfig, accessToken); - } - - public void setToken(String token) { - saveToken(token); - createClient(); - } - - private void saveToken(String token) { - AppPreferences.dropboxToken(mContext, token); - } - - public String getToken() { - return AppPreferences.dropboxToken(mContext); + private void saveCredential() { + AppPreferences.dropboxSerializedCredential(mContext, credential.toString()); } - private void deleteToken() { - AppPreferences.dropboxToken(mContext, null); + private void deleteCredential() { + AppPreferences.dropboxSerializedCredential(mContext, null); } public List getBooks(Uri repoUri) throws IOException { diff --git a/app/src/main/java/com/orgzly/android/ui/repo/dropbox/DropboxRepoActivity.kt b/app/src/main/java/com/orgzly/android/ui/repo/dropbox/DropboxRepoActivity.kt index 7eb99ffba..4e707c782 100644 --- a/app/src/main/java/com/orgzly/android/ui/repo/dropbox/DropboxRepoActivity.kt +++ b/app/src/main/java/com/orgzly/android/ui/repo/dropbox/DropboxRepoActivity.kt @@ -1,13 +1,11 @@ package com.orgzly.android.ui.repo.dropbox -import android.annotation.SuppressLint import android.app.Activity import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle import android.text.TextUtils -import android.widget.EditText import androidx.core.content.ContextCompat import androidx.core.widget.ImageViewCompat import androidx.lifecycle.Observer @@ -60,11 +58,6 @@ class DropboxRepoActivity : CommonActivity() { } } - binding.activityRepoDropboxLinkButton.setOnLongClickListener { - editAccessToken() - true - } - // Not working when done in XML binding.activityRepoDropboxDirectory.apply { setHorizontallyScrolling(false) @@ -125,47 +118,6 @@ class DropboxRepoActivity : CommonActivity() { } } - private fun editAccessToken() { - @SuppressLint("InflateParams") - val view = layoutInflater.inflate(R.layout.dialog_simple_one_liner, null, false) - - val editView = view.findViewById(R.id.dialog_input).apply { - setSelectAllOnFocus(true) - - setHint(R.string.access_token) - - client.token?.let { - setText(it) - } - } - - alertDialog = MaterialAlertDialogBuilder(this) - .setView(view) - .setTitle(R.string.access_token) - .setPositiveButton(R.string.set) { _, _ -> - editView.text.toString().let { value -> - if (TextUtils.isEmpty(value)) { - client.unlink() - } else { - client.setToken(value) - } - } - updateDropboxLinkUnlinkButton() - } - .setNeutralButton(R.string.clear) { _, _ -> - client.unlink() - updateDropboxLinkUnlinkButton() - } - .setNegativeButton(R.string.cancel) { _, _ -> } - .create().apply { - setOnShowListener { - KeyboardUtils.openSoftKeyboard(editView) - } - - show() - } - } - public override fun onResume() { super.onResume() diff --git a/app/src/main/res/values/prefs_keys.xml b/app/src/main/res/values/prefs_keys.xml index 4265f49be..8173d39fc 100644 --- a/app/src/main/res/values/prefs_keys.xml +++ b/app/src/main/res/values/prefs_keys.xml @@ -591,7 +591,7 @@ pref_key_clear_database - pref_key_dropbox_token + pref_key_dropbox_token pref_key_is_getting_started_notebook_loaded pref_key_last_used_version_code pref_key_last_successful_sync_time