diff --git a/libs/access-control/src/casbin/collab.rs b/libs/access-control/src/casbin/collab.rs index f64289a46..a50ffd811 100644 --- a/libs/access-control/src/casbin/collab.rs +++ b/libs/access-control/src/casbin/collab.rs @@ -6,7 +6,7 @@ use tracing::instrument; use crate::{ act::{Action, ActionVariant}, collab::{CollabAccessControl, RealtimeAccessControl}, - entity::{ObjectType, SubjectType}, + entity::ObjectType, }; use super::access::AccessControl; @@ -28,16 +28,25 @@ impl CollabAccessControl for CollabAccessControlImpl { &self, workspace_id: &str, uid: &i64, - oid: &str, + _oid: &str, action: Action, ) -> Result<(), AppError> { + // TODO: allow non workspace member to read a collab. + + // Anyone who can write to a workspace, can also delete a collab. + let workspace_action = match action { + Action::Read => Action::Read, + Action::Write => Action::Write, + Action::Delete => Action::Write, + }; + self .access_control .enforce( workspace_id, uid, - ObjectType::Collab(oid), - ActionVariant::FromAction(&action), + ObjectType::Workspace(workspace_id), + ActionVariant::FromAction(&workspace_action), ) .await } @@ -46,16 +55,26 @@ impl CollabAccessControl for CollabAccessControlImpl { &self, workspace_id: &str, uid: &i64, - oid: &str, + _oid: &str, access_level: AFAccessLevel, ) -> Result<(), AppError> { + // TODO: allow non workspace member to read a collab. + + // Anyone who can write to a workspace, also have full access to a collab. + let workspace_action = match access_level { + AFAccessLevel::ReadOnly => Action::Read, + AFAccessLevel::ReadAndComment => Action::Read, + AFAccessLevel::ReadAndWrite => Action::Write, + AFAccessLevel::FullAccess => Action::Write, + }; + self .access_control .enforce( workspace_id, uid, - ObjectType::Collab(oid), - ActionVariant::FromAccessLevel(&access_level), + ObjectType::Workspace(workspace_id), + ActionVariant::FromAction(&workspace_action), ) .await } @@ -63,28 +82,17 @@ impl CollabAccessControl for CollabAccessControlImpl { #[instrument(level = "info", skip_all)] async fn update_access_level_policy( &self, - uid: &i64, - oid: &str, - level: AFAccessLevel, + _uid: &i64, + _oid: &str, + _level: AFAccessLevel, ) -> Result<(), AppError> { - self - .access_control - .update_policy( - SubjectType::User(*uid), - ObjectType::Collab(oid), - ActionVariant::FromAccessLevel(&level), - ) - .await?; - + // TODO: allow non workspace member to read a collab. Ok(()) } #[instrument(level = "info", skip_all)] - async fn remove_access_level(&self, uid: &i64, oid: &str) -> Result<(), AppError> { - self - .access_control - .remove_policy(&SubjectType::User(*uid), &ObjectType::Collab(oid)) - .await?; + async fn remove_access_level(&self, _uid: &i64, _oid: &str) -> Result<(), AppError> { + // TODO: allow non workspace member to read a collab. Ok(()) } } @@ -103,20 +111,35 @@ impl RealtimeCollabAccessControlImpl { &self, workspace_id: &str, uid: &i64, - oid: &str, + _oid: &str, required_action: Action, ) -> Result { - self + // TODO: allow non workspace member to read a collab. + + // Anyone who can write to a workspace, can also delete a collab. + let workspace_action = match required_action { + Action::Read => Action::Read, + Action::Write => Action::Write, + Action::Delete => Action::Write, + }; + + let enforcement_result = self .access_control .enforce( workspace_id, uid, - ObjectType::Collab(oid), - ActionVariant::FromAction(&required_action), + ObjectType::Workspace(workspace_id), + ActionVariant::FromAction(&workspace_action), ) - .await?; - - Ok(true) + .await; + match enforcement_result { + Ok(_) => Ok(true), + Err(AppError::NotEnoughPermissions { + user: _user, + workspace_id: _workspace_id, + }) => Ok(false), + Err(e) => Err(e), + } } } diff --git a/libs/client-api-test/src/test_client.rs b/libs/client-api-test/src/test_client.rs index 1970d2a65..dcc0b7377 100644 --- a/libs/client-api-test/src/test_client.rs +++ b/libs/client-api-test/src/test_client.rs @@ -37,10 +37,9 @@ use client_api::entity::{ }; use client_api::ws::{WSClient, WSClientConfig}; use database_entity::dto::{ - AFAccessLevel, AFRole, AFSnapshotMeta, AFSnapshotMetas, AFUserProfile, AFUserWorkspaceInfo, - AFWorkspace, AFWorkspaceInvitationStatus, AFWorkspaceMember, BatchQueryCollabResult, - CollabParams, CreateCollabParams, InsertCollabMemberParams, QueryCollab, QueryCollabParams, - QuerySnapshotParams, SnapshotData, UpdateCollabMemberParams, + AFRole, AFSnapshotMeta, AFSnapshotMetas, AFUserProfile, AFUserWorkspaceInfo, AFWorkspace, + AFWorkspaceInvitationStatus, AFWorkspaceMember, BatchQueryCollabResult, CollabParams, + CreateCollabParams, QueryCollab, QueryCollabParams, QuerySnapshotParams, SnapshotData, }; use shared_entity::dto::workspace_dto::{ BlobMetadata, CollabResponse, PublishedDuplicate, WorkspaceMemberChangeset, @@ -441,46 +440,6 @@ impl TestClient { self.api_client.get_workspace_member(params).await } - pub async fn add_collab_member( - &self, - workspace_id: &str, - object_id: &str, - other_client: &TestClient, - access_level: AFAccessLevel, - ) { - let uid = other_client.uid().await; - self - .api_client - .add_collab_member(InsertCollabMemberParams { - uid, - workspace_id: workspace_id.to_string(), - object_id: object_id.to_string(), - access_level, - }) - .await - .unwrap(); - } - - pub async fn update_collab_member_access_level( - &self, - workspace_id: &str, - object_id: &str, - other_client: &TestClient, - access_level: AFAccessLevel, - ) { - let uid = other_client.uid().await; - self - .api_client - .update_collab_member(UpdateCollabMemberParams { - uid, - workspace_id: workspace_id.to_string(), - object_id: object_id.to_string(), - access_level, - }) - .await - .unwrap(); - } - pub async fn wait_object_sync_complete(&self, object_id: &str) -> Result<(), Error> { self .wait_object_sync_complete_with_secs(object_id, 60) diff --git a/tests/collab/awareness_test.rs b/tests/collab/awareness_test.rs index 169f1cdd4..d6f45f318 100644 --- a/tests/collab/awareness_test.rs +++ b/tests/collab/awareness_test.rs @@ -4,7 +4,7 @@ use collab_entity::CollabType; use tokio::time::sleep; use client_api_test::TestClient; -use database_entity::dto::{AFAccessLevel, AFRole}; +use database_entity::dto::AFRole; #[tokio::test] async fn viewing_document_editing_users_test() { @@ -27,14 +27,6 @@ async fn viewing_document_editing_users_test() { assert_eq!(clients.len(), 1); assert_eq!(clients[0], owner_uid); - owner - .add_collab_member( - &workspace_id, - &object_id, - &guest, - AFAccessLevel::ReadAndWrite, - ) - .await; guest .open_collab(&workspace_id, &object_id, collab_type) .await; diff --git a/tests/collab/missing_update_test.rs b/tests/collab/missing_update_test.rs index 545c8ec95..166b24808 100644 --- a/tests/collab/missing_update_test.rs +++ b/tests/collab/missing_update_test.rs @@ -1,11 +1,11 @@ use std::time::Duration; +use client_api::entity::AFRole; use collab_entity::CollabType; use serde_json::{json, Value}; use tokio::time::sleep; use client_api_test::{assert_client_collab_include_value, TestClient}; -use database_entity::dto::AFAccessLevel; #[tokio::test] async fn client_apply_update_find_missing_update_test() { @@ -56,13 +56,9 @@ async fn make_clients() -> (TestClient, TestClient, String, Value) { .create_and_edit_collab(&workspace_id, collab_type.clone()) .await; client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::ReadAndWrite, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); // after client 2 finish init sync and then disable receive message client_2 diff --git a/tests/collab/multi_devices_edit.rs b/tests/collab/multi_devices_edit.rs index 92aee778a..907356ab1 100644 --- a/tests/collab/multi_devices_edit.rs +++ b/tests/collab/multi_devices_edit.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use client_api::entity::AFRole; use collab_entity::CollabType; use serde_json::json; use sqlx::types::uuid; @@ -7,7 +8,7 @@ use tokio::time::sleep; use tracing::trace; use client_api_test::*; -use database_entity::dto::{AFAccessLevel, QueryCollabParams}; +use database_entity::dto::QueryCollabParams; #[tokio::test] async fn sync_collab_content_after_reconnect_test() { @@ -200,15 +201,11 @@ async fn edit_document_with_both_clients_offline_then_online_sync_test() { .create_and_edit_collab(&workspace_id, collab_type.clone()) .await; - // add client 2 as a member of the collab + // add client 2 as a member of the workspace client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::ReadAndWrite, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); client_1.disconnect().await; client_2 diff --git a/tests/collab/permission_test.rs b/tests/collab/permission_test.rs index 5e0cea744..d2c9b3822 100644 --- a/tests/collab/permission_test.rs +++ b/tests/collab/permission_test.rs @@ -12,7 +12,7 @@ use client_api_test::{ assert_client_collab_include_value, assert_client_collab_within_secs, assert_server_collab, TestClient, }; -use database_entity::dto::{AFAccessLevel, AFRole}; +use database_entity::dto::AFRole; use crate::collab::util::generate_random_string; @@ -164,13 +164,9 @@ async fn edit_collab_with_readonly_permission_test() { // Add client 2 as the member of the collab then the client 2 will receive the update. client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::ReadOnly, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Guest) + .await + .unwrap(); client_2 .open_collab(&workspace_id, &object_id, collab_type.clone()) @@ -214,13 +210,9 @@ async fn edit_collab_with_read_and_write_permission_test() { // Add client 2 as the member of the collab then the client 2 will receive the update. client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::ReadAndWrite, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); client_2 .open_collab(&workspace_id, &object_id, collab_type.clone()) @@ -265,13 +257,9 @@ async fn edit_collab_with_full_access_permission_test() { // Add client 2 as the member of the collab then the client 2 will receive the update. client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::FullAccess, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); client_2 .open_collab(&workspace_id, &object_id, collab_type.clone()) @@ -314,13 +302,9 @@ async fn edit_collab_with_full_access_then_readonly_permission() { // Add client 2 as the member of the collab then the client 2 will receive the update. client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::FullAccess, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); // client 2 edit the collab and then the server will broadcast the update { @@ -340,13 +324,9 @@ async fn edit_collab_with_full_access_then_readonly_permission() { // updates generated by client 2 { client_1 - .update_collab_member_access_level( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::ReadOnly, - ) - .await; + .try_update_workspace_member(&workspace_id, &client_2, AFRole::Guest) + .await + .unwrap(); client_2 .insert_into(&object_id, "subtitle", "Writing Rust, fun") .await; @@ -404,14 +384,6 @@ async fn multiple_user_with_read_and_write_permission_edit_same_collab_test() { .invite_and_accepted_workspace_member(&workspace_id, &new_member, AFRole::Member) .await .unwrap(); - owner - .add_collab_member( - &workspace_id, - &object_id, - &new_member, - AFAccessLevel::ReadAndWrite, - ) - .await; new_member .open_collab(&workspace_id, &object_id, collab_type.clone()) @@ -490,13 +462,9 @@ async fn multiple_user_with_read_only_permission_edit_same_collab_test() { // sleep 2 secs to make sure it do not trigger register user too fast in gotrue sleep(Duration::from_secs(i % 2)).await; owner - .add_collab_member( - &workspace_id, - &object_id, - &new_user, - AFAccessLevel::ReadOnly, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &new_user, AFRole::Guest) + .await + .unwrap(); new_user .open_collab(&workspace_id, &object_id, collab_type.clone()) diff --git a/tests/collab/single_device_edit.rs b/tests/collab/single_device_edit.rs index 08a21c4fd..bb5a85adb 100644 --- a/tests/collab/single_device_edit.rs +++ b/tests/collab/single_device_edit.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use std::time::Duration; use assert_json_diff::assert_json_eq; +use client_api::entity::AFRole; use collab::core::origin::CollabOrigin; use collab_entity::CollabType; use serde_json::json; @@ -11,7 +12,6 @@ use uuid::Uuid; use client_api_test::*; use collab_rt_entity::{CollabMessage, RealtimeMessage, UpdateSync, MAXIMUM_REALTIME_MESSAGE_SIZE}; -use database_entity::dto::AFAccessLevel; use crate::collab::util::{ generate_random_bytes, generate_random_string, make_big_collab_doc_state, @@ -325,13 +325,9 @@ async fn two_direction_peer_sync_test() { // Before the client_2 want to edit the collab object, it needs to become a member of the collab // Otherwise, the server will reject the edit request client_1 - .add_collab_member( - &workspace_id, - &object_id, - &client_2, - AFAccessLevel::FullAccess, - ) - .await; + .invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member) + .await + .unwrap(); client_2 .open_collab(&workspace_id, &object_id, collab_type.clone())