Merge pull request #328 from AppFlowy-IO/workspace-add-email
feat: send email and create user if adding member but member not exist
This commit is contained in:
commit
5e7794646a
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO af_collab_snapshot (oid, blob, len, encrypt, workspace_id) \n VALUES ($1, $2, $3, $4, $5)\n RETURNING sid AS snapshot_id, oid AS object_id, created_at\n ",
|
||||
"query": "\n INSERT INTO af_collab_snapshot (oid, blob, len, encrypt, workspace_id)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING sid AS snapshot_id, oid AS object_id, created_at\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -34,5 +34,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "5144da61424400216e10d9327f5136f77d96a83d462bf815f13ebe70d88fada3"
|
||||
"hash": "f18d6e075a522b0ce5935351dd57ab0dda4d8b4ed3881c2ad0bc09c07c43e6fe"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT sid as \"snapshot_id\", oid as \"object_id\", created_at\n FROM af_collab_snapshot \n WHERE oid = $1 AND deleted_at IS NULL\n ORDER BY created_at DESC;\n ",
|
||||
"query": "\n SELECT sid as \"snapshot_id\", oid as \"object_id\", created_at\n FROM af_collab_snapshot\n WHERE oid = $1 AND deleted_at IS NULL\n ORDER BY created_at DESC;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -30,5 +30,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "eff32b7631cb1b715776c66749706943c7ba23eac358cae6d36537991a8f8975"
|
||||
"hash": "f409626142553d4496d15b5dfa7da8a5a238da86f56c930c09a261f2efa1f55c"
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ pub async fn admin_users_handler(
|
|||
) -> Result<Html<String>, WebAppError> {
|
||||
let users = state
|
||||
.gotrue_client
|
||||
.admin_list_user(&session.token.access_token)
|
||||
.admin_list_user(&session.token.access_token, None)
|
||||
.await
|
||||
.map_or_else(
|
||||
|err| {
|
||||
|
|
|
|||
|
|
@ -408,20 +408,32 @@ impl Client {
|
|||
email: &str,
|
||||
password: &str,
|
||||
) -> Result<User, AppResponseError> {
|
||||
Ok(
|
||||
self
|
||||
.gotrue_client
|
||||
.admin_add_user(
|
||||
&self.access_token()?,
|
||||
&AdminUserParams {
|
||||
email: email.to_owned(),
|
||||
password: Some(password.to_owned()),
|
||||
email_confirm: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
let user = self
|
||||
.gotrue_client
|
||||
.admin_add_user(
|
||||
&self.access_token()?,
|
||||
&AdminUserParams {
|
||||
email: email.to_owned(),
|
||||
password: Some(password.to_owned()),
|
||||
email_confirm: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
// filter is postgre sql like filter
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn admin_list_users(
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
) -> Result<Vec<User>, AppResponseError> {
|
||||
let user = self
|
||||
.gotrue_client
|
||||
.admin_list_user(&self.access_token()?, filter)
|
||||
.await?;
|
||||
Ok(user.users)
|
||||
}
|
||||
|
||||
/// Only expose this method for testing
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ pub async fn select_workspace<'a, E: Executor<'a, Database = Postgres>>(
|
|||
.await?;
|
||||
Ok(workspace)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn update_updated_at_of_workspace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::grant::Grant;
|
||||
use crate::params::{
|
||||
AdminDeleteUserParams, AdminUserParams, CreateSSOProviderParams, GenerateLinkParams,
|
||||
GenerateLinkResponse, MagicLinkParams,
|
||||
GenerateLinkResponse, InviteUserParams, MagicLinkParams,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use gotrue_entity::dto::{
|
||||
|
|
@ -139,12 +139,14 @@ impl Client {
|
|||
pub async fn admin_list_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
filter: Option<&str>,
|
||||
) -> Result<AdminListUsersResponse, GoTrueError> {
|
||||
let url = format!("{}/admin/users", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url, access_token)
|
||||
.send()
|
||||
.await?;
|
||||
let mut req = self.http_client_with_auth(Method::GET, &url, access_token);
|
||||
if let Some(filter) = filter {
|
||||
req = req.query(&[("filter", filter)]);
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +193,20 @@ impl Client {
|
|||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
pub async fn admin_invite_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
admin_user_params: &InviteUserParams,
|
||||
) -> Result<User, GoTrueError> {
|
||||
let url = format!("{}/invite", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url, access_token)
|
||||
.json(&admin_user_params)
|
||||
.send()
|
||||
.await?;
|
||||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
pub async fn admin_add_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ pub struct AdminDeleteUserParams {
|
|||
pub should_soft_delete: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct InviteUserParams {
|
||||
pub email: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct AdminUserParams {
|
||||
pub aud: String,
|
||||
|
|
|
|||
|
|
@ -176,15 +176,13 @@ async fn add_workspace_members_handler(
|
|||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let create_members = payload.into_inner();
|
||||
let role_by_uid = workspace::ops::add_workspace_members(
|
||||
&state.pg_pool,
|
||||
&user_uuid,
|
||||
&workspace_id,
|
||||
create_members.0,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let role_by_uid =
|
||||
workspace::ops::add_workspace_members(&state, &user_uuid, &workspace_id, create_members.0)
|
||||
.await?;
|
||||
|
||||
for (uid, role) in role_by_uid {
|
||||
// TODO: use one single query to insert all roles
|
||||
state
|
||||
.workspace_access_control
|
||||
.insert_workspace_role(&uid, &workspace_id, role)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,32 @@
|
|||
use crate::api::metrics::metrics_scope;
|
||||
|
||||
use crate::component::auth::HEADER_TOKEN;
|
||||
use crate::config::config::{Config, DatabaseSetting, GoTrueSetting, S3Setting};
|
||||
use crate::middleware::request_id::RequestIdMiddleware;
|
||||
use crate::self_signed::create_self_signed_certificate;
|
||||
use crate::state::{AppState, UserCache};
|
||||
use actix_identity::IdentityMiddleware;
|
||||
use actix_session::storage::RedisSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
use actix_web::cookie::Key;
|
||||
use actix_web::{dev::Server, web, web::Data, App, HttpServer};
|
||||
|
||||
use actix::Actor;
|
||||
use anyhow::{Context, Error};
|
||||
use app_error::AppError;
|
||||
use gotrue::grant::{Grant, PasswordGrant};
|
||||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
|
||||
use openssl::x509::X509;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use snowflake::Snowflake;
|
||||
use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||
use std::net::TcpListener;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::info;
|
||||
|
||||
use crate::api::file_storage::file_storage_scope;
|
||||
use crate::api::user::user_scope;
|
||||
use crate::api::workspace::{collab_scope, workspace_scope};
|
||||
|
|
@ -10,32 +37,11 @@ use crate::biz::collab::storage::init_collab_storage;
|
|||
use crate::biz::pg_listener::PgListeners;
|
||||
use crate::biz::user::RealtimeUserImpl;
|
||||
use crate::biz::workspace::access_control::WorkspaceHttpAccessControl;
|
||||
use crate::component::auth::HEADER_TOKEN;
|
||||
use crate::config::config::{Config, DatabaseSetting, GoTrueSetting, S3Setting};
|
||||
use crate::middleware::access_control_mw::WorkspaceAccessControl;
|
||||
use crate::middleware::metrics_mw::MetricsMiddleware;
|
||||
use crate::middleware::request_id::RequestIdMiddleware;
|
||||
use crate::self_signed::create_self_signed_certificate;
|
||||
use crate::state::{AppMetrics, AppState, UserCache};
|
||||
use actix::Actor;
|
||||
use actix_identity::IdentityMiddleware;
|
||||
use actix_session::storage::RedisSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
use actix_web::cookie::Key;
|
||||
use actix_web::{dev::Server, web, web::Data, App, HttpServer};
|
||||
use anyhow::{Context, Error};
|
||||
use crate::state::AppMetrics;
|
||||
use database::file::bucket_s3_impl::S3BucketStorage;
|
||||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
|
||||
use openssl::x509::X509;
|
||||
use realtime::collaborate::CollabServer;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use snowflake::Snowflake;
|
||||
use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||
use std::net::TcpListener;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::info;
|
||||
|
||||
pub struct Application {
|
||||
port: u16,
|
||||
|
|
@ -167,6 +173,12 @@ pub async fn init_state(config: &Config) -> Result<AppState, Error> {
|
|||
let gotrue_client = get_gotrue_client(&config.gotrue).await?;
|
||||
setup_admin_account(&gotrue_client, &pg_pool, &config.gotrue).await?;
|
||||
|
||||
// Gotrue Admin
|
||||
let gotrue_admin = GoTrueAdmin::new(
|
||||
config.gotrue.admin_email.clone(),
|
||||
config.gotrue.admin_password.clone(),
|
||||
);
|
||||
|
||||
// Redis
|
||||
info!("Connecting to Redis...");
|
||||
let redis_client = get_redis_client(config.redis_uri.expose_secret()).await?;
|
||||
|
|
@ -207,6 +219,7 @@ pub async fn init_state(config: &Config) -> Result<AppState, Error> {
|
|||
users: Arc::new(users),
|
||||
id_gen: Arc::new(RwLock::new(Snowflake::new(1))),
|
||||
gotrue_client,
|
||||
gotrue_admin,
|
||||
redis_client,
|
||||
collab_storage,
|
||||
collab_access_control,
|
||||
|
|
@ -218,6 +231,31 @@ pub async fn init_state(config: &Config) -> Result<AppState, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoTrueAdmin {
|
||||
pub admin_email: String,
|
||||
pub password: Secret<String>,
|
||||
}
|
||||
|
||||
impl GoTrueAdmin {
|
||||
pub fn new(admin_email: String, password: String) -> Self {
|
||||
Self {
|
||||
admin_email,
|
||||
password: password.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn token(&self, client: &gotrue::api::Client) -> Result<String, AppError> {
|
||||
let token = client
|
||||
.token(&Grant::Password(PasswordGrant {
|
||||
email: self.admin_email.clone(),
|
||||
password: self.password.expose_secret().clone(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(token.access_token)
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_admin_account(
|
||||
gotrue_client: &gotrue::api::Client,
|
||||
pg_pool: &PgPool,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::{Context, Result};
|
||||
use gotrue_entity::dto::User;
|
||||
|
||||
use crate::biz::workspace::access_control::WorkspaceAccessControl;
|
||||
use crate::state::AppState;
|
||||
|
|
@ -26,15 +27,30 @@ use workspace_template::{WorkspaceTemplate, WorkspaceTemplateBuilder};
|
|||
#[instrument(skip_all, err)]
|
||||
pub async fn verify_token(access_token: &str, state: &AppState) -> Result<bool, AppError> {
|
||||
let user = state.gotrue_client.user_info(access_token).await?;
|
||||
let user_uuid = uuid::Uuid::parse_str(&user.id)?;
|
||||
let name = name_from_user_metadata(&user.user_metadata);
|
||||
|
||||
let mut txn = state
|
||||
.pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("acquire transaction to verify token")?;
|
||||
|
||||
let is_new = create_user_if_not_exists(&user, &mut txn, state).await;
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("fail to commit transaction to verify token")?;
|
||||
|
||||
is_new
|
||||
}
|
||||
|
||||
/// Returns if user is new
|
||||
pub async fn create_user_if_not_exists(
|
||||
user: &User,
|
||||
txn: &mut Transaction<'_, sqlx::Postgres>,
|
||||
state: &AppState,
|
||||
) -> Result<bool, AppError> {
|
||||
let user_uuid = uuid::Uuid::parse_str(&user.id)?;
|
||||
let name = name_from_user_metadata(&user.user_metadata);
|
||||
|
||||
// To prevent concurrent creation of the same user with the same workspace resources, we lock
|
||||
// the user row when `verify_token` is called. This means that if multiple requests try to
|
||||
// create the same user simultaneously, the first request will acquire the lock, create the user,
|
||||
|
|
@ -67,16 +83,12 @@ pub async fn verify_token(access_token: &str, state: &AppState) -> Result<bool,
|
|||
create_workspace_for_user(
|
||||
new_uid,
|
||||
&workspace_id,
|
||||
&mut txn,
|
||||
txn,
|
||||
vec![GetStartedDocumentTemplate],
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("fail to commit transaction to verify token")?;
|
||||
Ok(is_new)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use app_error::AppError;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{self, AsyncRead, ReadBuf};
|
||||
|
|
@ -42,3 +43,19 @@ where
|
|||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_user_exists(
|
||||
admin_token: &str,
|
||||
gotrue_client: &gotrue::api::Client,
|
||||
email: &str,
|
||||
) -> Result<bool, AppError> {
|
||||
let users = gotrue_client
|
||||
.admin_list_user(admin_token, Some(email))
|
||||
.await?;
|
||||
for user in users.users {
|
||||
if user.email == email {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use database::workspace::{
|
|||
select_workspace_member_list, update_updated_at_of_workspace, upsert_workspace_member,
|
||||
};
|
||||
use database_entity::dto::{AFAccessLevel, AFRole, AFWorkspace};
|
||||
use gotrue::params::InviteUserParams;
|
||||
use shared_entity::dto::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
|
||||
use shared_entity::response::AppResponseError;
|
||||
use sqlx::{types::uuid, PgPool};
|
||||
|
|
@ -17,6 +18,10 @@ use std::ops::DerefMut;
|
|||
use tracing::instrument;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::biz::user::create_user_if_not_exists;
|
||||
use crate::biz::utils::check_user_exists;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn delete_workspace_for_user(
|
||||
pg_pool: &PgPool,
|
||||
workspace_id: &Uuid,
|
||||
|
|
@ -83,16 +88,52 @@ pub async fn open_workspace(
|
|||
///
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn add_workspace_members(
|
||||
pg_pool: &PgPool,
|
||||
state: &AppState,
|
||||
_user_uuid: &Uuid,
|
||||
workspace_id: &Uuid,
|
||||
members: Vec<CreateWorkspaceMember>,
|
||||
) -> Result<HashMap<i64, AFRole>, AppError> {
|
||||
let mut txn = pg_pool
|
||||
// Invite user to workspace if user is not registered
|
||||
let admin_token = state.gotrue_admin.token(&state.gotrue_client).await?;
|
||||
let mut txn = state
|
||||
.pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to insert workspace members")?;
|
||||
|
||||
let gotrue_client = &state.gotrue_client;
|
||||
for member in members.iter() {
|
||||
let user_exists = check_user_exists(&admin_token, gotrue_client, &member.email).await?;
|
||||
if !user_exists {
|
||||
let user = gotrue_client
|
||||
.admin_invite_user(
|
||||
&admin_token,
|
||||
&InviteUserParams {
|
||||
email: member.email.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let is_new = create_user_if_not_exists(&user, &mut txn, state).await?;
|
||||
if !is_new {
|
||||
tracing::error!("User should be new but is not new. User: {:?}", user);
|
||||
}
|
||||
}
|
||||
}
|
||||
let role_by_uid = add_workspace_members_db(workspace_id, members, &mut txn).await?;
|
||||
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to insert workspace members")?;
|
||||
Ok(role_by_uid)
|
||||
}
|
||||
|
||||
pub async fn add_workspace_members_db(
|
||||
workspace_id: &Uuid,
|
||||
members: Vec<CreateWorkspaceMember>,
|
||||
txn: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<HashMap<i64, AFRole>, AppError> {
|
||||
let mut role_by_uid = HashMap::new();
|
||||
for member in members.into_iter() {
|
||||
let access_level = match &member.role {
|
||||
|
|
@ -106,16 +147,11 @@ pub async fn add_workspace_members(
|
|||
// "Failed to get uid from email {} when adding workspace members",
|
||||
// member.email
|
||||
// ))?;
|
||||
insert_workspace_member_with_txn(&mut txn, workspace_id, &member.email, member.role.clone())
|
||||
.await?;
|
||||
upsert_collab_member_with_txn(uid, workspace_id.to_string(), &access_level, &mut txn).await?;
|
||||
insert_workspace_member_with_txn(txn, workspace_id, &member.email, member.role.clone()).await?;
|
||||
upsert_collab_member_with_txn(uid, workspace_id.to_string(), &access_level, txn).await?;
|
||||
role_by_uid.insert(uid, member.role);
|
||||
}
|
||||
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to insert workspace members")?;
|
||||
Ok(role_by_uid)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::application::GoTrueAdmin;
|
||||
use crate::biz::casbin::{CollabAccessControlImpl, WorkspaceAccessControlImpl};
|
||||
use crate::biz::collab::storage::CollabPostgresDBStorage;
|
||||
use crate::biz::pg_listener::PgListeners;
|
||||
|
|
@ -29,6 +30,7 @@ pub struct AppState {
|
|||
pub users: Arc<UserCache>,
|
||||
pub id_gen: Arc<RwLock<Snowflake>>,
|
||||
pub gotrue_client: gotrue::api::Client,
|
||||
pub gotrue_admin: GoTrueAdmin,
|
||||
pub redis_client: RedisClient,
|
||||
pub collab_storage: Arc<CollabPostgresDBStorage>,
|
||||
pub collab_access_control: CollabAccessControlImpl,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::access_control::*;
|
||||
use actix_http::Method;
|
||||
use anyhow::{anyhow, Context};
|
||||
use appflowy_cloud::biz;
|
||||
use appflowy_cloud::biz::casbin::access_control::{Action, ActionType, ObjectType};
|
||||
|
||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||
|
|
@ -42,10 +41,7 @@ async fn test_collab_access_control(pool: PgPool) -> anyhow::Result<()> {
|
|||
role: AFRole::Guest,
|
||||
},
|
||||
];
|
||||
let _ =
|
||||
biz::workspace::ops::add_workspace_members(&pool, &user.uuid, &workspace.workspace_id, members)
|
||||
.await
|
||||
.context("adding users to workspace")?;
|
||||
let _ = add_workspace_members_in_tx(&pool, &workspace.workspace_id, members).await;
|
||||
|
||||
// user that created the workspace should have full access
|
||||
assert_access_level(
|
||||
|
|
@ -148,18 +144,15 @@ async fn test_collab_access_control_access_http_method(pool: PgPool) -> anyhow::
|
|||
.next()
|
||||
.ok_or(anyhow!("workspace should be created"))?;
|
||||
|
||||
let _ = biz::workspace::ops::add_workspace_members(
|
||||
let _ = add_workspace_members_in_tx(
|
||||
&pool,
|
||||
&guest.uuid,
|
||||
&workspace.workspace_id,
|
||||
vec![CreateWorkspaceMember {
|
||||
email: guest.email,
|
||||
role: AFRole::Guest,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.context("adding users to workspace")
|
||||
.unwrap();
|
||||
.await;
|
||||
|
||||
for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] {
|
||||
assert_can_access_http_method(
|
||||
|
|
@ -243,17 +236,15 @@ async fn test_collab_access_control_send_receive_collab_update(pool: PgPool) ->
|
|||
.next()
|
||||
.ok_or(anyhow!("workspace should be created"))?;
|
||||
|
||||
let _ = biz::workspace::ops::add_workspace_members(
|
||||
let _ = add_workspace_members_in_tx(
|
||||
&pool,
|
||||
&guest.uuid,
|
||||
&workspace.workspace_id,
|
||||
vec![CreateWorkspaceMember {
|
||||
email: guest.email,
|
||||
role: AFRole::Guest,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.context("adding users to workspace")?;
|
||||
.await;
|
||||
|
||||
// Need to wait for the listener(spawn_listen_on_workspace_member_change) to receive the event
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::access_control::{
|
||||
assert_workspace_role, assert_workspace_role_error, create_user, setup_access_control,
|
||||
add_workspace_members_in_tx, assert_workspace_role, assert_workspace_role_error, create_user,
|
||||
setup_access_control,
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use app_error::ErrorCode;
|
||||
|
|
@ -33,17 +34,16 @@ async fn test_workspace_access_control_get_role(pool: PgPool) -> anyhow::Result<
|
|||
.await;
|
||||
|
||||
let member = create_user(&pool).await?;
|
||||
let _ = biz::workspace::ops::add_workspace_members(
|
||||
|
||||
let _ = add_workspace_members_in_tx(
|
||||
&pool,
|
||||
&member.uuid,
|
||||
&workspace.workspace_id,
|
||||
vec![CreateWorkspaceMember {
|
||||
email: member.email.clone(),
|
||||
role: AFRole::Member,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.context("adding users to workspace")?;
|
||||
.await;
|
||||
|
||||
assert_workspace_role(
|
||||
&workspace_access_control,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
use actix_http::Method;
|
||||
use anyhow::{Context, Error};
|
||||
use app_error::ErrorCode;
|
||||
use appflowy_cloud::biz;
|
||||
use appflowy_cloud::biz::casbin::{CollabAccessControlImpl, WorkspaceAccessControlImpl};
|
||||
use appflowy_cloud::biz::workspace::access_control::WorkspaceAccessControl;
|
||||
use client_api_test_util::setup_log;
|
||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||
use lazy_static::lazy_static;
|
||||
use realtime::collaborate::CollabAccessControl;
|
||||
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
||||
use snowflake::Snowflake;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use casbin::{CoreApi, DefaultModel, Enforcer};
|
||||
use dashmap::DashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::{interval, timeout};
|
||||
|
|
@ -307,3 +310,22 @@ pub async fn assert_can_access_http_method(
|
|||
timeout(timeout_duration, operation).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_workspace_members_in_tx(
|
||||
pool: &PgPool,
|
||||
workspace_id: &Uuid,
|
||||
members: Vec<CreateWorkspaceMember>,
|
||||
) -> HashMap<i64, AFRole> {
|
||||
let mut txn = pool
|
||||
.begin()
|
||||
.await
|
||||
.expect("acquire transaction to add workspace members");
|
||||
let res = biz::workspace::ops::add_workspace_members_db(workspace_id, members, &mut txn)
|
||||
.await
|
||||
.expect("add workspace members");
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.expect("commit transaction to add workspace members");
|
||||
res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use crate::access_control::*;
|
||||
use anyhow::anyhow;
|
||||
use appflowy_cloud::biz;
|
||||
use appflowy_cloud::biz::casbin::access_control::{Action, ObjectType, ToCasbinAction};
|
||||
|
||||
use casbin::CoreApi;
|
||||
|
||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||
|
|
@ -96,14 +94,7 @@ async fn test_add_users_to_workspace(pool: PgPool) -> anyhow::Result<()> {
|
|||
role: AFRole::Guest,
|
||||
},
|
||||
];
|
||||
let _ = biz::workspace::ops::add_workspace_members(
|
||||
&pool,
|
||||
&user_main.uuid,
|
||||
&workspace.workspace_id,
|
||||
members,
|
||||
)
|
||||
.await
|
||||
.context("adding users to workspace")?;
|
||||
let _ = add_workspace_members_in_tx(&pool, &workspace.workspace_id, members).await;
|
||||
let enforcer = setup_enforcer(&pool).await?;
|
||||
{
|
||||
// Owner
|
||||
|
|
@ -232,14 +223,8 @@ async fn test_reload_policy_after_adding_user_to_workspace(pool: PgPool) -> anyh
|
|||
email: user_member.email.clone(),
|
||||
role: AFRole::Member,
|
||||
}];
|
||||
let _ = biz::workspace::ops::add_workspace_members(
|
||||
&pool,
|
||||
&user_owner.uuid,
|
||||
&workspace.workspace_id,
|
||||
members,
|
||||
)
|
||||
.await
|
||||
.context("adding users to workspace")?;
|
||||
|
||||
let _ = add_workspace_members_in_tx(&pool, &workspace.workspace_id, members).await;
|
||||
|
||||
assert!(!enforcer
|
||||
.enforce((
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ async fn admin_user_create_list_edit_delete() {
|
|||
|
||||
// list users
|
||||
let users = gotrue_client
|
||||
.admin_list_user(&admin_token.access_token)
|
||||
.admin_list_user(&admin_token.access_token, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.users;
|
||||
|
|
@ -94,7 +94,7 @@ async fn admin_user_create_list_edit_delete() {
|
|||
.unwrap();
|
||||
|
||||
let users = gotrue_client
|
||||
.admin_list_user(&admin_token.access_token)
|
||||
.admin_list_user(&admin_token.access_token, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.users;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use app_error::ErrorCode;
|
||||
use client_api_test_util::TestClient;
|
||||
use client_api_test_util::{admin_user_client, TestClient};
|
||||
use database_entity::dto::AFRole;
|
||||
use shared_entity::dto::workspace_dto::CreateWorkspaceMember;
|
||||
|
||||
|
|
@ -36,25 +36,6 @@ async fn add_duplicate_workspace_members() {
|
|||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_not_exist_workspace_members() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let workspace_id = c1.workspace_id().await;
|
||||
let email = format!("{}@appflowy.io", uuid::Uuid::new_v4());
|
||||
let err = c1
|
||||
.api_client
|
||||
.add_workspace_members(
|
||||
workspace_id,
|
||||
vec![CreateWorkspaceMember {
|
||||
email,
|
||||
role: AFRole::Member,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn update_workspace_member_role_not_enough_permission() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
|
|
@ -253,3 +234,34 @@ async fn get_user_workspace_info_after_open_workspace() {
|
|||
workspace_id_c1
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_workspace_member_not_exists() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let workspace_id_c1 = c1.workspace_id().await;
|
||||
let non_exist_email = format!("{}@appflowy.io", uuid::Uuid::new_v4());
|
||||
c1.api_client
|
||||
.add_workspace_members(
|
||||
&workspace_id_c1,
|
||||
vec![CreateWorkspaceMember {
|
||||
email: non_exist_email.clone(),
|
||||
role: AFRole::Member,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.expect("should be able to add member whose email not exists yet");
|
||||
|
||||
let admin_client = admin_user_client().await;
|
||||
let users = admin_client
|
||||
.admin_list_users(Some(&non_exist_email))
|
||||
.await
|
||||
.expect("should be able to list users");
|
||||
println!("---------non_exist_email: {}", non_exist_email);
|
||||
let non_exist_user = users
|
||||
.iter()
|
||||
.find(|u| u.email == non_exist_email)
|
||||
.expect("should find the user");
|
||||
|
||||
// since user does not exist and is just created, it should not be confirmed yet
|
||||
assert!(non_exist_user.confirmed_at.is_none());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue