Skip to content

Commit

Permalink
Merge pull request #885 from AppFlowy-IO/additional-ac-config
Browse files Browse the repository at this point in the history
feat: additional access control config
  • Loading branch information
khorshuheng authored Oct 16, 2024
2 parents d89cbe1 + 383629a commit ec124bc
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 145 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions libs/app-error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ pub enum AppError {
#[error("Not Logged In:{0}")]
NotLoggedIn(String),

#[error("{user}: do not have permissions to {action}")]
NotEnoughPermissions { user: String, action: String },
#[error("User does not have permissions to execute this action")]
NotEnoughPermissions,

#[error("s3 response error:{0}")]
S3ResponseError(String),
Expand Down
1 change: 1 addition & 0 deletions libs/database-entity/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ pub struct AccessRequestWithViewId {

#[derive(Serialize, Deserialize, Debug)]
pub struct AccessRequesterInfo {
pub uid: i64,
pub uuid: Uuid,
pub email: String,
pub name: String,
Expand Down
1 change: 1 addition & 0 deletions libs/database/src/access_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions libs/database/src/pg_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 1 addition & 4 deletions libs/database/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,10 +517,7 @@ pub async fn delete_workspace_members(
.unwrap_or(false);

if is_owner {
return Err(AppError::NotEnoughPermissions {
user: member_email.to_string(),
action: format!("delete member from workspace {}", workspace_id),
});
return Err(AppError::NotEnoughPermissions);
}

sqlx::query!(
Expand Down
20 changes: 4 additions & 16 deletions services/appflowy-collaborate/src/collab/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ where
.await?;

if !can_write_workspace {
return Err(AppError::NotEnoughPermissions {
user: uid.to_string(),
action: format!("write workspace:{}", workspace_id),
});
return Err(AppError::NotEnoughPermissions);
}
Ok(())
}
Expand All @@ -113,10 +110,7 @@ where
.await?;

if !can_write {
return Err(AppError::NotEnoughPermissions {
user: uid.to_string(),
action: format!("update collab:{}", object_id),
});
return Err(AppError::NotEnoughPermissions);
}
Ok(())
}
Expand Down Expand Up @@ -375,10 +369,7 @@ where
.await?;

if !can_read {
return Err(AppError::NotEnoughPermissions {
user: uid.to_string(),
action: format!("read collab:{}", params.object_id),
});
return Err(AppError::NotEnoughPermissions);
}
},
GetCollabOrigin::Server => {},
Expand Down Expand Up @@ -470,10 +461,7 @@ where
.enforce_delete(workspace_id, uid, object_id)
.await?
{
return Err(AppError::NotEnoughPermissions {
user: uid.to_string(),
action: format!("delete collab:{}", object_id),
});
return Err(AppError::NotEnoughPermissions);
}
self.cache.delete_collab(object_id).await?;
Ok(())
Expand Down
3 changes: 1 addition & 2 deletions src/api/access_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ async fn get_access_request_handler(
&state.pg_pool,
state.collab_access_control_storage.clone(),
access_request_id,
*uuid,
uid,
)
.await?;
Expand Down Expand Up @@ -100,11 +99,11 @@ 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,
uid,
*uuid,
is_approved,
)
.await?;
Expand Down
104 changes: 79 additions & 25 deletions src/api/workspace.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -266,11 +267,18 @@ 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.into());
}
workspace::ops::delete_workspace_for_user(
state.pg_pool.clone(),
*workspace_id,
Expand Down Expand Up @@ -303,6 +311,15 @@ 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.into());
}

let invited_members = payload.into_inner();
workspace::ops::invite_workspace_members(
&state.mailer,
Expand Down Expand Up @@ -371,13 +388,14 @@ 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.into());
}
let settings = workspace::ops::get_workspace_settings(&state.pg_pool, &workspace_id).await?;
Ok(AppResponse::Ok().with_data(settings).into())
}

Expand All @@ -391,23 +409,32 @@ 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.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.into());
}
let members = workspace::ops::get_workspace_members(&state.pg_pool, &workspace_id)
.await?
.into_iter()
Expand All @@ -424,11 +451,20 @@ 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.into());
}

let member_emails = payload
.into_inner()
.0
Expand All @@ -448,12 +484,22 @@ 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.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,
Expand Down Expand Up @@ -494,21 +540,29 @@ 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.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,
Expand Down
39 changes: 17 additions & 22 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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: {}",
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit ec124bc

Please sign in to comment.