chore: cache policy when load (#325)
This commit is contained in:
parent
c9a3cd6b1b
commit
f713ebedaa
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::api::metrics::{metrics_scope, AppFlowyCloudMetrics};
|
use crate::api::metrics::{metrics_scope, AppFlowyCloudMetrics};
|
||||||
use crate::biz::casbin::adapter::PgAdapter;
|
|
||||||
use crate::component::auth::HEADER_TOKEN;
|
use crate::component::auth::HEADER_TOKEN;
|
||||||
use crate::config::config::{Config, DatabaseSetting, GoTrueSetting, S3Setting};
|
use crate::config::config::{Config, DatabaseSetting, GoTrueSetting, S3Setting};
|
||||||
use crate::middleware::request_id::RequestIdMiddleware;
|
use crate::middleware::request_id::RequestIdMiddleware;
|
||||||
|
|
@ -29,7 +29,7 @@ use crate::api::file_storage::file_storage_scope;
|
||||||
use crate::api::user::user_scope;
|
use crate::api::user::user_scope;
|
||||||
use crate::api::workspace::{collab_scope, workspace_scope};
|
use crate::api::workspace::{collab_scope, workspace_scope};
|
||||||
use crate::api::ws::ws_scope;
|
use crate::api::ws::ws_scope;
|
||||||
use crate::biz::casbin::access_control::{AccessControl, MODEL_CONF};
|
use crate::biz::casbin::access_control::AccessControl;
|
||||||
use crate::biz::collab::access_control::CollabHttpAccessControl;
|
use crate::biz::collab::access_control::CollabHttpAccessControl;
|
||||||
use crate::biz::collab::storage::init_collab_storage;
|
use crate::biz::collab::storage::init_collab_storage;
|
||||||
use crate::biz::pg_listener::PgListeners;
|
use crate::biz::pg_listener::PgListeners;
|
||||||
|
|
@ -38,7 +38,6 @@ use crate::biz::workspace::access_control::WorkspaceHttpAccessControl;
|
||||||
use crate::middleware::access_control_mw::WorkspaceAccessControl;
|
use crate::middleware::access_control_mw::WorkspaceAccessControl;
|
||||||
|
|
||||||
use crate::middleware::metrics_mw::MetricsMiddleware;
|
use crate::middleware::metrics_mw::MetricsMiddleware;
|
||||||
use casbin::CoreApi;
|
|
||||||
use database::file::bucket_s3_impl::S3BucketStorage;
|
use database::file::bucket_s3_impl::S3BucketStorage;
|
||||||
use prometheus_client::registry::Registry;
|
use prometheus_client::registry::Registry;
|
||||||
use realtime::collaborate::{CollabServer, RealtimeMetrics};
|
use realtime::collaborate::{CollabServer, RealtimeMetrics};
|
||||||
|
|
@ -188,15 +187,12 @@ pub async fn init_state(config: &Config) -> Result<AppState, Error> {
|
||||||
let workspace_member_listener = pg_listeners.subscribe_workspace_member_change();
|
let workspace_member_listener = pg_listeners.subscribe_workspace_member_change();
|
||||||
|
|
||||||
info!("Setting up access controls...");
|
info!("Setting up access controls...");
|
||||||
let access_control_model = casbin::DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let access_control_adapter = PgAdapter::new(pg_pool.clone());
|
|
||||||
let enforcer = casbin::Enforcer::new(access_control_model, access_control_adapter).await?;
|
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pg_pool.clone(),
|
pg_pool.clone(),
|
||||||
collab_member_listener,
|
collab_member_listener,
|
||||||
workspace_member_listener,
|
workspace_member_listener,
|
||||||
enforcer,
|
)
|
||||||
);
|
.await?;
|
||||||
|
|
||||||
let collab_access_control = access_control.new_collab_access_control();
|
let collab_access_control = access_control.new_collab_access_control();
|
||||||
let workspace_access_control = access_control.new_workspace_access_control();
|
let workspace_access_control = access_control.new_workspace_access_control();
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ use crate::biz::casbin::pg_listen::*;
|
||||||
use crate::biz::casbin::workspace_ac::WorkspaceAccessControlImpl;
|
use crate::biz::casbin::workspace_ac::WorkspaceAccessControlImpl;
|
||||||
|
|
||||||
use app_error::AppError;
|
use app_error::AppError;
|
||||||
use casbin::Enforcer;
|
use casbin::CoreApi;
|
||||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||||
|
|
||||||
|
use crate::biz::casbin::adapter::PgAdapter;
|
||||||
use actix_http::Method;
|
use actix_http::Method;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use dashmap::DashMap;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
@ -30,16 +33,32 @@ pub struct AccessControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccessControl {
|
impl AccessControl {
|
||||||
pub fn new(
|
pub async fn new(
|
||||||
pg_pool: PgPool,
|
pg_pool: PgPool,
|
||||||
collab_listener: broadcast::Receiver<CollabMemberNotification>,
|
collab_listener: broadcast::Receiver<CollabMemberNotification>,
|
||||||
workspace_listener: broadcast::Receiver<WorkspaceMemberNotification>,
|
workspace_listener: broadcast::Receiver<WorkspaceMemberNotification>,
|
||||||
enforcer: Enforcer,
|
) -> Result<Self, AppError> {
|
||||||
) -> Self {
|
let enforcer_result_cache = Arc::new(DashMap::new());
|
||||||
let enforcer = Arc::new(AFEnforcer::new(enforcer));
|
let action_cache = Arc::new(DashMap::new());
|
||||||
|
|
||||||
|
let access_control_model = casbin::DefaultModel::from_str(MODEL_CONF)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AppError::Internal(anyhow!("Failed to create access control model: {}", e)))?;
|
||||||
|
let access_control_adapter = PgAdapter::new(pg_pool.clone(), action_cache.clone());
|
||||||
|
let enforcer = casbin::Enforcer::new(access_control_model, access_control_adapter)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
AppError::Internal(anyhow!("Failed to create access control enforcer: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let enforcer = Arc::new(AFEnforcer::new(
|
||||||
|
enforcer,
|
||||||
|
enforcer_result_cache,
|
||||||
|
action_cache,
|
||||||
|
));
|
||||||
spawn_listen_on_workspace_member_change(workspace_listener, enforcer.clone());
|
spawn_listen_on_workspace_member_change(workspace_listener, enforcer.clone());
|
||||||
spawn_listen_on_collab_member_change(pg_pool, collab_listener, enforcer.clone());
|
spawn_listen_on_collab_member_change(pg_pool, collab_listener, enforcer.clone());
|
||||||
Self { enforcer }
|
Ok(Self { enforcer })
|
||||||
}
|
}
|
||||||
pub fn new_collab_access_control(&self) -> CollabAccessControlImpl {
|
pub fn new_collab_access_control(&self) -> CollabAccessControlImpl {
|
||||||
CollabAccessControlImpl::new(self.clone())
|
CollabAccessControlImpl::new(self.clone())
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::biz::casbin::access_control::{Action, ObjectType, ToCasbinAction};
|
use crate::biz::casbin::access_control::{Action, ObjectType, ToCasbinAction};
|
||||||
|
use crate::biz::casbin::enforcer::ActionCacheKey;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use casbin::error::AdapterError;
|
|
||||||
use casbin::Adapter;
|
use casbin::Adapter;
|
||||||
use casbin::Filter;
|
use casbin::Filter;
|
||||||
use casbin::Model;
|
use casbin::Model;
|
||||||
use casbin::Result;
|
use casbin::Result;
|
||||||
|
use dashmap::DashMap;
|
||||||
use database::collab::select_collab_member_access_level;
|
use database::collab::select_collab_member_access_level;
|
||||||
use database::pg_row::AFCollabMemerAccessLevelRow;
|
use database::pg_row::AFCollabMemerAccessLevelRow;
|
||||||
use database::pg_row::AFWorkspaceMemberPermRow;
|
use database::pg_row::AFWorkspaceMemberPermRow;
|
||||||
|
|
@ -12,52 +14,58 @@ use database::workspace::select_workspace_member_perm_stream;
|
||||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||||
use futures_util::stream::BoxStream;
|
use futures_util::stream::BoxStream;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
/// Implmentation of [`casbin::Adapter`] for access control authorisation.
|
/// Implementation of [`casbin::Adapter`] for access control authorisation.
|
||||||
/// Access control policies that are managed by workspace and collab CRUD.
|
/// Access control policies that are managed by workspace and collab CRUD.
|
||||||
pub struct PgAdapter {
|
pub struct PgAdapter {
|
||||||
pg_pool: PgPool,
|
pg_pool: PgPool,
|
||||||
|
action_cache: Arc<DashMap<ActionCacheKey, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PgAdapter {
|
impl PgAdapter {
|
||||||
pub fn new(pg_pool: PgPool) -> Self {
|
pub fn new(pg_pool: PgPool, action_cache: Arc<DashMap<ActionCacheKey, String>>) -> Self {
|
||||||
Self { pg_pool }
|
Self {
|
||||||
|
pg_pool,
|
||||||
|
action_cache,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_collab_policies(
|
async fn load_collab_policies(
|
||||||
|
action_cache: &Arc<DashMap<ActionCacheKey, String>>,
|
||||||
mut stream: BoxStream<'_, sqlx::Result<AFCollabMemerAccessLevelRow>>,
|
mut stream: BoxStream<'_, sqlx::Result<AFCollabMemerAccessLevelRow>>,
|
||||||
) -> Result<Vec<Vec<String>>> {
|
) -> Result<Vec<Vec<String>>> {
|
||||||
let mut policies: Vec<Vec<String>> = Vec::new();
|
let mut policies: Vec<Vec<String>> = Vec::new();
|
||||||
|
|
||||||
while let Some(result) = stream.next().await {
|
while let Some(Ok(member_access_lv)) = stream.next().await {
|
||||||
let member_access_lv = result.map_err(|err| AdapterError(Box::new(err)))?;
|
let uid = member_access_lv.uid;
|
||||||
let policy = [
|
let object_type = ObjectType::Collab(&member_access_lv.oid);
|
||||||
member_access_lv.uid.to_string(),
|
let action = member_access_lv.access_level.to_action();
|
||||||
ObjectType::Collab(&member_access_lv.oid).to_object_id(),
|
action_cache.insert(ActionCacheKey::new(&uid, &object_type), action.clone());
|
||||||
member_access_lv.access_level.to_action(),
|
|
||||||
]
|
let policy = [uid.to_string(), object_type.to_object_id(), action].to_vec();
|
||||||
.to_vec();
|
|
||||||
policies.push(policy);
|
policies.push(policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(policies)
|
Ok(policies)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_workspace_policies(
|
async fn load_workspace_policies(
|
||||||
|
action_cache: &Arc<DashMap<ActionCacheKey, String>>,
|
||||||
mut stream: BoxStream<'_, sqlx::Result<AFWorkspaceMemberPermRow>>,
|
mut stream: BoxStream<'_, sqlx::Result<AFWorkspaceMemberPermRow>>,
|
||||||
) -> Result<Vec<Vec<String>>> {
|
) -> Result<Vec<Vec<String>>> {
|
||||||
let mut policies: Vec<Vec<String>> = Vec::new();
|
let mut policies: Vec<Vec<String>> = Vec::new();
|
||||||
|
|
||||||
while let Some(result) = stream.next().await {
|
while let Some(Ok(member_permission)) = stream.next().await {
|
||||||
let member_permission = result.map_err(|err| AdapterError(Box::new(err)))?;
|
let uid = member_permission.uid;
|
||||||
let policy = [
|
let workspace_id = member_permission.workspace_id.to_string();
|
||||||
member_permission.uid.to_string(),
|
let object_type = ObjectType::Workspace(&workspace_id);
|
||||||
ObjectType::Workspace(&member_permission.workspace_id.to_string()).to_object_id(),
|
let action = member_permission.role.to_action();
|
||||||
member_permission.role.to_action(),
|
action_cache.insert(ActionCacheKey::new(&uid, &object_type), action.clone());
|
||||||
]
|
|
||||||
.to_vec();
|
let policy = [uid.to_string(), object_type.to_object_id(), action].to_vec();
|
||||||
policies.push(policy);
|
policies.push(policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,13 +76,15 @@ async fn create_workspace_policies(
|
||||||
impl Adapter for PgAdapter {
|
impl Adapter for PgAdapter {
|
||||||
async fn load_policy(&mut self, model: &mut dyn Model) -> Result<()> {
|
async fn load_policy(&mut self, model: &mut dyn Model) -> Result<()> {
|
||||||
let workspace_member_perm_stream = select_workspace_member_perm_stream(&self.pg_pool);
|
let workspace_member_perm_stream = select_workspace_member_perm_stream(&self.pg_pool);
|
||||||
let workspace_policies = create_workspace_policies(workspace_member_perm_stream).await?;
|
let workspace_policies =
|
||||||
|
load_workspace_policies(&self.action_cache, workspace_member_perm_stream).await?;
|
||||||
|
|
||||||
// Policy definition `p` of type `p`. See `model.conf`
|
// Policy definition `p` of type `p`. See `model.conf`
|
||||||
model.add_policies("p", "p", workspace_policies);
|
model.add_policies("p", "p", workspace_policies);
|
||||||
|
|
||||||
let collab_member_access_lv_stream = select_collab_member_access_level(&self.pg_pool);
|
let collab_member_access_lv_stream = select_collab_member_access_level(&self.pg_pool);
|
||||||
let collab_policies = create_collab_policies(collab_member_access_lv_stream).await?;
|
let collab_policies =
|
||||||
|
load_collab_policies(&self.action_cache, collab_member_access_lv_stream).await?;
|
||||||
|
|
||||||
// Policy definition `p` of type `p`. See `model.conf`
|
// Policy definition `p` of type `p`. See `model.conf`
|
||||||
model.add_policies("p", "p", collab_policies);
|
model.add_policies("p", "p", collab_policies);
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,27 @@ use casbin::{CoreApi, Enforcer, MgmtApi};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::{event, trace};
|
use tracing::{event, trace};
|
||||||
|
|
||||||
pub struct AFEnforcer {
|
pub struct AFEnforcer {
|
||||||
enforcer: RwLock<Enforcer>,
|
enforcer: RwLock<Enforcer>,
|
||||||
/// Cache for the result of the policy check. It's a memory cache for faster access.
|
/// Cache for the result of the policy check. It's a memory cache for faster access.
|
||||||
result_by_policy_cache: DashMap<CachePolicyKey, bool>,
|
enforcer_result_cache: Arc<DashMap<PolicyCacheKey, bool>>,
|
||||||
action_by_object_cache: DashMap<CacheObjectKey, String>,
|
action_cache: Arc<DashMap<ActionCacheKey, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AFEnforcer {
|
impl AFEnforcer {
|
||||||
pub fn new(enforcer: Enforcer) -> Self {
|
pub fn new(
|
||||||
|
enforcer: Enforcer,
|
||||||
|
enforcer_result_cache: Arc<DashMap<PolicyCacheKey, bool>>,
|
||||||
|
action_cache: Arc<DashMap<ActionCacheKey, String>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
enforcer: RwLock::new(enforcer),
|
enforcer: RwLock::new(enforcer),
|
||||||
result_by_policy_cache: DashMap::new(),
|
enforcer_result_cache,
|
||||||
action_by_object_cache: Default::default(),
|
action_cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn contains(&self, obj: &ObjectType<'_>) -> bool {
|
pub async fn contains(&self, obj: &ObjectType<'_>) -> bool {
|
||||||
|
|
@ -65,17 +70,17 @@ impl AFEnforcer {
|
||||||
) -> Result<bool, AppError> {
|
) -> Result<bool, AppError> {
|
||||||
validate_obj_action(obj, act)?;
|
validate_obj_action(obj, act)?;
|
||||||
let policy = vec![uid.to_string(), obj.to_object_id(), act.to_action()];
|
let policy = vec![uid.to_string(), obj.to_object_id(), act.to_action()];
|
||||||
let policy_key = CachePolicyKey::new(&policy);
|
let policy_key = PolicyCacheKey::new(&policy);
|
||||||
|
|
||||||
// if the policy is already in the cache, return. Only update the policy if it's not in the cache.
|
// if the policy is already in the cache, return. Only update the policy if it's not in the cache.
|
||||||
if let Some(value) = self.result_by_policy_cache.get(&policy_key) {
|
if let Some(value) = self.enforcer_result_cache.get(&policy_key) {
|
||||||
return Ok(*value);
|
return Ok(*value);
|
||||||
}
|
}
|
||||||
|
|
||||||
event!(tracing::Level::INFO, "updating policy: {:?}", policy);
|
event!(tracing::Level::INFO, "updating policy: {:?}", policy);
|
||||||
// only one policy per user per object. So remove the old policy and add the new one.
|
// only one policy per user per object. So remove the old policy and add the new one.
|
||||||
let _remove_policies = self.remove(uid, obj).await?;
|
let _remove_policies = self.remove(uid, obj).await?;
|
||||||
let object_key = CacheObjectKey::new(uid, obj);
|
let object_key = ActionCacheKey::new(uid, obj);
|
||||||
let result = self
|
let result = self
|
||||||
.enforcer
|
.enforcer
|
||||||
.write()
|
.write()
|
||||||
|
|
@ -85,9 +90,7 @@ impl AFEnforcer {
|
||||||
.map_err(|e| AppError::Internal(anyhow!("fail to add policy: {e:?}")));
|
.map_err(|e| AppError::Internal(anyhow!("fail to add policy: {e:?}")));
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
trace!("cache action: {}:{}", object_key.0, act.to_action());
|
trace!("cache action: {}:{}", object_key.0, act.to_action());
|
||||||
self
|
self.action_cache.insert(object_key, act.to_action());
|
||||||
.action_by_object_cache
|
|
||||||
.insert(object_key, act.to_action());
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -126,12 +129,12 @@ impl AFEnforcer {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::Internal(anyhow!("casbin error enforce: {e:?}")))?;
|
.map_err(|e| AppError::Internal(anyhow!("casbin error enforce: {e:?}")))?;
|
||||||
|
|
||||||
let object_key = CacheObjectKey::new(uid, object_type);
|
let object_key = ActionCacheKey::new(uid, object_type);
|
||||||
self.action_by_object_cache.remove(&object_key);
|
self.action_cache.remove(&object_key);
|
||||||
for policy in &policies_for_user_on_object {
|
for policy in &policies_for_user_on_object {
|
||||||
self
|
self
|
||||||
.result_by_policy_cache
|
.enforcer_result_cache
|
||||||
.remove(&CachePolicyKey::new(policy));
|
.remove(&PolicyCacheKey::new(policy));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(policies_for_user_on_object)
|
Ok(policies_for_user_on_object)
|
||||||
|
|
@ -142,8 +145,8 @@ impl AFEnforcer {
|
||||||
A: ToCasbinAction,
|
A: ToCasbinAction,
|
||||||
{
|
{
|
||||||
let policy = vec![uid.to_string(), obj.to_object_id(), act.to_action()];
|
let policy = vec![uid.to_string(), obj.to_object_id(), act.to_action()];
|
||||||
let policy_key = CachePolicyKey::new(&policy);
|
let policy_key = PolicyCacheKey::new(&policy);
|
||||||
if let Some(value) = self.result_by_policy_cache.get(&policy_key) {
|
if let Some(value) = self.enforcer_result_cache.get(&policy_key) {
|
||||||
return Ok(*value);
|
return Ok(*value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,13 +166,13 @@ impl AFEnforcer {
|
||||||
.await
|
.await
|
||||||
.enforce(policy)
|
.enforce(policy)
|
||||||
.map_err(|e| AppError::Internal(anyhow!("error enforce: {e:?}")))?;
|
.map_err(|e| AppError::Internal(anyhow!("error enforce: {e:?}")))?;
|
||||||
self.result_by_policy_cache.insert(policy_key, result);
|
self.enforcer_result_cache.insert(policy_key, result);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_action(&self, uid: &i64, object_type: &ObjectType<'_>) -> Option<String> {
|
pub async fn get_action(&self, uid: &i64, object_type: &ObjectType<'_>) -> Option<String> {
|
||||||
let object_key = CacheObjectKey::new(uid, object_type);
|
let object_key = ActionCacheKey::new(uid, object_type);
|
||||||
if let Some(value) = self.action_by_object_cache.get(&object_key) {
|
if let Some(value) = self.action_cache.get(&object_key) {
|
||||||
return Some(value.clone());
|
return Some(value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,23 +183,21 @@ impl AFEnforcer {
|
||||||
let action = policies.first()?[POLICY_FIELD_INDEX_ACTION].clone();
|
let action = policies.first()?[POLICY_FIELD_INDEX_ACTION].clone();
|
||||||
|
|
||||||
trace!("cache action: {}:{}", object_key.0, action.clone());
|
trace!("cache action: {}:{}", object_key.0, action.clone());
|
||||||
self
|
self.action_cache.insert(object_key, action.clone());
|
||||||
.action_by_object_cache
|
|
||||||
.insert(object_key, action.clone());
|
|
||||||
Some(action)
|
Some(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
struct CachePolicyKey(String);
|
pub struct PolicyCacheKey(String);
|
||||||
|
|
||||||
impl CachePolicyKey {
|
impl PolicyCacheKey {
|
||||||
fn new(policy: &[String]) -> Self {
|
fn new(policy: &[String]) -> Self {
|
||||||
Self(policy.join(":"))
|
Self(policy.join(":"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for CachePolicyKey {
|
impl Deref for PolicyCacheKey {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
|
|
@ -204,10 +205,10 @@ impl Deref for CachePolicyKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
struct CacheObjectKey(String);
|
pub struct ActionCacheKey(String);
|
||||||
|
|
||||||
impl CacheObjectKey {
|
impl ActionCacheKey {
|
||||||
fn new(uid: &i64, object_type: &ObjectType<'_>) -> Self {
|
pub(crate) fn new(uid: &i64, object_type: &ObjectType<'_>) -> Self {
|
||||||
Self(format!("{}:{}", uid, object_type.to_object_id()))
|
Self(format!("{}:{}", uid, object_type.to_object_id()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ use crate::access_control::*;
|
||||||
use actix_http::Method;
|
use actix_http::Method;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use appflowy_cloud::biz;
|
use appflowy_cloud::biz;
|
||||||
use appflowy_cloud::biz::casbin::access_control::{
|
use appflowy_cloud::biz::casbin::access_control::{AccessControl, Action, ActionType, ObjectType};
|
||||||
AccessControl, Action, ActionType, ObjectType, MODEL_CONF,
|
|
||||||
};
|
|
||||||
use appflowy_cloud::biz::casbin::adapter::PgAdapter;
|
|
||||||
use appflowy_cloud::biz::pg_listener::PgListeners;
|
use appflowy_cloud::biz::pg_listener::PgListeners;
|
||||||
use casbin::{CoreApi, DefaultModel, Enforcer};
|
|
||||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||||
use realtime::collaborate::CollabAccessControl;
|
use realtime::collaborate::CollabAccessControl;
|
||||||
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
||||||
|
|
@ -19,15 +16,14 @@ use tokio::time::sleep;
|
||||||
async fn test_collab_access_control(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_collab_access_control(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_collab_access_control();
|
let access_control = access_control.new_collab_access_control();
|
||||||
|
|
||||||
let user = create_user(&pool).await?;
|
let user = create_user(&pool).await?;
|
||||||
|
|
@ -135,15 +131,14 @@ async fn test_collab_access_control(pool: PgPool) -> anyhow::Result<()> {
|
||||||
async fn test_collab_access_control_when_obj_not_exist(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_collab_access_control_when_obj_not_exist(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_collab_access_control();
|
let access_control = access_control.new_collab_access_control();
|
||||||
let user = create_user(&pool).await?;
|
let user = create_user(&pool).await?;
|
||||||
|
|
||||||
|
|
@ -158,15 +153,14 @@ async fn test_collab_access_control_when_obj_not_exist(pool: PgPool) -> anyhow::
|
||||||
async fn test_collab_access_control_access_http_method(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_collab_access_control_access_http_method(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_collab_access_control();
|
let access_control = access_control.new_collab_access_control();
|
||||||
|
|
||||||
let user = create_user(&pool).await?;
|
let user = create_user(&pool).await?;
|
||||||
|
|
@ -259,15 +253,14 @@ async fn test_collab_access_control_access_http_method(pool: PgPool) -> anyhow::
|
||||||
async fn test_collab_access_control_send_receive_collab_update(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_collab_access_control_send_receive_collab_update(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_collab_access_control();
|
let access_control = access_control.new_collab_access_control();
|
||||||
|
|
||||||
let user = create_user(&pool).await?;
|
let user = create_user(&pool).await?;
|
||||||
|
|
@ -344,15 +337,14 @@ async fn test_collab_access_control_send_receive_collab_update(pool: PgPool) ->
|
||||||
async fn test_collab_access_control_cache_collab_access_level(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_collab_access_control_cache_collab_access_level(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_collab_access_control();
|
let access_control = access_control.new_collab_access_control();
|
||||||
|
|
||||||
let uid = 123;
|
let uid = 123;
|
||||||
|
|
@ -381,16 +373,14 @@ async fn test_collab_access_control_cache_collab_access_level(pool: PgPool) -> a
|
||||||
#[sqlx::test(migrations = false)]
|
#[sqlx::test(migrations = false)]
|
||||||
async fn test_casbin_access_control_update_remove(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_casbin_access_control_update_remove(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let uid = 123;
|
let uid = 123;
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@ use crate::access_control::{
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use app_error::ErrorCode;
|
use app_error::ErrorCode;
|
||||||
use appflowy_cloud::biz;
|
use appflowy_cloud::biz;
|
||||||
use appflowy_cloud::biz::casbin::access_control::{AccessControl, MODEL_CONF};
|
use appflowy_cloud::biz::casbin::access_control::AccessControl;
|
||||||
use appflowy_cloud::biz::casbin::adapter::PgAdapter;
|
|
||||||
use appflowy_cloud::biz::pg_listener::PgListeners;
|
use appflowy_cloud::biz::pg_listener::PgListeners;
|
||||||
use casbin::{CoreApi, DefaultModel, Enforcer};
|
|
||||||
use database_entity::dto::AFRole;
|
use database_entity::dto::AFRole;
|
||||||
use shared_entity::dto::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
|
use shared_entity::dto::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
@ -16,15 +15,14 @@ use sqlx::PgPool;
|
||||||
async fn test_workspace_access_control_get_role(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_workspace_access_control_get_role(pool: PgPool) -> anyhow::Result<()> {
|
||||||
setup_db(&pool).await?;
|
setup_db(&pool).await?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
|
||||||
let listeners = PgListeners::new(&pool).await?;
|
let listeners = PgListeners::new(&pool).await?;
|
||||||
let access_control = AccessControl::new(
|
let access_control = AccessControl::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
listeners.subscribe_collab_member_change(),
|
listeners.subscribe_collab_member_change(),
|
||||||
listeners.subscribe_workspace_member_change(),
|
listeners.subscribe_workspace_member_change(),
|
||||||
enforcer,
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let access_control = access_control.new_workspace_access_control();
|
let access_control = access_control.new_workspace_access_control();
|
||||||
|
|
||||||
let user = create_user(&pool).await?;
|
let user = create_user(&pool).await?;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ use appflowy_cloud::biz;
|
||||||
use appflowy_cloud::biz::casbin::access_control::{Action, ObjectType, ToCasbinAction, MODEL_CONF};
|
use appflowy_cloud::biz::casbin::access_control::{Action, ObjectType, ToCasbinAction, MODEL_CONF};
|
||||||
use appflowy_cloud::biz::casbin::adapter::PgAdapter;
|
use appflowy_cloud::biz::casbin::adapter::PgAdapter;
|
||||||
use casbin::{CoreApi, DefaultModel, Enforcer};
|
use casbin::{CoreApi, DefaultModel, Enforcer};
|
||||||
|
use dashmap::DashMap;
|
||||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||||
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[sqlx::test(migrations = false)]
|
#[sqlx::test(migrations = false)]
|
||||||
async fn test_create_user(pool: PgPool) -> anyhow::Result<()> {
|
async fn test_create_user(pool: PgPool) -> anyhow::Result<()> {
|
||||||
|
|
@ -22,7 +24,11 @@ async fn test_create_user(pool: PgPool) -> anyhow::Result<()> {
|
||||||
.ok_or(anyhow!("workspace should be created"))?;
|
.ok_or(anyhow!("workspace should be created"))?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
let enforcer = Enforcer::new(
|
||||||
|
model,
|
||||||
|
PgAdapter::new(pool.clone(), Arc::new(DashMap::new())),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
assert!(enforcer
|
assert!(enforcer
|
||||||
.enforce((
|
.enforce((
|
||||||
|
|
@ -107,8 +113,11 @@ async fn test_add_users_to_workspace(pool: PgPool) -> anyhow::Result<()> {
|
||||||
.context("adding users to workspace")?;
|
.context("adding users to workspace")?;
|
||||||
|
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
||||||
let enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
let enforcer = Enforcer::new(
|
||||||
|
model,
|
||||||
|
PgAdapter::new(pool.clone(), Arc::new(DashMap::new())),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
{
|
{
|
||||||
// Owner
|
// Owner
|
||||||
let user = user_owner;
|
let user = user_owner;
|
||||||
|
|
@ -233,8 +242,11 @@ async fn test_reload_policy_after_adding_user_to_workspace(pool: PgPool) -> anyh
|
||||||
|
|
||||||
// Create enforcer before adding user to workspace
|
// Create enforcer before adding user to workspace
|
||||||
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
let model = DefaultModel::from_str(MODEL_CONF).await?;
|
||||||
let mut enforcer = Enforcer::new(model, PgAdapter::new(pool.clone())).await?;
|
let mut enforcer = Enforcer::new(
|
||||||
|
model,
|
||||||
|
PgAdapter::new(pool.clone(), Arc::new(DashMap::new())),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let members = vec![CreateWorkspaceMember {
|
let members = vec![CreateWorkspaceMember {
|
||||||
email: user_member.email.clone(),
|
email: user_member.email.clone(),
|
||||||
role: AFRole::Member,
|
role: AFRole::Member,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue