Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(services/dropbox): Implement refresh token support #2604

Merged
merged 5 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion core/src/services/dropbox/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ use std::sync::Arc;

use async_trait::async_trait;
use http::StatusCode;
use serde::Deserialize;

use super::core::DropboxCore;
use super::error::parse_error;
use super::response::DropboxMetadataResponse;
use super::writer::DropboxWriter;
use crate::raw::*;
use crate::*;
Expand Down Expand Up @@ -163,3 +163,57 @@ impl Accessor for DropboxBackend {
}
}
}

#[derive(Default, Debug, Deserialize)]
#[serde(default)]
pub struct DropboxMetadataResponse {
#[serde(rename(deserialize = ".tag"))]
pub tag: String,
pub client_modified: String,
pub content_hash: Option<String>,
pub file_lock_info: Option<DropboxMetadataFileLockInfo>,
pub has_explicit_shared_members: Option<bool>,
pub id: String,
pub is_downloadable: Option<bool>,
pub name: String,
pub path_display: String,
pub path_lower: String,
pub property_groups: Option<Vec<DropboxMetadataPropertyGroup>>,
pub rev: Option<String>,
pub server_modified: Option<String>,
pub sharing_info: Option<DropboxMetadataSharingInfo>,
pub size: Option<u64>,
}

#[derive(Default, Debug, Deserialize)]
#[serde(default)]
pub struct DropboxMetadataFileLockInfo {
pub created: Option<String>,
pub is_lockholder: bool,
pub lockholder_name: Option<String>,
}

#[derive(Default, Debug, Deserialize)]
#[serde(default)]
pub struct DropboxMetadataPropertyGroup {
pub fields: Vec<DropboxMetadataPropertyGroupField>,
pub template_id: String,
}

#[derive(Default, Debug, Deserialize)]
#[serde(default)]
pub struct DropboxMetadataPropertyGroupField {
pub name: String,
pub value: String,
}

#[derive(Default, Debug, Deserialize)]
#[serde(default)]
pub struct DropboxMetadataSharingInfo {
pub modified_by: Option<String>,
pub parent_shared_folder_id: Option<String>,
pub read_only: Option<bool>,
pub shared_folder_id: Option<String>,
pub traverse_only: Option<bool>,
pub no_access: Option<bool>,
}
103 changes: 92 additions & 11 deletions core/src/services/dropbox/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;

use chrono::DateTime;
use chrono::Utc;
use tokio::sync::Mutex;

use super::backend::DropboxBackend;
use super::core::DropboxCore;
use super::core::DropboxSigner;
use crate::raw::*;
use crate::*;

Expand Down Expand Up @@ -72,8 +77,12 @@ use crate::*;

#[derive(Default)]
pub struct DropboxBuilder {
access_token: Option<String>,
root: Option<String>,
access_token: Option<String>,
refresh_token: Option<String>,
client_id: Option<String>,
client_secret: Option<String>,

http_client: Option<HttpClient>,
}

Expand All @@ -84,15 +93,47 @@ impl Debug for DropboxBuilder {
}

impl DropboxBuilder {
/// default: no access token, which leads to failure
/// Set the root directory for dropbox.
///
/// Default to `/` if not set.
pub fn root(&mut self, root: &str) -> &mut Self {
self.root = Some(root.to_string());
self
}

/// Access token is used for temporary access to the Dropbox API.
///
/// You can get the access token from [Dropbox App Console](https://www.dropbox.com/developers/apps)
///
/// NOTE: this token will be expired in 4 hours. If you are trying to use dropbox services in a long time, please set a refresh_token instead.
pub fn access_token(&mut self, access_token: &str) -> &mut Self {
self.access_token = Some(access_token.to_string());
self
}

/// default: no root path, which leads to failure
pub fn root(&mut self, root: &str) -> &mut Self {
self.root = Some(root.to_string());
/// Refersh token is used for long term access to the Dropbox API.
///
/// You can get the refresh token via OAuth2.0 Flow of dropbox.
///
/// OpenDAL will use this refresh token to get a new access token when the old one is expired.
pub fn refresh_token(&mut self, refresh_token: &str) -> &mut Self {
self.refresh_token = Some(refresh_token.to_string());
self
}

/// Set the client id for dropbox.
///
/// This is required for OAuth2.0 Flow with refresh token.
pub fn client_id(&mut self, client_id: &str) -> &mut Self {
self.client_id = Some(client_id.to_string());
self
}

/// Set the client secret for dropbox.
///
/// This is required for OAuth2.0 Flow with refresh token.
pub fn client_secret(&mut self, client_secret: &str) -> &mut Self {
self.client_secret = Some(client_secret.to_string());
self
}

Expand All @@ -116,6 +157,9 @@ impl Builder for DropboxBuilder {
let mut builder = Self::default();
map.get("root").map(|v| builder.root(v));
map.get("access_token").map(|v| builder.access_token(v));
map.get("refresh_token").map(|v| builder.refresh_token(v));
map.get("client_id").map(|v| builder.client_id(v));
map.get("client_secret").map(|v| builder.client_secret(v));
builder
}

Expand All @@ -129,20 +173,57 @@ impl Builder for DropboxBuilder {
.with_context("service", Scheme::Dropbox)
})?
};
let token = match self.access_token.clone() {
Some(access_token) => access_token,
None => {

let signer = match (self.access_token.take(), self.refresh_token.take()) {
(Some(access_token), None) => DropboxSigner {
access_token,
// We will never expire user specified token.
expires_in: DateTime::<Utc>::MAX_UTC,
..Default::default()
},
(None, Some(refresh_token)) => {
let client_id = self.client_id.take().ok_or_else(|| {
Error::new(
ErrorKind::ConfigInvalid,
"client_id must be set when refresh_token is set",
)
.with_context("service", Scheme::Dropbox)
})?;
let client_secret = self.client_secret.take().ok_or_else(|| {
Error::new(
ErrorKind::ConfigInvalid,
"client_secret must be set when refresh_token is set",
)
.with_context("service", Scheme::Dropbox)
})?;

DropboxSigner {
refresh_token,
client_id,
client_secret,
..Default::default()
}
}
(Some(_), Some(_)) => {
return Err(Error::new(
ErrorKind::ConfigInvalid,
"access_token and refresh_token can not be set at the same time",
)
.with_context("service", Scheme::Dropbox))
}
(None, None) => {
return Err(Error::new(
ErrorKind::ConfigInvalid,
"access_token is required",
))
"access_token or refresh_token must be set",
)
.with_context("service", Scheme::Dropbox))
}
};

Ok(DropboxBackend {
core: Arc::new(DropboxCore {
root,
token,
signer: Arc::new(Mutex::new(signer)),
client,
}),
})
Expand Down
Loading