This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 6e18b9409a feat(services): add optional access_token for AliyunDrive 
(#4740)
6e18b9409a is described below

commit 6e18b9409a6e82a54930296d82a3cf8da9470cd8
Author: Hanchin Hsieh <[email protected]>
AuthorDate: Sun Jun 16 02:37:22 2024 +0800

    feat(services): add optional access_token for AliyunDrive (#4740)
---
 core/src/services/aliyun_drive/backend.rs | 81 ++++++++++++++++---------------
 core/src/services/aliyun_drive/core.rs    | 71 ++++++++++++++++++---------
 core/src/services/aliyun_drive/docs.md    |  1 +
 3 files changed, 92 insertions(+), 61 deletions(-)

diff --git a/core/src/services/aliyun_drive/backend.rs 
b/core/src/services/aliyun_drive/backend.rs
index cb414ed877..766c059955 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<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.
@@ -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 19351d681d..51f30665c7 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<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,
 
@@ -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,
@@ -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))
@@ -120,17 +133,31 @@ 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 {
@@ -138,10 +165,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<Buffer> {
diff --git a/core/src/services/aliyun_drive/docs.md 
b/core/src/services/aliyun_drive/docs.md
index 689e45080c..b356ebe3f3 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.

Reply via email to