chore: simplify collab level access control (#1008)
This commit is contained in:
parent
e6dbc95641
commit
afeaeb7796
|
|
@ -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<bool, AppError> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue