Skip to content

Commit

Permalink
feat(services): add optional access_token for AliyunDrive
Browse files Browse the repository at this point in the history
  • Loading branch information
yuchanns committed Jun 15, 2024
1 parent 40b15c8 commit eed5092
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 61 deletions.
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

0 comments on commit eed5092

Please sign in to comment.