mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: additional access control config
This commit is contained in:
parent
1fd57b055d
commit
34a7fd3633
11 changed files with 196 additions and 93 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH request_id_workspace_member_count AS (\n SELECT\n request_id,\n COUNT(*) AS member_count\n FROM af_access_request\n JOIN af_workspace_member USING (workspace_id)\n WHERE request_id = $1\n GROUP BY request_id\n )\n SELECT\n request_id,\n view_id,\n (\n workspace_id,\n af_workspace.database_storage_id,\n af_workspace.owner_uid,\n owner_profile.name,\n owner_profile.email,\n af_workspace.created_at,\n af_workspace.workspace_type,\n af_workspace.deleted_at,\n af_workspace.workspace_name,\n af_workspace.icon,\n request_id_workspace_member_count.member_count\n ) AS \"workspace!: AFWorkspaceWithMemberCountRow\",\n (\n af_user.uuid,\n af_user.name,\n af_user.email,\n af_user.metadata ->> 'icon_url'\n ) AS \"requester!: AFAccessRequesterColumn\",\n status AS \"status: AFAccessRequestStatusColumn\",\n af_access_request.created_at AS created_at\n FROM af_access_request\n JOIN af_user USING (uid)\n JOIN af_workspace USING (workspace_id)\n JOIN af_user AS owner_profile ON af_workspace.owner_uid = owner_profile.uid\n JOIN request_id_workspace_member_count USING (request_id)\n WHERE request_id = $1\n ",
|
||||
"query": "\n WITH request_id_workspace_member_count AS (\n SELECT\n request_id,\n COUNT(*) AS member_count\n FROM af_access_request\n JOIN af_workspace_member USING (workspace_id)\n WHERE request_id = $1\n GROUP BY request_id\n )\n SELECT\n request_id,\n view_id,\n (\n workspace_id,\n af_workspace.database_storage_id,\n af_workspace.owner_uid,\n owner_profile.name,\n owner_profile.email,\n af_workspace.created_at,\n af_workspace.workspace_type,\n af_workspace.deleted_at,\n af_workspace.workspace_name,\n af_workspace.icon,\n request_id_workspace_member_count.member_count\n ) AS \"workspace!: AFWorkspaceWithMemberCountRow\",\n (\n af_user.uid,\n af_user.uuid,\n af_user.name,\n af_user.email,\n af_user.metadata ->> 'icon_url'\n ) AS \"requester!: AFAccessRequesterColumn\",\n status AS \"status: AFAccessRequestStatusColumn\",\n af_access_request.created_at AS created_at\n FROM af_access_request\n JOIN af_user USING (uid)\n JOIN af_workspace USING (workspace_id)\n JOIN af_user AS owner_profile ON af_workspace.owner_uid = owner_profile.uid\n JOIN request_id_workspace_member_count USING (request_id)\n WHERE request_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -48,5 +48,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "0c3ae560880e82218d13c5992540386ea1566e45e31acd5fb51886aabcd98479"
|
||||
"hash": "67b381fdcd20f8cfe782d939e56bf94f105cdb23a59fefb846afe8105d91d129"
|
||||
}
|
|
@ -1397,6 +1397,7 @@ pub struct AccessRequestWithViewId {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AccessRequesterInfo {
|
||||
pub uid: i64,
|
||||
pub uuid: Uuid,
|
||||
pub email: String,
|
||||
pub name: String,
|
||||
|
|
|
@ -80,6 +80,7 @@ pub async fn select_access_request_by_request_id<'a, E: Executor<'a, Database =
|
|||
request_id_workspace_member_count.member_count
|
||||
) AS "workspace!: AFWorkspaceWithMemberCountRow",
|
||||
(
|
||||
af_user.uid,
|
||||
af_user.uuid,
|
||||
af_user.name,
|
||||
af_user.email,
|
||||
|
|
|
@ -581,6 +581,7 @@ impl From<AFAccessRequestStatusColumn> for AccessRequestStatus {
|
|||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
pub struct AFAccessRequesterColumn {
|
||||
pub uid: i64,
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
|
@ -590,6 +591,7 @@ pub struct AFAccessRequesterColumn {
|
|||
impl From<AFAccessRequesterColumn> for AccessRequesterInfo {
|
||||
fn from(value: AFAccessRequesterColumn) -> Self {
|
||||
Self {
|
||||
uid: value.uid,
|
||||
uuid: value.uuid,
|
||||
name: value.name,
|
||||
email: value.email,
|
||||
|
|
|
@ -100,6 +100,7 @@ async fn post_approve_access_request_handler(
|
|||
)))?;
|
||||
approve_or_reject_access_request(
|
||||
&state.pg_pool,
|
||||
state.workspace_access_control.clone(),
|
||||
state.mailer.clone(),
|
||||
&appflowy_web_url,
|
||||
access_request_id,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use access_control::act::Action;
|
||||
use actix_web::web::{Bytes, Payload};
|
||||
use actix_web::web::{Data, Json, PayloadConfig};
|
||||
use actix_web::{web, Scope};
|
||||
|
@ -262,11 +263,24 @@ async fn patch_workspace_handler(
|
|||
}
|
||||
|
||||
async fn delete_workspace_handler(
|
||||
_user_id: UserUuid,
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<()>>> {
|
||||
// TODO: add permission for workspace deletion
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_role(&uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "delete workspace".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
workspace::ops::delete_workspace_for_user(
|
||||
state.pg_pool.clone(),
|
||||
*workspace_id,
|
||||
|
@ -299,6 +313,21 @@ async fn post_workspace_invite_handler(
|
|||
payload: Json<Vec<WorkspaceMemberInvitation>>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_role(&uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "invite workspace member".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let invited_members = payload.into_inner();
|
||||
workspace::ops::invite_workspace_members(
|
||||
&state.mailer,
|
||||
|
@ -367,13 +396,20 @@ async fn get_workspace_settings_handler(
|
|||
workspace_id: web::Path<Uuid>,
|
||||
) -> Result<JsonAppResponse<AFWorkspaceSettings>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let settings = workspace::ops::get_workspace_settings(
|
||||
&state.pg_pool,
|
||||
state.workspace_access_control.clone(),
|
||||
&workspace_id,
|
||||
&uid,
|
||||
)
|
||||
.await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_action(&uid, &workspace_id.to_string(), Action::Read)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "read workspace setting".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let settings = workspace::ops::get_workspace_settings(&state.pg_pool, &workspace_id).await?;
|
||||
Ok(AppResponse::Ok().with_data(settings).into())
|
||||
}
|
||||
|
||||
|
@ -387,23 +423,44 @@ async fn post_workspace_settings_handler(
|
|||
let data = data.into_inner();
|
||||
trace!("workspace settings: {:?}", data);
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let settings = workspace::ops::update_workspace_settings(
|
||||
&state.pg_pool,
|
||||
state.workspace_access_control.clone(),
|
||||
&workspace_id,
|
||||
&uid,
|
||||
data,
|
||||
)
|
||||
.await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_action(&uid, &workspace_id.to_string(), Action::Write)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "update workspace setting".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let settings =
|
||||
workspace::ops::update_workspace_settings(&state.pg_pool, &workspace_id, data).await?;
|
||||
Ok(AppResponse::Ok().with_data(settings).into())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, err)]
|
||||
async fn get_workspace_members_handler(
|
||||
_user_uuid: UserUuid,
|
||||
user_uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
) -> Result<JsonAppResponse<Vec<AFWorkspaceMember>>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_action(&uid, &workspace_id.to_string(), Action::Read)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "get workspace members".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let members = workspace::ops::get_workspace_members(&state.pg_pool, &workspace_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
@ -420,11 +477,26 @@ async fn get_workspace_members_handler(
|
|||
|
||||
#[instrument(skip_all, err)]
|
||||
async fn remove_workspace_member_handler(
|
||||
_user_uuid: UserUuid,
|
||||
user_uuid: UserUuid,
|
||||
payload: Json<WorkspaceMembers>,
|
||||
state: Data<AppState>,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_role(&uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "remove workspace member".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let member_emails = payload
|
||||
.into_inner()
|
||||
.0
|
||||
|
@ -444,12 +516,28 @@ async fn remove_workspace_member_handler(
|
|||
|
||||
#[instrument(skip_all, err)]
|
||||
async fn get_workspace_member_handler(
|
||||
user_uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
path: web::Path<(Uuid, i64)>,
|
||||
) -> Result<JsonAppResponse<AFWorkspaceMember>> {
|
||||
let (workspace_id, user_id) = path.into_inner();
|
||||
let (workspace_id, user_uuid_to_retrieved) = path.into_inner();
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_action(&uid, &workspace_id.to_string(), Action::Read)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "get workspace member".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let member_row =
|
||||
workspace::ops::get_workspace_member(&user_id, &state.pg_pool, &workspace_id).await?;
|
||||
workspace::ops::get_workspace_member(&user_uuid_to_retrieved, &state.pg_pool, &workspace_id)
|
||||
.await?;
|
||||
let member = AFWorkspaceMember {
|
||||
name: member_row.name,
|
||||
email: member_row.email,
|
||||
|
@ -490,21 +578,35 @@ async fn leave_workspace_handler(
|
|||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn update_workspace_member_handler(
|
||||
user_uuid: UserUuid,
|
||||
payload: Json<WorkspaceMemberChangeset>,
|
||||
state: Data<AppState>,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
// TODO: only owner is allowed to update member role
|
||||
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let has_access = state
|
||||
.workspace_access_control
|
||||
.enforce_role(&uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(
|
||||
AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "update workspace member".to_string(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let changeset = payload.into_inner();
|
||||
|
||||
if changeset.role.is_some() {
|
||||
let uid = select_uid_from_email(&state.pg_pool, &changeset.email)
|
||||
let changeset_uid = select_uid_from_email(&state.pg_pool, &changeset.email)
|
||||
.await
|
||||
.map_err(AppResponseError::from)?;
|
||||
workspace::ops::update_workspace_member(
|
||||
&uid,
|
||||
&changeset_uid,
|
||||
&state.pg_pool,
|
||||
&workspace_id,
|
||||
&changeset,
|
||||
|
|
|
@ -165,8 +165,10 @@ pub async fn run_actix_server(
|
|||
SessionMiddleware::builder(redis_store.clone(), key.clone())
|
||||
.build(),
|
||||
)
|
||||
// .wrap(DecryptPayloadMiddleware)
|
||||
.wrap(Condition::new(config.access_control.is_enabled, access_control.clone()))
|
||||
.wrap(Condition::new(
|
||||
config.access_control.is_enabled && config.access_control.enable_middleware,
|
||||
access_control.clone())
|
||||
)
|
||||
.wrap(RequestIdMiddleware)
|
||||
.service(server_info_scope())
|
||||
.service(user_scope())
|
||||
|
@ -270,7 +272,6 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
|
|||
info!("Setting up Pg listeners...");
|
||||
let pg_listeners = Arc::new(PgListeners::new(&pg_pool).await?);
|
||||
// let collab_member_listener = pg_listeners.subscribe_collab_member_change();
|
||||
// let workspace_member_listener = pg_listeners.subscribe_workspace_member_change();
|
||||
|
||||
info!(
|
||||
"Setting up access controls, is_enable: {}",
|
||||
|
@ -279,31 +280,25 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
|
|||
let access_control =
|
||||
AccessControl::new(pg_pool.clone(), metrics.access_control_metrics.clone()).await?;
|
||||
|
||||
// spawn_listen_on_workspace_member_change(workspace_member_listener, access_control.clone());
|
||||
// spawn_listen_on_collab_member_change(
|
||||
// pg_pool.clone(),
|
||||
// collab_member_listener,
|
||||
// access_control.clone(),
|
||||
// );
|
||||
|
||||
let user_cache = UserCache::new(pg_pool.clone()).await;
|
||||
let collab_access_control: Arc<dyn CollabAccessControl> = if config.access_control.is_enabled {
|
||||
Arc::new(CollabAccessControlImpl::new(access_control.clone()))
|
||||
} else {
|
||||
Arc::new(NoOpsCollabAccessControlImpl::new())
|
||||
};
|
||||
let collab_access_control: Arc<dyn CollabAccessControl> =
|
||||
if config.access_control.is_enabled && config.access_control.enable_collab_access_control {
|
||||
Arc::new(CollabAccessControlImpl::new(access_control.clone()))
|
||||
} else {
|
||||
Arc::new(NoOpsCollabAccessControlImpl::new())
|
||||
};
|
||||
let workspace_access_control: Arc<dyn WorkspaceAccessControl> =
|
||||
if config.access_control.is_enabled {
|
||||
if config.access_control.is_enabled && config.access_control.enable_workspace_access_control {
|
||||
Arc::new(WorkspaceAccessControlImpl::new(access_control.clone()))
|
||||
} else {
|
||||
Arc::new(NoOpsWorkspaceAccessControlImpl::new())
|
||||
};
|
||||
let realtime_access_control: Arc<dyn RealtimeAccessControl> = if config.access_control.is_enabled
|
||||
{
|
||||
Arc::new(RealtimeCollabAccessControlImpl::new(access_control))
|
||||
} else {
|
||||
Arc::new(NoOpsRealtimeCollabAccessControlImpl::new())
|
||||
};
|
||||
let realtime_access_control: Arc<dyn RealtimeAccessControl> =
|
||||
if config.access_control.is_enabled && config.access_control.enable_realtime_access_control {
|
||||
Arc::new(RealtimeCollabAccessControlImpl::new(access_control))
|
||||
} else {
|
||||
Arc::new(NoOpsRealtimeCollabAccessControlImpl::new())
|
||||
};
|
||||
let collab_cache = CollabCache::new(redis_conn_manager.clone(), pg_pool.clone());
|
||||
|
||||
let collab_storage_access_control = CollabStorageAccessControlImpl {
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
},
|
||||
mailer::{WorkspaceAccessRequestApprovedMailerParam, WorkspaceAccessRequestMailerParam},
|
||||
};
|
||||
use access_control::workspace::WorkspaceAccessControl;
|
||||
use anyhow::Context;
|
||||
use app_error::AppError;
|
||||
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
||||
|
@ -116,8 +117,10 @@ pub async fn get_access_request(
|
|||
Ok(access_request)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn approve_or_reject_access_request(
|
||||
pg_pool: &PgPool,
|
||||
workspace_access_control: Arc<dyn WorkspaceAccessControl>,
|
||||
mailer: AFCloudMailer,
|
||||
appflowy_web_url: &str,
|
||||
request_id: Uuid,
|
||||
|
@ -126,7 +129,14 @@ pub async fn approve_or_reject_access_request(
|
|||
is_approved: bool,
|
||||
) -> Result<(), AppError> {
|
||||
let access_request = select_access_request_by_request_id(pg_pool, request_id).await?;
|
||||
if access_request.workspace.owner_uid != uid {
|
||||
let has_access = workspace_access_control
|
||||
.enforce_role(
|
||||
&uid,
|
||||
&access_request.workspace.workspace_id.to_string(),
|
||||
AFRole::Owner,
|
||||
)
|
||||
.await?;
|
||||
if !has_access {
|
||||
return Err(AppError::NotEnoughPermissions {
|
||||
user: user_uuid.to_string(),
|
||||
action: "approve access request".to_string(),
|
||||
|
@ -140,9 +150,16 @@ pub async fn approve_or_reject_access_request(
|
|||
&mut txn,
|
||||
&access_request.workspace.workspace_id,
|
||||
&access_request.requester.email,
|
||||
role,
|
||||
role.clone(),
|
||||
)
|
||||
.await?;
|
||||
workspace_access_control
|
||||
.insert_role(
|
||||
&access_request.requester.uid,
|
||||
&access_request.workspace.workspace_id,
|
||||
role.clone(),
|
||||
)
|
||||
.await?;
|
||||
let cloned_mailer = mailer.clone();
|
||||
let launch_workspace_url = format!(
|
||||
"{}/app/{}",
|
||||
|
|
|
@ -13,7 +13,7 @@ use tracing::instrument;
|
|||
use uuid::Uuid;
|
||||
|
||||
use access_control::workspace::WorkspaceAccessControl;
|
||||
use app_error::{AppError, ErrorCode};
|
||||
use app_error::AppError;
|
||||
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
||||
use database::collab::upsert_collab_member_with_txn;
|
||||
use database::file::s3_client_impl::S3BucketStorage;
|
||||
|
@ -611,43 +611,17 @@ pub async fn get_workspace_document_total_bytes(
|
|||
|
||||
pub async fn get_workspace_settings(
|
||||
pg_pool: &PgPool,
|
||||
workspace_access_control: Arc<dyn WorkspaceAccessControl>,
|
||||
workspace_id: &Uuid,
|
||||
owner_uid: &i64,
|
||||
) -> Result<AFWorkspaceSettings, AppResponseError> {
|
||||
let has_access = workspace_access_control
|
||||
.enforce_role(owner_uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
|
||||
if !has_access {
|
||||
return Err(AppResponseError::new(
|
||||
ErrorCode::UserUnAuthorized,
|
||||
"Only workspace owner can access workspace settings",
|
||||
));
|
||||
}
|
||||
|
||||
let settings = select_workspace_settings(pg_pool, workspace_id).await?;
|
||||
Ok(settings.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub async fn update_workspace_settings(
|
||||
pg_pool: &PgPool,
|
||||
workspace_access_control: Arc<dyn WorkspaceAccessControl>,
|
||||
workspace_id: &Uuid,
|
||||
owner_uid: &i64,
|
||||
change: AFWorkspaceSettingsChange,
|
||||
) -> Result<AFWorkspaceSettings, AppResponseError> {
|
||||
let has_access = workspace_access_control
|
||||
.enforce_role(owner_uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
|
||||
if !has_access {
|
||||
return Err(AppResponseError::new(
|
||||
ErrorCode::UserUnAuthorized,
|
||||
"Only workspace owner can edit workspace settings",
|
||||
));
|
||||
}
|
||||
|
||||
let mut tx = pg_pool.begin().await?;
|
||||
let mut setting = select_workspace_settings(tx.deref_mut(), workspace_id)
|
||||
.await?
|
||||
|
|
|
@ -33,6 +33,10 @@ pub struct Config {
|
|||
|
||||
pub struct AccessControlSetting {
|
||||
pub is_enabled: bool,
|
||||
pub enable_middleware: bool,
|
||||
pub enable_workspace_access_control: bool,
|
||||
pub enable_collab_access_control: bool,
|
||||
pub enable_realtime_access_control: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Clone, Debug)]
|
||||
|
@ -176,6 +180,18 @@ pub fn get_configuration() -> Result<Config, anyhow::Error> {
|
|||
is_enabled: get_env_var("APPFLOWY_ACCESS_CONTROL", "false")
|
||||
.parse()
|
||||
.context("fail to get APPFLOWY_ACCESS_CONTROL")?,
|
||||
enable_middleware: get_env_var("APPFLOWY_ACCESS_CONTROL_MIDDLEWARE", "true")
|
||||
.parse()
|
||||
.context("fail to get APPFLOWY_ACCESS_CONTROL_MIDDLEWARE")?,
|
||||
enable_workspace_access_control: get_env_var("APPFLOWY_ACCESS_CONTROL_WORKSPACE", "true")
|
||||
.parse()
|
||||
.context("fail to get APPFLOWY_ACCESS_CONTROL_WORKSPACE")?,
|
||||
enable_collab_access_control: get_env_var("APPFLOWY_ACCESS_CONTROL_COLLAB", "true")
|
||||
.parse()
|
||||
.context("fail to get APPFLOWY_ACCESS_CONTROL_COLLAB")?,
|
||||
enable_realtime_access_control: get_env_var("APPFLOWY_ACCESS_CONTROL_REALTIME", "true")
|
||||
.parse()
|
||||
.context("fail to get APPFLOWY_ACCESS_CONTROL_REALTIME")?,
|
||||
},
|
||||
db_settings: DatabaseSetting {
|
||||
pg_conn_opts: PgConnectOptions::from_str(&get_env_var(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use app_error::ErrorCode;
|
||||
use client_api::Client;
|
||||
use client_api_test::generate_unique_registered_user_client;
|
||||
use database_entity::dto::{AFRole, AFWorkspaceInvitationStatus, AFWorkspaceSettingsChange};
|
||||
|
@ -31,6 +30,9 @@ async fn get_and_set_workspace_by_owner() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_and_set_workspace_by_non_owner() {
|
||||
// TODO: currently, workspace settings contains only AI preference, which is
|
||||
// better suited as a user setting. Meanwhile, we can permit workspace members
|
||||
// to view the settings.
|
||||
let (alice_client, _alice) = generate_unique_registered_user_client().await;
|
||||
let workspaces = alice_client.get_workspaces().await.unwrap();
|
||||
let alice_workspace_id = workspaces.first().unwrap().workspace_id;
|
||||
|
@ -39,26 +41,18 @@ async fn get_and_set_workspace_by_non_owner() {
|
|||
|
||||
invite_user_to_workspace(&alice_workspace_id, &alice_client, &bob_client, &bob.email).await;
|
||||
|
||||
let resp = bob_client
|
||||
bob_client
|
||||
.get_workspace_settings(&alice_workspace_id.to_string())
|
||||
.await;
|
||||
assert!(
|
||||
resp.is_err(),
|
||||
"non-owner should not have access to workspace settings"
|
||||
);
|
||||
assert_eq!(resp.err().unwrap().code, ErrorCode::UserUnAuthorized);
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp = bob_client
|
||||
bob_client
|
||||
.update_workspace_settings(
|
||||
&alice_workspace_id.to_string(),
|
||||
&AFWorkspaceSettingsChange::new().disable_search_indexing(true),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
resp.is_err(),
|
||||
"non-owner should not be able to edit workspace settings"
|
||||
);
|
||||
assert_eq!(resp.err().unwrap().code, ErrorCode::UserUnAuthorized);
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn invite_user_to_workspace(
|
||||
|
|
Loading…
Add table
Reference in a new issue