/* * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use jmap::mailbox::{INBOX_ID, TRASH_ID}; use jmap_client::{ core::{ error::{MethodError, MethodErrorType}, set::{SetError, SetErrorType}, }, email::{self, import::EmailImportResponse, query::Filter, Property}, mailbox::{self, Role}, principal::ACL, }; use jmap_proto::types::id::Id; use std::fmt::Debug; use store::ahash::AHashMap; use crate::{ directory::internal::TestInternalDirectory, jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, test_account_login}, }; use super::JMAPTest; pub async fn test(params: &mut JMAPTest) { println!("Running ACL tests..."); let server = params.server.clone(); // Create a group and three test accounts let inbox_id = Id::new(INBOX_ID as u64).to_string(); let trash_id = Id::new(TRASH_ID as u64).to_string(); let john_id: Id = server .core .storage .data .create_test_user( "jdoe@example.com", "12345", "John Doe", &["jdoe@example.com"], ) .await .into(); let jane_id: Id = server .core .storage .data .create_test_user( "jane.smith@example.com", "abcde", "Jane Smith", &["jane.smith@example.com"], ) .await .into(); let bill_id: Id = server .core .storage .data .create_test_user( "bill@example.com", "098765", "Bill Foobar", &["bill@example.com"], ) .await .into(); let sales_id: Id = server .core .storage .data .create_test_group("sales@example.com", "Sales Group", &["sales@example.com"]) .await .into(); // Authenticate all accounts let mut john_client = test_account_login("jdoe@example.com", "12345").await; let mut jane_client = test_account_login("jane.smith@example.com", "abcde").await; let mut bill_client = test_account_login("bill@example.com", "098765").await; // Insert two emails in each account let mut email_ids = AHashMap::default(); for (client, account_id, name) in [ (&mut john_client, &john_id, "john"), (&mut jane_client, &jane_id, "jane"), (&mut bill_client, &bill_id, "bill"), (&mut params.client, &sales_id, "sales"), ] { let user_name = client.session().username().to_string(); let mut ids = Vec::with_capacity(2); for (mailbox_id, mailbox_name) in [(&inbox_id, "inbox"), (&trash_id, "trash")] { ids.push( client .set_default_account_id(account_id.to_string()) .email_import( format!( concat!( "From: acl_test@example.com\r\n", "To: {}\r\n", "Subject: Owned by {} in {}\r\n", "\r\n", "This message is owned by {}.", ), user_name, name, mailbox_name, name ) .into_bytes(), [mailbox_id], None::>, None, ) .await .unwrap() .take_id(), ); } email_ids.insert(name, ids); } // John should have access to his emails only assert_eq!( john_client .email_get( email_ids.get("john").unwrap().first().unwrap(), [Property::Subject].into(), ) .await .unwrap() .unwrap() .subject() .unwrap(), "Owned by john in inbox" ); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_get( email_ids.get("jane").unwrap().first().unwrap(), [Property::Subject].into(), ) .await, ); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_get(&inbox_id, None::>) .await, ); assert_forbidden( john_client .set_default_account_id(sales_id.to_string()) .email_get( email_ids.get("sales").unwrap().first().unwrap(), [Property::Subject].into(), ) .await, ); assert_forbidden( john_client .set_default_account_id(sales_id.to_string()) .mailbox_get(&inbox_id, None::>) .await, ); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_query(None::, None::>) .await, ); // Jane grants Inbox ReadItems access to John jane_client .mailbox_update_acl(&inbox_id, "jdoe@example.com", [ACL::ReadItems]) .await .unwrap(); // John should have ReadItems access to Inbox assert_eq!( john_client .set_default_account_id(jane_id.to_string()) .email_get( email_ids.get("jane").unwrap().first().unwrap(), [Property::Subject].into(), ) .await .unwrap() .unwrap() .subject() .unwrap(), "Owned by jane in inbox" ); assert_eq!( john_client .set_default_account_id(jane_id.to_string()) .email_query(None::, None::>) .await .unwrap() .ids(), [email_ids.get("jane").unwrap().first().unwrap().as_str()] ); // John's session resource should contain Jane's account details john_client.refresh_session().await.unwrap(); assert_eq!( john_client .session() .account(&jane_id.to_string()) .unwrap() .name(), "jane.smith@example.com" ); // John should not have access to emails in Jane's Trash folder assert!(john_client .set_default_account_id(jane_id.to_string()) .email_get( email_ids.get("jane").unwrap().last().unwrap(), [Property::Subject].into(), ) .await .unwrap() .is_none()); // John should only be able to copy blobs he has access to let blob_id = jane_client .email_get( email_ids.get("jane").unwrap().first().unwrap(), [Property::BlobId].into(), ) .await .unwrap() .unwrap() .take_blob_id(); john_client .set_default_account_id(john_id.to_string()) .blob_copy(jane_id.to_string(), &blob_id) .await .unwrap(); let blob_id = jane_client .email_get( email_ids.get("jane").unwrap().last().unwrap(), [Property::BlobId].into(), ) .await .unwrap() .unwrap() .take_blob_id(); assert_forbidden( john_client .set_default_account_id(john_id.to_string()) .blob_copy(jane_id.to_string(), &blob_id) .await, ); // John only has ReadItems access to Inbox but no Read access assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_get(&inbox_id, [mailbox::Property::MyRights].into()) .await, ); jane_client .mailbox_update_acl(&inbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems]) .await .unwrap(); assert_eq!( john_client .set_default_account_id(jane_id.to_string()) .mailbox_get(&inbox_id, [mailbox::Property::MyRights].into()) .await .unwrap() .unwrap() .my_rights() .unwrap() .acl_list(), vec![ACL::ReadItems] ); // Try to add items using import and copy let blob_id = john_client .set_default_account_id(john_id.to_string()) .upload( Some(&john_id.to_string()), concat!( "From: acl_test@example.com\r\n", "To: jane.smith@example.com\r\n", "Subject: Created by john in jane's inbox\r\n", "\r\n", "This message is owned by jane.", ) .as_bytes() .to_vec(), None, ) .await .unwrap() .take_blob_id(); let mut request = john_client .set_default_account_id(jane_id.to_string()) .build(); let email_id = request .import_email() .email(&blob_id) .mailbox_ids([&inbox_id]) .create_id(); assert_forbidden( request .send_single::() .await .unwrap() .created(&email_id), ); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_copy( &john_id.to_string(), email_ids.get("john").unwrap().last().unwrap(), [&inbox_id], None::>, None, ) .await, ); // Grant access and try again jane_client .mailbox_update_acl( &inbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems, ACL::AddItems], ) .await .unwrap(); let mut request = john_client .set_default_account_id(jane_id.to_string()) .build(); let email_id = request .import_email() .email(&blob_id) .mailbox_ids([&inbox_id]) .create_id(); let email_id = request .send_single::() .await .unwrap() .created(&email_id) .unwrap() .take_id(); let email_id_2 = john_client .set_default_account_id(jane_id.to_string()) .email_copy( &john_id.to_string(), email_ids.get("john").unwrap().last().unwrap(), [&inbox_id], None::>, None, ) .await .unwrap() .take_id(); assert_eq!( jane_client .email_get(&email_id, [Property::Subject].into(),) .await .unwrap() .unwrap() .subject() .unwrap(), "Created by john in jane's inbox" ); assert_eq!( jane_client .email_get(&email_id_2, [Property::Subject].into(),) .await .unwrap() .unwrap() .subject() .unwrap(), "Owned by john in trash" ); // Try removing items assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_destroy(&email_id) .await, ); jane_client .mailbox_update_acl( &inbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems, ACL::AddItems, ACL::RemoveItems], ) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .email_destroy(&email_id) .await .unwrap(); // Try to set keywords assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_set_keyword(&email_id_2, "$seen", true) .await, ); jane_client .mailbox_update_acl( &inbox_id, "jdoe@example.com", [ ACL::Read, ACL::ReadItems, ACL::AddItems, ACL::RemoveItems, ACL::ModifyItems, ], ) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .email_set_keyword(&email_id_2, "$seen", true) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .email_set_keyword(&email_id_2, "my-keyword", true) .await .unwrap(); // Try to create a child assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_create("John's mailbox", None::<&str>, Role::None) .await, ); jane_client .mailbox_update_acl( &inbox_id, "jdoe@example.com", [ ACL::Read, ACL::ReadItems, ACL::AddItems, ACL::RemoveItems, ACL::ModifyItems, ACL::CreateChild, ], ) .await .unwrap(); let mailbox_id = john_client .set_default_account_id(jane_id.to_string()) .mailbox_create("John's mailbox", Some(&inbox_id), Role::None) .await .unwrap() .take_id(); // Try renaming a mailbox assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_rename(&mailbox_id, "John's private mailbox") .await, ); jane_client .mailbox_update_acl( &mailbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems, ACL::Modify], ) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .mailbox_rename(&mailbox_id, "John's private mailbox") .await .unwrap(); // Try moving a message assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_set_mailbox(&email_id_2, &mailbox_id, true) .await, ); jane_client .mailbox_update_acl( &mailbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems, ACL::Modify, ACL::AddItems], ) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .email_set_mailbox(&email_id_2, &mailbox_id, true) .await .unwrap(); // Try deleting a mailbox assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_destroy(&mailbox_id, true) .await, ); jane_client .mailbox_update_acl( &mailbox_id, "jdoe@example.com", [ ACL::Read, ACL::ReadItems, ACL::Modify, ACL::AddItems, ACL::Delete, ], ) .await .unwrap(); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_destroy(&mailbox_id, true) .await, ); jane_client .mailbox_update_acl( &mailbox_id, "jdoe@example.com", [ ACL::Read, ACL::ReadItems, ACL::Modify, ACL::AddItems, ACL::Delete, ACL::RemoveItems, ], ) .await .unwrap(); john_client .set_default_account_id(jane_id.to_string()) .mailbox_destroy(&mailbox_id, true) .await .unwrap(); // Try changing ACL assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .mailbox_update_acl(&inbox_id, "bill@example.com", [ACL::Read, ACL::ReadItems]) .await, ); assert_forbidden( bill_client .set_default_account_id(jane_id.to_string()) .email_query(None::, None::>) .await, ); jane_client .mailbox_update_acl( &inbox_id, "jdoe@example.com", [ ACL::Read, ACL::ReadItems, ACL::AddItems, ACL::RemoveItems, ACL::ModifyItems, ACL::CreateChild, ACL::Modify, ACL::Administer, ], ) .await .unwrap(); assert_eq!( john_client .set_default_account_id(jane_id.to_string()) .mailbox_get(&inbox_id, [mailbox::Property::MyRights].into()) .await .unwrap() .unwrap() .my_rights() .unwrap() .acl_list(), vec![ ACL::ReadItems, ACL::AddItems, ACL::RemoveItems, ACL::ModifyItems, ACL::CreateChild, ACL::Modify ] ); john_client .set_default_account_id(jane_id.to_string()) .mailbox_update_acl(&inbox_id, "bill@example.com", [ACL::Read, ACL::ReadItems]) .await .unwrap(); assert_eq!( bill_client .set_default_account_id(jane_id.to_string()) .email_query( None::, vec![email::query::Comparator::subject()].into() ) .await .unwrap() .ids(), [ email_ids.get("jane").unwrap().first().unwrap().as_str(), &email_id_2 ] ); // Revoke all access to John jane_client .mailbox_update_acl(&inbox_id, "jdoe@example.com", []) .await .unwrap(); assert_forbidden( john_client .set_default_account_id(jane_id.to_string()) .email_get( email_ids.get("jane").unwrap().first().unwrap(), [Property::Subject].into(), ) .await, ); john_client.refresh_session().await.unwrap(); assert!(john_client .session() .account(&jane_id.to_string()) .is_none()); assert_eq!( bill_client .set_default_account_id(jane_id.to_string()) .email_get( email_ids.get("jane").unwrap().first().unwrap(), [Property::Subject].into(), ) .await .unwrap() .unwrap() .subject() .unwrap(), "Owned by jane in inbox" ); // Add John and Jane to the Sales group for name in ["jdoe@example.com", "jane.smith@example.com"] { server .core .storage .data .add_to_group(name, "sales@example.com") .await; } server.inner.cache.access_tokens.clear(); john_client.refresh_session().await.unwrap(); jane_client.refresh_session().await.unwrap(); bill_client.refresh_session().await.unwrap(); assert_eq!( john_client .session() .account(&sales_id.to_string()) .unwrap() .name(), "sales@example.com" ); assert!(!john_client .session() .account(&sales_id.to_string()) .unwrap() .is_personal()); assert_eq!( jane_client .session() .account(&sales_id.to_string()) .unwrap() .name(), "sales@example.com" ); assert!(bill_client .session() .account(&sales_id.to_string()) .is_none()); // Insert a message in Sales's inbox let blob_id = john_client .set_default_account_id(sales_id.to_string()) .upload( Some(&sales_id.to_string()), concat!( "From: acl_test@example.com\r\n", "To: sales@example.com\r\n", "Subject: Created by john in sales\r\n", "\r\n", "This message is owned by sales.", ) .as_bytes() .to_vec(), None, ) .await .unwrap() .take_blob_id(); let mut request = john_client.build(); let email_id = request .import_email() .email(&blob_id) .mailbox_ids([&inbox_id]) .create_id(); let email_id = request .send_single::() .await .unwrap() .created(&email_id) .unwrap() .take_id(); // Both Jane and John should be able to see this message, but not Bill assert_eq!( john_client .set_default_account_id(sales_id.to_string()) .email_get(&email_id, [Property::Subject].into(),) .await .unwrap() .unwrap() .subject() .unwrap(), "Created by john in sales" ); assert_eq!( jane_client .set_default_account_id(sales_id.to_string()) .email_get(&email_id, [Property::Subject].into(),) .await .unwrap() .unwrap() .subject() .unwrap(), "Created by john in sales" ); assert_forbidden( bill_client .set_default_account_id(sales_id.to_string()) .email_get(&email_id, [Property::Subject].into()) .await, ); // Remove John from the sales group server .core .storage .data .remove_from_group("jdoe@example.com", "sales@example.com") .await; server.inner.cache.http_auth.clear(); server.inner.cache.access_tokens.clear(); assert_forbidden( john_client .set_default_account_id(sales_id.to_string()) .email_get(&email_id, [Property::Subject].into()) .await, ); // Destroy test account data for id in [john_id, bill_id, jane_id, sales_id] { params.client.set_default_account_id(id.to_string()); destroy_all_mailboxes(params).await; } assert_is_empty(server).await; } pub fn assert_forbidden(result: Result) { if !matches!( result, Err(jmap_client::Error::Method(MethodError { p_type: MethodErrorType::Forbidden })) | Err(jmap_client::Error::Set(SetError { type_: SetErrorType::BlobNotFound | SetErrorType::Forbidden, .. })) ) { panic!("Expected forbidden, got {:?}", result); } }