Skip to content
Open
Changes from 1 commit
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
178 changes: 18 additions & 160 deletions crates/bitwarden-send/src/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,6 @@ pub enum GetFileDownloadDataError {

// ===== HTTP request functions =====

async fn access_send_v1(
api_client: &ApiClient,
send_id: &str,
password: Option<String>,
) -> Result<SendAccessResponse, AccessSendError> {
let resp = api_client
.sends_api()
.access(send_id, Some(models::SendAccessRequestModel { password }))
.await
.map_err(ApiError::from)?;
Ok(resp.try_into()?)
}

async fn access_send(
api_client: &ApiClient,
access_token: &str,
Expand All @@ -126,24 +113,6 @@ async fn access_send(
Ok(resp.try_into()?)
}

async fn get_file_download_data_v1(
api_client: &ApiClient,
send_id: &str,
file_id: &str,
password: Option<String>,
) -> Result<SendFileDownloadData, GetFileDownloadDataError> {
let resp = api_client
.sends_api()
.get_send_file_download_data(
send_id,
file_id,
Some(models::SendAccessRequestModel { password }),
)
.await
.map_err(ApiError::from)?;
Ok(resp.into())
}

async fn get_file_download_data(
api_client: &ApiClient,
file_id: &str,
Expand Down Expand Up @@ -196,20 +165,7 @@ impl From<models::SendFileDownloadDataResponseModel> for SendFileDownloadData {

#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl SendClient {
/// Accesses a send using the V1 (legacy) API endpoint.
/// The `password` is the SHA256 hash of the user-entered password, if the send is
/// password-protected. The returned [SendAccessResponse] contains encrypted fields that must
/// be decrypted client-side using the key derived from the URL fragment.
pub async fn access_send_v1(
&self,
send_id: String,
password: Option<String>,
) -> Result<SendAccessResponse, AccessSendError> {
let config = self.client.internal.get_api_configurations();
access_send_v1(&config.api_client, &send_id, password).await
}

/// Accesses a send using the V2 API endpoint, authenticated with a send access token.
/// Accesses a send, authenticated with a send access token.
/// The returned [SendAccessResponse] contains encrypted fields that must be decrypted
/// client-side using the key derived from the URL fragment.
pub async fn access_send(
Expand All @@ -220,20 +176,7 @@ impl SendClient {
access_send(&config.api_client, &access_token).await
}

/// Gets file download data for a file send using the V1 (legacy) API endpoint.
/// The `password` is the SHA256 hash of the user-entered password, if the send is
/// password-protected.
pub async fn get_file_download_data_v1(
&self,
send_id: String,
file_id: String,
password: Option<String>,
) -> Result<SendFileDownloadData, GetFileDownloadDataError> {
let config = self.client.internal.get_api_configurations();
get_file_download_data_v1(&config.api_client, &send_id, &file_id, password).await
}

/// Gets file download data for a file send using the V2 API endpoint, authenticated
/// Gets file download data for a file send, authenticated
/// with a send access token.
pub async fn get_file_download_data(
&self,
Expand Down Expand Up @@ -261,17 +204,15 @@ mod tests {
const FILE_ID: &str = "file-id-abc";
const ACCESS_TOKEN: &str = "send-access-token";

// ===== access_send_v1 =====
// ===== access_send =====

#[tokio::test]
async fn test_access_send_v1_text() {
async fn test_access_send_text() {
let api_client = ApiClient::new_mocked(|mock| {
mock.sends_api
.expect_access()
.returning(|id, request| {
assert_eq!(id, SEND_ID);
let request = request.expect("request body should be present");
assert_eq!(request.password, Some("hashed-password".to_string()));
.expect_access_using_auth()
.returning(|token| {
assert_eq!(token, ACCESS_TOKEN);
Ok(SendAccessResponseModel {
object: Some("send-access".to_string()),
id: Some(SEND_ID.to_string()),
Expand All @@ -280,57 +221,29 @@ mod tests {
name: Some("encrypted-name".to_string()),
file: None,
text: Some(Box::new(SendTextModel {
text: Some("encrypted-text".to_string()),
hidden: Some(true),
text: Some("encrypted_send_text".to_string()),
hidden: Some(false),
})),
expiration_date: Some("2025-01-10T00:00:00Z".to_string()),
creator_identifier: Some("user@example.com".to_string()),
expiration_date: None,
creator_identifier: None,
})
})
.once();
});

let result = access_send_v1(&api_client, SEND_ID, Some("hashed-password".to_string()))
.await
.unwrap();
let result = access_send(&api_client, ACCESS_TOKEN).await.unwrap();

assert_eq!(result.id, Some(SEND_ID.to_string()));
assert_eq!(result.type_, Some(crate::SendType::Text));
assert_eq!(result.type_, Some(crate::SendType::File));
Comment thread
mcamirault marked this conversation as resolved.
Outdated
assert_eq!(result.name, Some("encrypted-name".to_string()));
let text = result.text.expect("text variant should be populated");
assert_eq!(text.text, Some("encrypted-text".to_string()));
assert!(text.hidden);
assert!(result.file.is_none());
assert_eq!(
result.expiration_date,
Some("2025-01-10T00:00:00Z".parse::<DateTime<Utc>>().unwrap())
);
assert_eq!(
result.creator_identifier,
Some("user@example.com".to_string())
);
}

#[tokio::test]
async fn test_access_send_v1_http_error() {
let api_client = ApiClient::new_mocked(|mock| {
mock.sends_api
.expect_access()
.returning(|_id, _request| {
Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other(
"Simulated error",
)))
})
.once();
});

let result = access_send_v1(&api_client, SEND_ID, None).await;

assert!(matches!(result.unwrap_err(), AccessSendError::Api(_)));
let text = result.text.expect("text variant should be populated");
assert_eq!(text.text, Some("encrypted_send_text".to_string()));
assert!(!text.hidden);
assert_eq!(result.expiration_date, None);
assert_eq!(result.creator_identifier, None);
}

// ===== access_send =====

#[tokio::test]
async fn test_access_send_file() {
let api_client = ApiClient::new_mocked(|mock| {
Expand Down Expand Up @@ -391,61 +304,6 @@ mod tests {
assert!(matches!(result.unwrap_err(), AccessSendError::Api(_)));
}

// ===== get_file_download_data_v1 =====

#[tokio::test]
async fn test_get_file_download_data_v1() {
let api_client = ApiClient::new_mocked(|mock| {
mock.sends_api
.expect_get_send_file_download_data()
.returning(|send_id, file_id, request| {
assert_eq!(send_id, SEND_ID);
assert_eq!(file_id, FILE_ID);
let request = request.expect("request body should be present");
assert_eq!(request.password, Some("hashed-password".to_string()));
Ok(SendFileDownloadDataResponseModel {
object: Some("send-fileDownload".to_string()),
id: Some(FILE_ID.to_string()),
url: Some("https://example.com/download".to_string()),
})
})
.once();
});

let result = get_file_download_data_v1(
&api_client,
SEND_ID,
FILE_ID,
Some("hashed-password".to_string()),
)
.await
.unwrap();

assert_eq!(result.id, Some(FILE_ID.to_string()));
assert_eq!(result.url, Some("https://example.com/download".to_string()));
}

#[tokio::test]
async fn test_get_file_download_data_v1_http_error() {
let api_client = ApiClient::new_mocked(|mock| {
mock.sends_api
.expect_get_send_file_download_data()
.returning(|_send_id, _file_id, _request| {
Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other(
"Simulated error",
)))
})
.once();
});

let result = get_file_download_data_v1(&api_client, SEND_ID, FILE_ID, None).await;

assert!(matches!(
result.unwrap_err(),
GetFileDownloadDataError::Api(_)
));
}

// ===== get_file_download_data =====

#[tokio::test]
Expand Down
Loading