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.