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): add optional access_token for AliyunDrive #4740

Merged
merged 1 commit into from
Jun 15, 2024
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
81 changes: 42 additions & 39 deletions core/src/services/aliyun_drive/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,24 @@ pub struct AliyunDriveConfig {
///
/// default to `/` if not set.
pub root: Option<String>,
/// access_token of this backend.
///
/// Solution for client-only purpose. #4733
///
/// required if no client_id, client_secret and refresh_token are provided.
pub access_token: Option<String>,
/// client_id of this backend.
///
/// required.
pub client_id: String,
/// required if no access_token is provided.
pub client_id: Option<String>,
/// client_secret of this backend.
///
/// required.
pub client_secret: String,
/// required if no access_token is provided.
pub client_secret: Option<String>,
/// refresh_token of this backend.
///
/// required.
pub refresh_token: String,
/// required if no access_token is provided.
pub refresh_token: Option<String>,
/// drive_type of this backend.
///
/// All operations will happen under this type of drive.
Expand Down Expand Up @@ -119,23 +125,30 @@ impl AliyunDriveBuilder {
self
}

/// Set access_token of this backend.
pub fn access_token(&mut self, access_token: &str) -> &mut Self {
self.config.access_token = Some(access_token.to_string());

self
}

/// Set client_id of this backend.
pub fn client_id(&mut self, client_id: &str) -> &mut Self {
self.config.client_id = client_id.to_string();
self.config.client_id = Some(client_id.to_string());

self
}

/// Set client_secret of this backend.
pub fn client_secret(&mut self, client_secret: &str) -> &mut Self {
self.config.client_secret = client_secret.to_string();
self.config.client_secret = Some(client_secret.to_string());

self
}

/// Set refresh_token of this backend.
pub fn refresh_token(&mut self, refresh_token: &str) -> &mut Self {
self.config.refresh_token = refresh_token.to_string();
self.config.refresh_token = Some(refresh_token.to_string());

self
}
Expand Down Expand Up @@ -196,32 +209,26 @@ impl Builder for AliyunDriveBuilder {
})?
};

let client_id = self.config.client_id.clone();
if client_id.is_empty() {
return Err(
Error::new(ErrorKind::ConfigInvalid, "client_id is missing.")
.with_operation("Builder::build")
.with_context("service", Scheme::AliyunDrive),
);
}

let client_secret = self.config.client_secret.clone();
if client_secret.is_empty() {
return Err(
Error::new(ErrorKind::ConfigInvalid, "client_secret is missing.")
.with_operation("Builder::build")
.with_context("service", Scheme::AliyunDrive),
);
}

let refresh_token = self.config.refresh_token.clone();
if refresh_token.is_empty() {
return Err(
Error::new(ErrorKind::ConfigInvalid, "refresh_token is missing.")
let sign = match self.config.access_token.clone() {
Some(access_token) if !access_token.is_empty() => {
AliyunDriveSign::Access(access_token)
}
_ => match (
self.config.client_id.clone(),
self.config.client_secret.clone(),
self.config.refresh_token.clone(),
) {
(Some(client_id), Some(client_secret), Some(refresh_token)) if
!client_id.is_empty() && !client_secret.is_empty() && !refresh_token.is_empty() => {
AliyunDriveSign::Refresh(client_id, client_secret, refresh_token, None, 0)
}
_ => return Err(Error::new(
ErrorKind::ConfigInvalid,
"access_token and a set of client_id, client_secret, and refresh_token are both missing.")
.with_operation("Builder::build")
.with_context("service", Scheme::AliyunDrive),
);
}
.with_context("service", Scheme::AliyunDrive)),
},
};

