From 200f746d5b2bb105b50a041b60ad54ac70ff8340 Mon Sep 17 00:00:00 2001 From: yuchanns Date: Sun, 16 Jun 2024 00:31:42 +0800 Subject: [PATCH] feat(services): add optional access_token for AliyunDrive --- core/src/services/aliyun_drive/backend.rs | 81 ++++++++++++----------- core/src/services/aliyun_drive/core.rs | 68 +++++++++++++------ core/src/services/aliyun_drive/docs.md | 1 + 3 files changed, 89 insertions(+), 61 deletions(-) diff --git a/core/src/services/aliyun_drive/backend.rs b/core/src/services/aliyun_drive/backend.rs index cb414ed8779..766c0599556 100644 --- a/core/src/services/aliyun_drive/backend.rs +++ b/core/src/services/aliyun_drive/backend.rs @@ -49,18 +49,24 @@ pub struct AliyunDriveConfig { /// /// default to `/` if not set. pub root: Option, + /// 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, /// client_id of this backend. /// - /// required. - pub client_id: String, + /// required if no access_token is provided. + pub client_id: Option, /// client_secret of this backend. /// - /// required. - pub client_secret: String, + /// required if no access_token is provided. + pub client_secret: Option, /// refresh_token of this backend. /// - /// required. - pub refresh_token: String, + /// required if no access_token is provided. + pub refresh_token: Option, /// drive_type of this backend. /// /// All operations will happen under this type of drive. @@ -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 } @@ -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, @@ -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, }), diff --git a/core/src/services/aliyun_drive/core.rs b/core/src/services/aliyun_drive/core.rs index 19351d681dd..f9720b7702d 100644 --- a/core/src/services/aliyun_drive/core.rs +++ b/core/src/services/aliyun_drive/core.rs @@ -48,18 +48,20 @@ pub enum DriveType { Resource, } +/// Available Aliyun Drive Signer Set +pub enum AliyunDriveSign { + Refresh(String, String, String, Option, i64), + Access(String), +} + pub struct AliyunDriveSigner { pub drive_id: Option, - pub access_token: Option, - 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, @@ -78,6 +80,9 @@ impl Debug for AliyunDriveCore { impl AliyunDriveCore { async fn send(&self, mut req: Request, token: Option<&str>) -> Result { + // AliyunDrive raise NullPointerException if you haven't set a user-agent. + req.headers_mut() + .insert(header::USER_AGENT, HeaderValue::from_static("opendal")); if req.method() == Method::POST { req.headers_mut().insert( header::CONTENT_TYPE, @@ -98,12 +103,17 @@ impl AliyunDriveCore { Ok(res.into_body()) } - async fn get_access_token(&self, refresh_token: &str) -> Result { + async fn get_access_token( + &self, + client_id: &str, + client_secret: &str, + refresh_token: &str, + ) -> Result { 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)) @@ -120,17 +130,31 @@ impl AliyunDriveCore { } pub async fn get_token_and_drive(&self) -> Result<(Option, 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 { @@ -138,10 +162,10 @@ impl AliyunDriveCore { 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 { diff --git a/core/src/services/aliyun_drive/docs.md b/core/src/services/aliyun_drive/docs.md index 689e45080cf..b356ebe3f30 100644 --- a/core/src/services/aliyun_drive/docs.md +++ b/core/src/services/aliyun_drive/docs.md @@ -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.