diff --git a/Cargo.lock b/Cargo.lock index 4dbd076..5a9eb2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,7 +1363,7 @@ dependencies = [ [[package]] name = "ic-oss" -version = "0.6.5" +version = "0.6.6" dependencies = [ "bytes", "candid", @@ -1382,7 +1382,7 @@ dependencies = [ [[package]] name = "ic-oss-can" -version = "0.6.5" +version = "0.6.6" dependencies = [ "bytes", "candid", @@ -1396,7 +1396,7 @@ dependencies = [ [[package]] name = "ic-oss-cli" -version = "0.6.5" +version = "0.6.6" dependencies = [ "anyhow", "bytes", @@ -1423,7 +1423,7 @@ dependencies = [ [[package]] name = "ic-oss-cose" -version = "0.6.5" +version = "0.6.6" dependencies = [ "base64 0.21.7", "candid", @@ -1444,7 +1444,7 @@ dependencies = [ [[package]] name = "ic-oss-types" -version = "0.6.5" +version = "0.6.6" dependencies = [ "base64 0.21.7", "candid", @@ -1517,7 +1517,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_oss_bucket" -version = "0.6.5" +version = "0.6.6" dependencies = [ "base64 0.21.7", "bytes", @@ -1546,7 +1546,7 @@ dependencies = [ [[package]] name = "ic_oss_cluster" -version = "0.6.5" +version = "0.6.6" dependencies = [ "base64 0.21.7", "bytes", diff --git a/Cargo.toml b/Cargo.toml index e649d11..9869d7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ strip = true opt-level = 's' [workspace.package] -version = "0.6.5" +version = "0.6.6" edition = "2021" repository = "https://github.com/ldclabs/ic-oss" keywords = ["file", "storage", "oss", "s3", "icp"] diff --git a/canister_ids.json b/canister_ids.json index 9c50c6a..cb1b08e 100644 --- a/canister_ids.json +++ b/canister_ids.json @@ -1,5 +1,8 @@ { "ic_oss_bucket": { "ic": "mmrxu-fqaaa-aaaap-ahhna-cai" + }, + "ic_oss_cluster": { + "ic": "x5573-nqaaa-aaaap-ahopq-cai" } } \ No newline at end of file diff --git a/src/ic_oss/src/bucket.rs b/src/ic_oss/src/bucket.rs index 879442a..ed85560 100644 --- a/src/ic_oss/src/bucket.rs +++ b/src/ic_oss/src/bucket.rs @@ -439,7 +439,6 @@ impl Client { ) .await?; let out = out?; - Ok(Progress { filled: out.filled, size, diff --git a/src/ic_oss_bucket/README.md b/src/ic_oss_bucket/README.md index cae5a44..858bb94 100644 --- a/src/ic_oss_bucket/README.md +++ b/src/ic_oss_bucket/README.md @@ -71,7 +71,7 @@ ic-oss-cli -i debug/uploader.pem identity # principal: nprym-ylvyz-ig3fr-lgcmn-zzzt4-tyuix-3v6bm-fsel7-6lq6x-zh2w7-zqe # add managers -dfx canister call ic_oss_bucket admin_set_managers "(vec {principal \"$MYID\"; principal \"nprym-ylvyz-ig3fr-lgcmn-zzzt4-tyuix-3v6bm-fsel7-6lq6x-zh2w7-zqe\"; principal \"pxfqr-x3orr-z5yip-7yzdd-hyxgd-dktgh-3awsk-ohzma-lfjzi-753j7-tae\"})" +dfx canister call ic_oss_bucket admin_set_managers "(vec {principal \"$MYID\"; principal \"nprym-ylvyz-ig3fr-lgcmn-zzzt4-tyuix-3v6bm-fsel7-6lq6x-zh2w7-zqe\"})" # add public keys to verify the access tokens dfx canister call ic_oss_bucket admin_update_bucket '(record { @@ -110,7 +110,6 @@ dfx canister call ic_oss_bucket create_folder "(record { parent = 0; name = \"home\"; }, null)" -dfx canister call ic_oss_bucket list_folders '(0, null)' dfx canister call ic_oss_bucket create_folder "(record { parent = 1; diff --git a/src/ic_oss_bucket/src/api_admin.rs b/src/ic_oss_bucket/src/api_admin.rs index d563370..393eeeb 100644 --- a/src/ic_oss_bucket/src/api_admin.rs +++ b/src/ic_oss_bucket/src/api_admin.rs @@ -13,7 +13,7 @@ fn admin_set_managers(args: BTreeSet) -> Result<(), String> { Ok(()) } -#[ic_cdk::query] +#[ic_cdk::update] fn validate_admin_set_managers(args: BTreeSet) -> Result<(), String> { if args.is_empty() { return Err("managers cannot be empty".to_string()); @@ -33,7 +33,7 @@ fn admin_set_auditors(args: BTreeSet) -> Result<(), String> { Ok(()) } -#[ic_cdk::query] +#[ic_cdk::update] fn validate_admin_set_auditors(args: BTreeSet) -> Result<(), String> { if args.is_empty() { return Err("auditors cannot be empty".to_string()); @@ -82,7 +82,7 @@ fn admin_update_bucket(args: UpdateBucketInput) -> Result<(), String> { Ok(()) } -#[ic_cdk::query] +#[ic_cdk::update] fn validate_admin_update_bucket(args: UpdateBucketInput) -> Result<(), String> { args.validate() } diff --git a/src/ic_oss_bucket/src/api_http.rs b/src/ic_oss_bucket/src/api_http.rs index 6a49993..f6aeece 100644 --- a/src/ic_oss_bucket/src/api_http.rs +++ b/src/ic_oss_bucket/src/api_http.rs @@ -125,15 +125,15 @@ fn http_request(request: HttpRequest) -> HttpStreamingResponse { }; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, param.token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((status_code, err)) => { return HttpStreamingResponse { status_code, @@ -152,7 +152,16 @@ fn http_request(request: HttpRequest) -> HttpStreamingResponse { ..Default::default() }, Some(file) => { - if !permission::check_file_read(&ps, &canister, id, file.parent) { + if file.status < 0 && ctx.role < store::Role::Auditor { + return HttpStreamingResponse { + status_code: 403, + headers, + body: ByteBuf::from("file archived".as_bytes()), + ..Default::default() + }; + } + + if !permission::check_file_read(&ctx.ps, &canister, id, file.parent) { return HttpStreamingResponse { status_code: 403, headers, diff --git a/src/ic_oss_bucket/src/api_init.rs b/src/ic_oss_bucket/src/api_init.rs index 63354b1..31c7e92 100644 --- a/src/ic_oss_bucket/src/api_init.rs +++ b/src/ic_oss_bucket/src/api_init.rs @@ -37,9 +37,9 @@ impl UpgradeArgs { if max_file_size == 0 { return Err("max_file_size should be greater than 0".to_string()); } - if max_file_size < MAX_FILE_SIZE { + if max_file_size >= MAX_FILE_SIZE { return Err(format!( - "max_file_size should be greater than or equal to {}", + "max_file_size should be smaller than or equal to {}", MAX_FILE_SIZE )); } diff --git a/src/ic_oss_bucket/src/api_query.rs b/src/ic_oss_bucket/src/api_query.rs index 4c1c985..e8f13af 100644 --- a/src/ic_oss_bucket/src/api_query.rs +++ b/src/ic_oss_bucket/src/api_query.rs @@ -16,21 +16,21 @@ fn api_version() -> u16 { #[ic_cdk::query] fn get_bucket_info(access_token: Option) -> Result { let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_bucket_read(&ps, &canister) { + if !permission::check_bucket_read(&ctx.ps, &canister) { return Err("permission denied".to_string()); } @@ -61,21 +61,21 @@ fn get_file_info(id: u32, access_token: Option) -> Result Err("file not found".to_string()), Some(file) => { let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_read(&ps, &canister, id, file.parent) { + if !permission::check_file_read(&ctx.ps, &canister, id, file.parent) { Err("permission denied".to_string())?; } @@ -99,21 +99,21 @@ fn get_file_ancestors(id: u32, access_token: Option) -> Result ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_read(&ps, &canister, id, parent.id) { + if !permission::check_file_read(&ctx.ps, &canister, id, parent.id) { Err("permission denied".to_string())?; } } @@ -131,21 +131,25 @@ fn get_file_chunks( None => Err("file not found".to_string()), Some(file) => { let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_read(&ps, &canister, id, file.parent) { + if file.status < 0 && ctx.role < store::Role::Auditor { + Err("file archived".to_string())?; + } + + if !permission::check_file_read(&ctx.ps, &canister, id, file.parent) { Err("permission denied".to_string())?; } @@ -164,24 +168,24 @@ fn list_files( let prev = prev.unwrap_or(u32::MAX); let take = take.unwrap_or(10).min(100); let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_list(&ps, &canister, parent) { + if !permission::check_file_list(&ctx.ps, &canister, parent) { Err("permission denied".to_string())?; } - Ok(store::fs::list_files(parent, prev, take)) + Ok(store::fs::list_files(&ctx, parent, prev, take)) } #[ic_cdk::query] @@ -190,21 +194,21 @@ fn get_folder_info(id: u32, access_token: Option) -> Result Err("folder not found".to_string()), Some(meta) => { let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_folder_read(&ps, &canister, id) { + if !permission::check_folder_read(&ctx.ps, &canister, id) { Err("permission denied".to_string())?; } @@ -218,21 +222,21 @@ fn get_folder_ancestors(id: u32, access_token: Option) -> Result ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_folder_read(&ps, &canister, id) { + if !permission::check_folder_read(&ctx.ps, &canister, id) { Err("permission denied".to_string())?; } } @@ -250,22 +254,22 @@ fn list_folders( let take = take.unwrap_or(10).min(100); let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.read_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_folder_list(&ps, &canister, parent) { + if !permission::check_folder_list(&ctx.ps, &canister, parent) { Err("permission denied".to_string())?; } - Ok(store::fs::list_folders(parent, prev, take)) + Ok(store::fs::list_folders(&ctx, parent, prev, take)) } diff --git a/src/ic_oss_bucket/src/api_update.rs b/src/ic_oss_bucket/src/api_update.rs index 953f952..71b2a6e 100644 --- a/src/ic_oss_bucket/src/api_update.rs +++ b/src/ic_oss_bucket/src/api_update.rs @@ -30,16 +30,16 @@ fn create_file( let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_create(&ps, &canister, input.parent) { + if !permission::check_file_create(&ctx.ps, &canister, input.parent) { Err("permission denied".to_string())?; } @@ -120,10 +120,10 @@ fn update_file_info( let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } @@ -131,7 +131,7 @@ fn update_file_info( let id = input.id; store::fs::update_file(input, now_ms, |file| { - match permission::check_file_update(&ps, &canister, id, file.parent) { + match permission::check_file_update(&ctx.ps, &canister, id, file.parent) { true => Ok(()), false => Err("permission denied".to_string()), } @@ -152,15 +152,15 @@ fn update_file_chunk( let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { + let ctx = match store::state::with(|s| { s.write_permission( - &ic_cdk::caller(), + ic_cdk::caller(), &canister, access_token, ic_cdk::api::time() / SECONDS, ) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } @@ -172,7 +172,7 @@ fn update_file_chunk( input.chunk_index, now_ms, input.content.into_vec(), - |file| match permission::check_file_update(&ps, &canister, id, file.parent) { + |file| match permission::check_file_update(&ctx.ps, &canister, id, file.parent) { true => Ok(()), false => Err("permission denied".to_string()), }, @@ -188,20 +188,20 @@ fn update_file_chunk( fn move_file(input: MoveInput, access_token: Option) -> Result { let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_delete(&ps, &canister, input.from) { + if !permission::check_file_delete(&ctx.ps, &canister, input.from) { Err("permission denied".to_string())?; } - if !permission::check_file_create(&ps, &canister, input.to) { + if !permission::check_file_create(&ctx.ps, &canister, input.to) { Err("permission denied".to_string())?; } @@ -213,17 +213,17 @@ fn move_file(input: MoveInput, access_token: Option) -> Result) -> Result { let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; store::fs::delete_file(id, now_ms, |file| { - match permission::check_file_delete(&ps, &canister, file.parent) { + match permission::check_file_delete(&ctx.ps, &canister, file.parent) { true => Ok(()), false => Err("permission denied".to_string()), } @@ -238,16 +238,16 @@ fn batch_delete_subfiles( ) -> Result, String> { let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_file_delete(&ps, &canister, parent) { + if !permission::check_file_delete(&ctx.ps, &canister, parent) { Err("permission denied".to_string())?; } @@ -262,16 +262,16 @@ fn create_folder( input.validate()?; let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_folder_create(&ps, &canister, input.parent) { + if !permission::check_folder_create(&ctx.ps, &canister, input.parent) { Err("permission denied".to_string())?; } @@ -308,10 +308,10 @@ fn update_folder_info( let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } @@ -321,7 +321,7 @@ fn update_folder_info( store::fs::update_folder( input, now_ms, - |folder| match permission::check_folder_update(&ps, &canister, id, folder.parent) { + |folder| match permission::check_folder_update(&ctx.ps, &canister, id, folder.parent) { true => Ok(()), false => Err("permission denied".to_string()), }, @@ -337,20 +337,20 @@ fn move_folder( ) -> Result { let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; - if !permission::check_folder_delete(&ps, &canister, input.from) { + if !permission::check_folder_delete(&ctx.ps, &canister, input.from) { Err("permission denied".to_string())?; } - if !permission::check_folder_create(&ps, &canister, input.to) { + if !permission::check_folder_create(&ctx.ps, &canister, input.to) { Err("permission denied".to_string())?; } @@ -362,17 +362,17 @@ fn move_folder( fn delete_folder(id: u32, access_token: Option) -> Result { let now_ms = ic_cdk::api::time() / MILLISECONDS; let canister = ic_cdk::id(); - let ps = match store::state::with(|s| { - s.write_permission(&ic_cdk::caller(), &canister, access_token, now_ms / 1000) + let ctx = match store::state::with(|s| { + s.write_permission(ic_cdk::caller(), &canister, access_token, now_ms / 1000) }) { - Ok(ps) => ps, + Ok(ctx) => ctx, Err((_, err)) => { return Err(err); } }; store::fs::delete_folder(id, now_ms, |folder| { - match permission::check_file_delete(&ps, &canister, folder.parent) { + match permission::check_file_delete(&ctx.ps, &canister, folder.parent) { true => Ok(()), false => Err("permission denied".to_string()), } diff --git a/src/ic_oss_bucket/src/store.rs b/src/ic_oss_bucket/src/store.rs index 4109af7..32494b6 100644 --- a/src/ic_oss_bucket/src/store.rs +++ b/src/ic_oss_bucket/src/store.rs @@ -73,24 +73,50 @@ impl Default for Bucket { } } +#[derive(Clone, Debug)] +pub struct Context { + pub caller: Principal, + pub ps: Policies, + pub role: Role, +} + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum Role { + User, + Auditor, + Manager, +} + impl Bucket { pub fn read_permission( &self, - caller: &Principal, + caller: Principal, canister: &Principal, sign1_token: Option, now_sec: u64, - ) -> Result { + ) -> Result { + let mut ctx = Context { + caller, + ps: Policies::read(), + role: if self.managers.contains(&caller) { + Role::Manager + } else if self.auditors.contains(&caller) { + Role::Auditor + } else { + Role::User + }, + }; + if self.status < 0 { - if self.managers.contains(caller) || self.auditors.contains(caller) { - return Ok(Policies::all()); + if ctx.role >= Role::Auditor { + return Ok(ctx); } Err((403, "bucket is archived".to_string()))?; } - if self.visibility > 0 || self.managers.contains(caller) || self.auditors.contains(caller) { - return Ok(Policies::all()); + if self.visibility > 0 || ctx.role >= Role::Auditor { + return Ok(ctx); } if let Some(token) = sign1_token { @@ -102,8 +128,10 @@ impl Bucket { now_sec as i64, ) .map_err(|err| (401, err))?; - if &token.subject == caller && &token.audience == canister { - return Policies::try_from(token.policies.as_str()).map_err(|err| (403u16, err)); + if token.subject == ctx.caller && &token.audience == canister { + ctx.ps = + Policies::try_from(token.policies.as_str()).map_err(|err| (403u16, err))?; + return Ok(ctx); } } @@ -112,17 +140,29 @@ impl Bucket { pub fn write_permission( &self, - caller: &Principal, + caller: Principal, canister: &Principal, sign1_token: Option, now_sec: u64, - ) -> Result { + ) -> Result { if self.status != 0 { Err((403, "bucket is not writable".to_string()))?; } - if self.managers.contains(caller) { - return Ok(Policies::all()); + let mut ctx = Context { + caller, + ps: Policies::all(), + role: if self.managers.contains(&caller) { + Role::Manager + } else if self.auditors.contains(&caller) { + Role::Auditor + } else { + Role::User + }, + }; + + if ctx.role >= Role::Manager { + return Ok(ctx); } if let Some(token) = sign1_token { @@ -134,8 +174,10 @@ impl Bucket { now_sec as i64, ) .map_err(|err| (401, err))?; - if &token.subject == caller && &token.audience == canister { - return Policies::try_from(token.policies.as_str()).map_err(|err| (403u16, err)); + if token.subject == ctx.caller && &token.audience == canister { + ctx.ps = + Policies::try_from(token.policies.as_str()).map_err(|err| (403u16, err))?; + return Ok(ctx); } } @@ -389,10 +431,14 @@ impl FoldersTree { res } - fn list_folders(&self, parent: u32, prev: u32, take: u32) -> Vec { + fn list_folders(&self, ctx: &Context, parent: u32, prev: u32, take: u32) -> Vec { match self.0.get(&parent) { None => Vec::new(), Some(parent) => { + if parent.status < 0 && ctx.role < Role::Auditor { + return Vec::new(); + } + let mut res = Vec::with_capacity(parent.folders.len()); for &folder_id in parent.folders.range(ops::RangeTo { end: prev }).rev() { match self.get(&folder_id) { @@ -412,6 +458,7 @@ impl FoldersTree { fn list_files( &self, + ctx: &Context, fs_metadata: &StableBTreeMap, parent: u32, prev: u32, @@ -420,6 +467,10 @@ impl FoldersTree { match self.get(&parent) { None => Vec::new(), Some(parent) => { + if parent.status < 0 && ctx.role < Role::Auditor { + return Vec::new(); + } + let mut res = Vec::with_capacity(take as usize); for &file_id in parent.files.range(ops::RangeTo { end: prev }).rev() { match fs_metadata.get(&file_id) { @@ -836,13 +887,16 @@ pub mod fs { } } - pub fn list_folders(parent: u32, prev: u32, take: u32) -> Vec { - FOLDERS.with(|r| r.borrow().list_folders(parent, prev, take)) + pub fn list_folders(ctx: &Context, parent: u32, prev: u32, take: u32) -> Vec { + FOLDERS.with(|r| r.borrow().list_folders(ctx, parent, prev, take)) } - pub fn list_files(parent: u32, prev: u32, take: u32) -> Vec { + pub fn list_files(ctx: &Context, parent: u32, prev: u32, take: u32) -> Vec { FOLDERS.with(|r1| { - FS_METADATA_STORE.with(|r2| r1.borrow().list_files(&r2.borrow(), parent, prev, take)) + FS_METADATA_STORE.with(|r2| { + r1.borrow() + .list_files(ctx, &r2.borrow(), parent, prev, take) + }) }) } @@ -1296,6 +1350,12 @@ mod test { println!("FileId min_size: {:?}, {}", v.len(), hex::encode(&v)); } + #[test] + fn test_role() { + assert!(Role::Manager > Role::Auditor); + assert!(Role::Auditor > Role::User); + } + #[test] fn test_fs() { state::with_mut(|b| { @@ -1372,9 +1432,14 @@ mod test { assert_eq!(f2_meta.chunks, 3); // folders + let ctx = Context { + caller: Principal::anonymous(), + ps: Policies::default(), + role: Role::Manager, + }; assert_eq!( - fs::list_folders(0, 999, 999) + fs::list_folders(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), @@ -1382,7 +1447,7 @@ mod test { ); assert_eq!( - fs::list_files(0, 999, 999) + fs::list_files(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), @@ -1410,7 +1475,7 @@ mod test { ); assert_eq!( - fs::list_folders(0, 999, 999) + fs::list_folders(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), @@ -1426,14 +1491,14 @@ mod test { }] ); assert_eq!( - fs::list_files(0, 999, 999) + fs::list_files(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), vec![f2] ); assert_eq!( - fs::list_files(1, 999, 999) + fs::list_files(&ctx, 1, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), @@ -1449,14 +1514,14 @@ mod test { }] ); assert_eq!( - fs::list_files(0, 999, 999) + fs::list_files(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), Vec::::new() ); assert_eq!( - fs::list_files(2, 999, 999) + fs::list_files(&ctx, 2, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), @@ -1616,22 +1681,28 @@ mod test { ) .unwrap(); + let ctx = Context { + caller: Principal::anonymous(), + ps: Policies::default(), + role: Role::Manager, + }; + assert_eq!( - tree.list_folders(0, 999, 999) + tree.list_folders(&ctx, 0, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), vec![1] ); assert_eq!( - tree.list_folders(1, 999, 999) + tree.list_folders(&ctx, 1, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), vec![3, 2] ); assert_eq!( - tree.list_folders(99, 999, 999) + tree.list_folders(&ctx, 99, 999, 999) .into_iter() .map(|v| v.id) .collect::>(), diff --git a/src/ic_oss_types/src/permission.rs b/src/ic_oss_types/src/permission.rs index 28d16a6..1f2e6c1 100644 --- a/src/ic_oss_types/src/permission.rs +++ b/src/ic_oss_types/src/permission.rs @@ -364,10 +364,34 @@ impl TryFrom<&str> for Policy { pub struct Policies(pub BTreeSet); impl Policies { + /// Create policies with all permissions for all resources pub fn all() -> Self { Self(BTreeSet::from([Policy::default()])) } + /// Create policies with Read and List permissions for all resources + pub fn read() -> Self { + Self(BTreeSet::from([ + Policy { + permission: Permission { + resource: Resource::All, + operation: Operation::Read, + constraint: None, + }, + resources: Resources::default(), + }, + Policy { + permission: Permission { + resource: Resource::All, + operation: Operation::List, + constraint: None, + }, + resources: Resources::default(), + }, + ])) + } + + // TODO: compress policies pub fn append(&mut self, policies: &mut Policies) { self.0.append(&mut policies.0); }