Skip to content

Commit

Permalink
Support dropbox refresh token (#3)
Browse files Browse the repository at this point in the history
* Add dynamic access token fetching using a refresh token

* Fix indentation

* Extract access token retrieval into separate method

* Simplify blank check for multiple variables

---------

Co-authored-by: Can <[email protected]>
  • Loading branch information
marcelreppi and jckleiner authored Dec 23, 2023
1 parent 3593326 commit 672fecb
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 12 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/build-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ jobs:
GOOGLE_DRIVE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_DRIVE_SERVICE_ACCOUNT }}
GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_JSON: ${{ secrets.GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_JSON }}
GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_FILE_PATH: ${{ secrets.GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_FILE_PATH }}

DROPBOX_ACCESS_TOKEN: ${{ secrets.DROPBOX_ACCESS_TOKEN }}
DROPBOX_APP_KEY: ${{ secrets.DROPBOX_APP_KEY }}
DROPBOX_APP_SECRET: ${{ secrets.DROPBOX_APP_SECRET }}
DROPBOX_REFRESH_TOKEN: ${{ secrets.DROPBOX_REFRESH_TOKEN }}

NEXTCLOUD_EMAIL: ${{ secrets.NEXTCLOUD_EMAIL }}
NEXTCLOUD_PASSWORD: ${{ secrets.NEXTCLOUD_PASSWORD }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docker-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_FILE_PATH=${{ secrets.GOOGLE_DRIVE_SERVICE_ACCOUNT_SECRET_FILE_PATH }}
DROPBOX_ACCESS_TOKEN=${{ secrets.DROPBOX_ACCESS_TOKEN }}
DROPBOX_APP_KEY=${{ secrets.DROPBOX_APP_KEY }}
DROPBOX_APP_SECRET=${{ secrets.DROPBOX_APP_SECRET }}
DROPBOX_REFRESH_TOKEN=${{ secrets.DROPBOX_REFRESH_TOKEN }}
NEXTCLOUD_EMAIL=${{ secrets.NEXTCLOUD_EMAIL }}
NEXTCLOUD_PASSWORD=${{ secrets.NEXTCLOUD_PASSWORD }}
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Create a `.env` file with the following properties ([How do I find all these val

# Dropbox (Optional)
DROPBOX_ACCESS_TOKEN=
DROPBOX_APP_KEY=
DROPBOX_APP_SECRET=
DROPBOX_REFRESH_TOKEN=

# Nextcloud (Optional)
NEXTCLOUD_EMAIL=
Expand Down
51 changes: 45 additions & 6 deletions documentation/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,51 @@
### Dropbox

1. Create a new app on developer console (https://www.dropbox.com/developers/apps/create)
2. Select "Scoped access" > "App folder – Access to a single folder created specifically for your app." and give
your app a name
3. Go to permissions tab > enable `files.content.write` & `files.content.read` and click "Submit" to save your changes.
Make sure you saved these changes **before** you generate your access token.
4. Go to Settings > OAuth 2 > Generate access token > "generate" and copy the generated token. Paste it in your
`.env` file as the value for `DROPBOX_ACCESS_TOKEN`
2. Go to the Permissions tab > enable `files.content.write` & `files.content.read` and click "Submit" to save your changes.
Make sure you saved these changes **before** you generate your access token.
3. Go to the Settings tab > OAuth 2 > Generate access token > Generate. Note that these tokens are short-lived and expire after a few hours.
Due to security reason long-lived tokens have been deprecated.

For an automated setup, for example with [GitHub Action](../README.md#fork-github-actions), short-lived access tokens will not work because they will expire.
An alternative solution would be to use a refresh token that does not expire which you need to retrieve manually once.
The refresh token will then be used to fetch an access token on-the-fly everytime the application runs.
Do the following steps to set up the refresh token flow.

1. Go to the Settings tab and store the App Key in the `DROPBOX_APP_KEY` environment variable
2. Go to the Settings tab and store the App Secret in the `DROPBOX_APP_SECRET` environment variable
3. Open a browser and enter the following URL and replace the `<APP_KEY>` placeholder

https://www.dropbox.com/oauth2/authorize?client_id=<APP_KEY>&token_access_type=offline&response_type=code

4. Click on continue and allow the app to access your files
5. Copy the authorization code (referred to as `AUTH_CODE` in the following steps) shown in the following screen
6. Send the following HTTP POST request with the actual values replacing the placeholders

```
curl --request POST \
--url https://api.dropboxapi.com/oauth2/token \
--header 'Authorization: Basic <BASE64 ENCODING OF <APP_KEY>:<APP_SECRET>>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data code=<AUTH_CODE> \
--data grant_type=authorization_code
```

7. Extract the `refresh_token` from the response and store it in the `DROPBOX_REFRESH_TOKEN` environment variable.
**DO NOT SHARE THIS TOKEN WITH ANYONE!**

8. To ensure it works try sending the following HTTP POST request with the actual values replacing the placeholders

```
curl --request POST \
--url https://api.dropbox.com/oauth2/token \
--header 'Authorization: Basic <BASE64 ENCODING OF <APP_KEY>:<APP_SECRET>>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=refresh_token \
--data refresh_token=<REFRESH_TOKEN>
```

9. Leave the `DROPBOX_ACCESS_TOKEN` environment variable empty.
Otherwise, the refresh token flow will be skipped and no new access token will be fetched.

### Nextcloud

Expand Down
47 changes: 42 additions & 5 deletions src/main/java/com/greydev/notionbackup/NotionBackup.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
import org.apache.commons.lang3.StringUtils;

import com.dropbox.core.v2.DbxClientV2;
import com.dropbox.core.DbxException;
import com.dropbox.core.DbxRequestConfig;
import com.dropbox.core.oauth.DbxCredential;
import com.dropbox.core.oauth.DbxRefreshResult;
import com.google.api.services.drive.Drive;
import com.greydev.notionbackup.cloudstorage.dropbox.DropboxClient;
import com.greydev.notionbackup.cloudstorage.dropbox.DropboxServiceFactory;
Expand All @@ -29,6 +33,9 @@
public class NotionBackup {

public static final String KEY_DROPBOX_ACCESS_TOKEN = "DROPBOX_ACCESS_TOKEN";
public static final String KEY_DROPBOX_APP_KEY = "DROPBOX_APP_KEY";
public static final String KEY_DROPBOX_APP_SECRET = "DROPBOX_APP_SECRET";
public static final String KEY_DROPBOX_REFRESH_TOKEN = "DROPBOX_REFRESH_TOKEN";

public static final String KEY_NEXTCLOUD_EMAIL = "NEXTCLOUD_EMAIL";
public static final String KEY_NEXTCLOUD_PASSWORD = "NEXTCLOUD_PASSWORD";
Expand Down Expand Up @@ -90,13 +97,13 @@ public static void startGoogleDriveBackup(File fileToUpload) {


public static void startDropboxBackup(File fileToUpload) {
String dropboxAccessToken = dotenv.get(KEY_DROPBOX_ACCESS_TOKEN);

if (StringUtils.isBlank(dropboxAccessToken)) {
log.info("Skipping Dropbox upload. {} is blank.", KEY_DROPBOX_ACCESS_TOKEN);
Optional<String> dropboxAccessToken = getDropboxAccessToken();
if (dropboxAccessToken.isEmpty()) {
log.info("No Dropbox access token available. Skipping Dropbox upload.");
return;
}
Optional<DbxClientV2> dropboxServiceOptional = DropboxServiceFactory.create(dropboxAccessToken);

Optional<DbxClientV2> dropboxServiceOptional = DropboxServiceFactory.create(dropboxAccessToken.get());
if (dropboxServiceOptional.isEmpty()) {
log.warn("Could not create Dropbox service. Skipping Dropbox upload");
return;
Expand All @@ -105,6 +112,36 @@ public static void startDropboxBackup(File fileToUpload) {
dropboxClient.upload(fileToUpload);
}

private static Optional<String> getDropboxAccessToken() {
String dropboxAccessToken = dotenv.get(KEY_DROPBOX_ACCESS_TOKEN);

if (StringUtils.isBlank(dropboxAccessToken)) {
log.info("{} is blank. Trying to fetch an access token with the refresh token...", KEY_DROPBOX_ACCESS_TOKEN);

String dropboxAppKey = dotenv.get(KEY_DROPBOX_APP_KEY);
String dropboxAppSecret = dotenv.get(KEY_DROPBOX_APP_SECRET);
String dropboxRefreshToken = dotenv.get(KEY_DROPBOX_REFRESH_TOKEN);
if (StringUtils.isAnyBlank(dropboxAppKey, dropboxAppSecret, dropboxRefreshToken)) {
log.info("Failed to fetch an access token. Either {}, {} or {} is blank.", KEY_DROPBOX_REFRESH_TOKEN, KEY_DROPBOX_APP_KEY, KEY_DROPBOX_APP_SECRET);
return Optional.empty();
}

DbxCredential dbxCredential = new DbxCredential("", 14400L, dropboxRefreshToken, dropboxAppKey, dropboxAppSecret);
DbxRefreshResult refreshResult;
try {
refreshResult = dbxCredential.refresh(new DbxRequestConfig("NotionBackup"));
} catch (DbxException e) {
log.info("Token refresh call to Dropbox API failed.");
return Optional.empty();
}

dropboxAccessToken = refreshResult.getAccessToken();
log.info("Successfully fetched an access token.");
}

return Optional.of(dropboxAccessToken);
}


public static void startNextcloudBackup(File fileToUpload) {
String email = dotenv.get(KEY_NEXTCLOUD_EMAIL);
Expand Down

0 comments on commit 672fecb

Please sign in to comment.