let drive_type = match self.config.drive_type.as_str() {
"" | "default" => DriveType::Default,
Expand All @@ -243,15 +250,11 @@ impl Builder for AliyunDriveBuilder {
core: Arc::new(AliyunDriveCore {
endpoint: "https://openapi.alipan.com".to_string(),
root,
client_id,
client_secret,
drive_type,
rapid_upload,
signer: Arc::new(Mutex::new(AliyunDriveSigner {
drive_id: None,
access_token: None,
refresh_token,
expire_at: 0,
sign,
})),
client,
}),
Expand Down
71 changes: 49 additions & 22 deletions core/src/services/aliyun_drive/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,20 @@ pub enum DriveType {
Resource,
}

/// Available Aliyun Drive Signer Set
pub enum AliyunDriveSign {
Refresh(String, String, String, Option<String>, i64),
Access(String),
}

pub struct AliyunDriveSigner {
pub drive_id: Option<String>,
pub access_token: Option<String>,
pub refresh_token: String,
pub expire_at: i64,
pub sign: AliyunDriveSign,
}

pub struct AliyunDriveCore {
pub endpoint: String,
pub root: String,
pub client_id: String,
pub client_secret: String,
pub drive_type: DriveType,
pub rapid_upload: bool,

Expand All @@ -78,6 +80,12 @@ impl Debug for AliyunDriveCore {

impl AliyunDriveCore {
async fn send(&self, mut req: Request<Buffer>, token: Option<&str>) -> Result<Buffer> {
// AliyunDrive raise NullPointerException if you haven't set a user-agent.
req.headers_mut().insert(
header::USER_AGENT,
HeaderValue::from_str(&format!("opendal/{}", VERSION))
.expect("user agent must be valid header value"),
);
if req.method() == Method::POST {
req.headers_mut().insert(
header::CONTENT_TYPE,
Expand All @@ -98,12 +106,17 @@ impl AliyunDriveCore {
Ok(res.into_body())
}

async fn get_access_token(&self, refresh_token: &str) -> Result<Buffer> {
async fn get_access_token(
&self,
client_id: &str,
client_secret: &str,
refresh_token: &str,
) -> Result<Buffer> {
let body = serde_json::to_vec(&AccessTokenRequest {
refresh_token,
grant_type: "refresh_token",
client_id: &self.client_id,
client_secret: &self.client_secret,
client_id,
client_secret,
})
.map_err(new_json_serialize_error)?;
let req = Request::post(format!("{}/oauth/access_token", self.endpoint))
Expand All @@ -120,28 +133,42 @@ impl AliyunDriveCore {
}

pub async fn get_token_and_drive(&self) -> Result<(Option<String>, String)> {
let mut tokener = self.signer.lock().await;
if tokener.expire_at < Utc::now().timestamp() || tokener.access_token.is_none() {
let res = self.get_access_token(&tokener.refresh_token).await?;
let output: RefreshTokenResponse =
serde_json::from_reader(res.reader()).map_err(new_json_deserialize_error)?;
tokener.access_token = Some(output.access_token);
tokener.expire_at = output.expires_in + Utc::now().timestamp();
tokener.refresh_token = output.refresh_token;
}
let Some(drive_id) = &tokener.drive_id else {
let res = self.get_drive_id(tokener.access_token.as_deref()).await?;
let mut signer = self.signer.lock().await;
let token = match &mut signer.sign {
AliyunDriveSign::Access(access_token) => Some(access_token.clone()),
AliyunDriveSign::Refresh(
client_id,
client_secret,
refresh_token,
access_token,
expire_at,
) => {
if *expire_at < Utc::now().timestamp() || access_token.is_none() {
let res = self
.get_access_token(client_id, client_secret, refresh_token)
.await?;
let output: RefreshTokenResponse = serde_json::from_reader(res.reader())
.map_err(new_json_deserialize_error)?;
*access_token = Some(output.access_token);
*expire_at = output.expires_in + Utc::now().timestamp();
*refresh_token = output.refresh_token;
}
access_token.clone()
}
};
let Some(drive_id) = &signer.drive_id else {
let res = self.get_drive_id(token.as_deref()).await?;
let output: DriveInfoResponse =
serde_json::from_reader(res.reader()).map_err(new_json_deserialize_error)?;
let drive_id = match self.drive_type {
DriveType::Default => output.default_drive_id,
DriveType::Backup => output.backup_drive_id.unwrap_or(output.default_drive_id),
DriveType::Resource => output.resource_drive_id.unwrap_or(output.default_drive_id),
};
tokener.drive_id = Some(drive_id.clone());
return Ok((tokener.access_token.clone(), drive_id));
signer.drive_id = Some(drive_id.clone());
return Ok((token, drive_id));
};
Ok((tokener.access_token.clone(), drive_id.clone()))
Ok((token, drive_id.clone()))
}

pub async fn get_by_path(&self, path: &str) -> Result<Buffer> {
Expand Down
1 change: 1 addition & 0 deletions core/src/services/aliyun_drive/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This service can be used to:
## Configuration

- `root`: Set the work dir for backend.
- `access_token`: Set the access_token for backend.
- `client_id`: Set the client_id for backend.
- `client_secret`: Set the client_secret for backend.
- `refresh_token`: Set the refresh_token for backend.
Expand Down
Loading