Skip to content

Commit b7e6082

Browse files
santikaplanTDemeco
authored andcommitted
fix(backend): 🐛 validate file upload against bucket owner instead of file owner (#570)
1 parent 0a9170a commit b7e6082

File tree

3 files changed

+33
-30
lines changed

3 files changed

+33
-30
lines changed

backend/lib/src/services/msp.rs

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::{collections::HashSet, sync::Arc};
44

5-
use alloy_core::primitives::Address;
5+
use alloy_core::{hex::ToHexExt, primitives::Address};
66
use axum_extra::extract::multipart::Field;
77
use bigdecimal::{BigDecimal, RoundingMode};
88
use codec::{Decode, Encode};
@@ -214,9 +214,9 @@ impl MspService {
214214
}))
215215
}
216216

217-
/// Get a specific bucket by ID
217+
/// Get a specific bucket by its ID
218218
///
219-
/// Verifies ownership of bucket is `user` (or that the bucket is public if `user` is `None`)
219+
/// Verifies that the owner of the bucket is `user`. If the bucket is public, this check always passes.
220220
pub async fn get_bucket(
221221
&self,
222222
bucket_id: &str,
@@ -514,18 +514,7 @@ impl MspService {
514514
"Starting file upload"
515515
);
516516

517-
// Retrieve the onchain file info and verify user has permission to access the file
518-
let info = self.get_file_info(user, &file_key).await?;
519-
520-
// Validate bucket id and file key against metadata
521-
let expected_bucket_id = hex::encode(file_metadata.bucket_id());
522-
let bucket_id_without_prefix = info.bucket_id.trim_start_matches("0x");
523-
if bucket_id_without_prefix != expected_bucket_id {
524-
return Err(Error::BadRequest(
525-
format!("Bucket ID in URL does not match file metadata: {expected_bucket_id} != {bucket_id_without_prefix}"),
526-
));
527-
}
528-
517+
// Validate the received file key against the one corresponding to the file metadata.
529518
let expected_file_key = hex::encode(file_metadata.file_key::<Blake2Hasher>());
530519
let file_key_without_prefix = file_key.trim_start_matches("0x");
531520
if file_key_without_prefix != expected_file_key {
@@ -534,6 +523,15 @@ impl MspService {
534523
)));
535524
}
536525

526+
// Get the bucket ID from the metadata and verify that the user is its owner.
527+
// We check the bucket ownership instead of the file ownership as the file might not be in
528+
// the indexer at this point (since the storage request would have to have been finalised).
529+
// TODO: This could still fail as the bucket creation extrinsic might not have been finalised yet,
530+
// ideally we should have a way to directly check on-chain (like an RPC).
531+
let bucket_id = hex::encode(file_metadata.bucket_id());
532+
let bucket = self.get_db_bucket(&bucket_id).await?;
533+
self.can_user_view_bucket(bucket, user)?;
534+
537535
// Initialize the trie that will hold the chunked file data.
538536
let mut trie = InMemoryFileDataTrie::<StorageProofsMerkleTrieLayout>::new();
539537

@@ -674,20 +672,20 @@ impl MspService {
674672
// If the complete file was uploaded to the MSP successfully, we can return the response.
675673
let bytes_location = file_metadata.location();
676674
let location = str::from_utf8(&bytes_location)
677-
.unwrap_or(&info.file_key)
675+
.unwrap_or(&file_key)
678676
.to_string();
679677

680678
debug!(
681-
file_key = %info.file_key,
679+
file_key = %file_key,
682680
chunks = total_chunks,
683681
"File upload completed"
684682
);
685683

686684
Ok(FileUploadResponse {
687685
status: "upload_successful".to_string(),
688-
fingerprint: info.fingerprint_hexstr(),
689-
file_key: info.file_key,
690-
bucket_id: info.bucket_id,
686+
fingerprint: file_metadata.fingerprint().encode_hex_with_prefix(),
687+
file_key: file_key.to_string(),
688+
bucket_id: bucket_id,
691689
location,
692690
})
693691
}
@@ -867,11 +865,11 @@ impl MspService {
867865
}
868866

869867
impl MspService {
870-
/// Verifies user can access the given bucket
868+
/// Verifies that a user can access the given bucket.
871869
///
872-
/// If the bucket is public, the `user` may be `None`
870+
/// If the bucket is public, this check always passes.
873871
///
874-
/// Will return the bucket if the user has the required permissions
872+
/// Will return the bucket metadata if the user has the required permissions, or an error otherwise.
875873
fn can_user_view_bucket(
876874
&self,
877875
bucket: DBBucket,
@@ -880,15 +878,20 @@ impl MspService {
880878
// TODO: NFT ownership
881879
if bucket.private {
882880
let Some(user) = user else {
883-
return Err(Error::Unauthorized("This bucket is private".to_string()));
881+
return Err(Error::Unauthorized(format!(
882+
"Bucket with ID {} is private and no user received.",
883+
bucket.onchain_bucket_id.encode_hex_with_prefix()
884+
)));
884885
};
885886

886887
if bucket.account.as_str() == user.to_string() {
887888
Ok(bucket)
888889
} else {
889-
Err(Error::Unauthorized(
890-
"Specified user is not authorized to view this bucket".to_string(),
891-
))
890+
Err(Error::Unauthorized(format!(
891+
"User {} is not authorized to view bucket with ID {}",
892+
user,
893+
bucket.onchain_bucket_id.encode_hex_with_prefix()
894+
)))
892895
}
893896
} else {
894897
Ok(bucket)

test/suites/integration/backend/upload-download-file.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ await describeMspNet(
273273
const uploadResult = JSON.parse(responseBody);
274274
uploadedFileKeyHex = u8aToHex(fileKey);
275275
strictEqual(
276-
`0x${uploadResult.fileKey}`,
276+
uploadResult.fileKey,
277277
uploadedFileKeyHex,
278278
"Response should contain correct file key"
279279
);

test/suites/integration/solochain-evm/sdk-precompiles.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ await describeMspNet(
398398
// Check that the upload was successful
399399
strictEqual(uploadResponse.status, "upload_successful", "Upload should return success");
400400
strictEqual(
401-
`0x${uploadResponse.fileKey}`,
401+
uploadResponse.fileKey,
402402
fileKey.toHex(),
403403
"Upload should return expected file key"
404404
);
@@ -408,7 +408,7 @@ await describeMspNet(
408408
"Upload should return expected bucket ID"
409409
);
410410
strictEqual(
411-
`0x${uploadResponse.fingerprint}`,
411+
uploadResponse.fingerprint,
412412
(await fileManager.getFingerprint()).toString(),
413413
"Upload should return expected fingerprint"
414414
);

0 commit comments

Comments
 (0)