|
@ -48,6 +48,7 @@ pub fn routes() -> Vec<Route> { |
|
|
confirm_invite, |
|
|
confirm_invite, |
|
|
bulk_confirm_invite, |
|
|
bulk_confirm_invite, |
|
|
accept_invite, |
|
|
accept_invite, |
|
|
|
|
|
get_org_user_mini_details, |
|
|
get_user, |
|
|
get_user, |
|
|
edit_user, |
|
|
edit_user, |
|
|
put_organization_user, |
|
|
put_organization_user, |
|
@ -77,6 +78,7 @@ pub fn routes() -> Vec<Route> { |
|
|
restore_organization_user, |
|
|
restore_organization_user, |
|
|
bulk_restore_organization_user, |
|
|
bulk_restore_organization_user, |
|
|
get_groups, |
|
|
get_groups, |
|
|
|
|
|
get_groups_details, |
|
|
post_groups, |
|
|
post_groups, |
|
|
get_group, |
|
|
get_group, |
|
|
put_group, |
|
|
put_group, |
|
@ -98,6 +100,7 @@ pub fn routes() -> Vec<Route> { |
|
|
get_org_export, |
|
|
get_org_export, |
|
|
api_key, |
|
|
api_key, |
|
|
rotate_api_key, |
|
|
rotate_api_key, |
|
|
|
|
|
get_billing_metadata, |
|
|
] |
|
|
] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -322,7 +325,14 @@ async fn get_org_collections_details(org_id: &str, headers: ManagerHeadersLoose, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// get all collection memberships for the current organization
|
|
|
// get all collection memberships for the current organization
|
|
|
let coll_users = CollectionUser::find_by_organization(org_id, &mut conn).await; |
|
|
let coll_users = CollectionUser::find_by_organization_swap_user_uuid_with_org_user_uuid(org_id, &mut conn).await; |
|
|
|
|
|
// Generate a HashMap to get the correct UserOrgType per user to determine the manage permission
|
|
|
|
|
|
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
|
|
|
|
|
let users_org_type: HashMap<String, i32> = UserOrganization::find_confirmed_by_org(org_id, &mut conn) |
|
|
|
|
|
.await |
|
|
|
|
|
.into_iter() |
|
|
|
|
|
.map(|uo| (uo.uuid, uo.atype)) |
|
|
|
|
|
.collect(); |
|
|
|
|
|
|
|
|
// check if current user has full access to the organization (either directly or via any group)
|
|
|
// check if current user has full access to the organization (either directly or via any group)
|
|
|
let has_full_access_to_org = user_org.access_all |
|
|
let has_full_access_to_org = user_org.access_all |
|
@ -336,11 +346,22 @@ async fn get_org_collections_details(org_id: &str, headers: ManagerHeadersLoose, |
|
|
|| (CONFIG.org_groups_enabled() |
|
|
|| (CONFIG.org_groups_enabled() |
|
|
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &user_org.uuid, &mut conn).await); |
|
|
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &user_org.uuid, &mut conn).await); |
|
|
|
|
|
|
|
|
|
|
|
// Not assigned collections should not be returned
|
|
|
|
|
|
if !assigned { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// get the users assigned directly to the given collection
|
|
|
// get the users assigned directly to the given collection
|
|
|
let users: Vec<Value> = coll_users |
|
|
let users: Vec<Value> = coll_users |
|
|
.iter() |
|
|
.iter() |
|
|
.filter(|collection_user| collection_user.collection_uuid == col.uuid) |
|
|
.filter(|collection_user| collection_user.collection_uuid == col.uuid) |
|
|
.map(|collection_user| SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json()) |
|
|
.map(|collection_user| { |
|
|
|
|
|
SelectionReadOnly::to_collection_user_details_read_only( |
|
|
|
|
|
collection_user, |
|
|
|
|
|
*users_org_type.get(&collection_user.user_uuid).unwrap_or(&(UserOrgType::User as i32)), |
|
|
|
|
|
) |
|
|
|
|
|
.to_json() |
|
|
|
|
|
}) |
|
|
.collect(); |
|
|
.collect(); |
|
|
|
|
|
|
|
|
// get the group details for the given collection
|
|
|
// get the group details for the given collection
|
|
@ -645,12 +666,24 @@ async fn get_org_collection_detail( |
|
|
Vec::with_capacity(0) |
|
|
Vec::with_capacity(0) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Generate a HashMap to get the correct UserOrgType per user to determine the manage permission
|
|
|
|
|
|
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
|
|
|
|
|
let users_org_type: HashMap<String, i32> = UserOrganization::find_confirmed_by_org(org_id, &mut conn) |
|
|
|
|
|
.await |
|
|
|
|
|
.into_iter() |
|
|
|
|
|
.map(|uo| (uo.uuid, uo.atype)) |
|
|
|
|
|
.collect(); |
|
|
|
|
|
|
|
|
let users: Vec<Value> = |
|
|
let users: Vec<Value> = |
|
|
CollectionUser::find_by_collection_swap_user_uuid_with_org_user_uuid(&collection.uuid, &mut conn) |
|
|
CollectionUser::find_by_collection_swap_user_uuid_with_org_user_uuid(&collection.uuid, &mut conn) |
|
|
.await |
|
|
.await |
|
|
.iter() |
|
|
.iter() |
|
|
.map(|collection_user| { |
|
|
.map(|collection_user| { |
|
|
SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json() |
|
|
SelectionReadOnly::to_collection_user_details_read_only( |
|
|
|
|
|
collection_user, |
|
|
|
|
|
*users_org_type.get(&collection_user.user_uuid).unwrap_or(&(UserOrgType::User as i32)), |
|
|
|
|
|
) |
|
|
|
|
|
.to_json() |
|
|
}) |
|
|
}) |
|
|
.collect(); |
|
|
.collect(); |
|
|
|
|
|
|
|
@ -830,13 +863,19 @@ struct InviteData { |
|
|
collections: Option<Vec<CollectionData>>, |
|
|
collections: Option<Vec<CollectionData>>, |
|
|
#[serde(default)] |
|
|
#[serde(default)] |
|
|
access_all: bool, |
|
|
access_all: bool, |
|
|
|
|
|
#[serde(default)] |
|
|
|
|
|
permissions: HashMap<String, Value>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[post("/organizations/<org_id>/users/invite", data = "<data>")] |
|
|
#[post("/organizations/<org_id>/users/invite", data = "<data>")] |
|
|
async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { |
|
|
async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { |
|
|
let data: InviteData = data.into_inner(); |
|
|
let mut data: InviteData = data.into_inner(); |
|
|
|
|
|
|
|
|
let new_type = match UserOrgType::from_str(&data.r#type.into_string()) { |
|
|
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
|
|
|
|
|
|
// The from_str() will convert the custom role type into a manager role type
|
|
|
|
|
|
let raw_type = &data.r#type.into_string(); |
|
|
|
|
|
// UserOrgType::from_str will convert custom (4) to manager (3)
|
|
|
|
|
|
let new_type = match UserOrgType::from_str(raw_type) { |
|
|
Some(new_type) => new_type as i32, |
|
|
Some(new_type) => new_type as i32, |
|
|
None => err!("Invalid type"), |
|
|
None => err!("Invalid type"), |
|
|
}; |
|
|
}; |
|
@ -845,6 +884,18 @@ async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders |
|
|
err!("Only Owners can invite Managers, Admins or Owners") |
|
|
err!("Only Owners can invite Managers, Admins or Owners") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
|
|
|
|
|
|
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
|
|
|
|
|
|
// If the box is not checked, the user will still be a manager, but not with the access_all permission
|
|
|
|
|
|
if raw_type.eq("4") |
|
|
|
|
|
&& data.permissions.get("editAnyCollection") == Some(&json!(true)) |
|
|
|
|
|
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true)) |
|
|
|
|
|
&& data.permissions.get("createNewCollections") == Some(&json!(true)) |
|
|
|
|
|
{ |
|
|
|
|
|
data.access_all = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let mut user_created: bool = false; |
|
|
for email in data.emails.iter() { |
|
|
for email in data.emails.iter() { |
|
|
let mut user_org_status = UserOrgStatus::Invited as i32; |
|
|
let mut user_org_status = UserOrgStatus::Invited as i32; |
|
|
let user = match User::find_by_mail(email, &mut conn).await { |
|
|
let user = match User::find_by_mail(email, &mut conn).await { |
|
@ -858,13 +909,13 @@ async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if !CONFIG.mail_enabled() { |
|
|
if !CONFIG.mail_enabled() { |
|
|
let invitation = Invitation::new(email); |
|
|
Invitation::new(email).save(&mut conn).await?; |
|
|
invitation.save(&mut conn).await?; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let mut user = User::new(email.clone()); |
|
|
let mut new_user = User::new(email.clone()); |
|
|
user.save(&mut conn).await?; |
|
|
new_user.save(&mut conn).await?; |
|
|
user |
|
|
user_created = true; |
|
|
|
|
|
new_user |
|
|
} |
|
|
} |
|
|
Some(user) => { |
|
|
Some(user) => { |
|
|
if UserOrganization::find_by_user_and_org(&user.uuid, org_id, &mut conn).await.is_some() { |
|
|
if UserOrganization::find_by_user_and_org(&user.uuid, org_id, &mut conn).await.is_some() { |
|
@ -879,11 +930,49 @@ async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let mut new_user = UserOrganization::new(user.uuid.clone(), String::from(org_id)); |
|
|
let mut new_member = UserOrganization::new(user.uuid.clone(), String::from(org_id)); |
|
|
let access_all = data.access_all; |
|
|
let access_all = data.access_all; |
|
|
new_user.access_all = access_all; |
|
|
new_member.access_all = access_all; |
|
|
new_user.atype = new_type; |
|
|
new_member.atype = new_type; |
|
|
new_user.status = user_org_status; |
|
|
new_member.status = user_org_status; |
|
|
|
|
|
new_member.save(&mut conn).await?; |
|
|
|
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() { |
|
|
|
|
|
let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { |
|
|
|
|
|
Some(org) => org.name, |
|
|
|
|
|
None => err!("Error looking up organization"), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if let Err(e) = mail::send_invite( |
|
|
|
|
|
&user, |
|
|
|
|
|
Some(String::from(org_id)), |
|
|
|
|
|
Some(new_member.uuid.clone()), |
|
|
|
|
|
&org_name, |
|
|
|
|
|
Some(headers.user.email.clone()), |
|
|
|
|
|
) |
|
|
|
|
|
.await |
|
|
|
|
|
{ |
|
|
|
|
|
// Upon error delete the user, invite and org member records when needed
|
|
|
|
|
|
if user_created { |
|
|
|
|
|
user.delete(&mut conn).await?; |
|
|
|
|
|
} else { |
|
|
|
|
|
new_member.delete(&mut conn).await?; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
err!(format!("Error sending invite: {e:?} ")); |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log_event( |
|
|
|
|
|
EventType::OrganizationUserInvited as i32, |
|
|
|
|
|
&new_member.uuid.clone(), |
|
|
|
|
|
org_id, |
|
|
|
|
|
&headers.user.uuid, |
|
|
|
|
|
headers.device.atype, |
|
|
|
|
|
&headers.ip.ip, |
|
|
|
|
|
&mut conn, |
|
|
|
|
|
) |
|
|
|
|
|
.await; |
|
|
|
|
|
|
|
|
// If no accessAll, add the collections received
|
|
|
// If no accessAll, add the collections received
|
|
|
if !access_all { |
|
|
if !access_all { |
|
@ -904,39 +993,10 @@ async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
new_user.save(&mut conn).await?; |
|
|
|
|
|
|
|
|
|
|
|
for group in data.groups.iter() { |
|
|
for group in data.groups.iter() { |
|
|
let mut group_entry = GroupUser::new(String::from(group), user.uuid.clone()); |
|
|
let mut group_entry = GroupUser::new(String::from(group), new_member.uuid.clone()); |
|
|
group_entry.save(&mut conn).await?; |
|
|
group_entry.save(&mut conn).await?; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
log_event( |
|
|
|
|
|
EventType::OrganizationUserInvited as i32, |
|
|
|
|
|
&new_user.uuid, |
|
|
|
|
|
org_id, |
|
|
|
|
|
&headers.user.uuid, |
|
|
|
|
|
headers.device.atype, |
|
|
|
|
|
&headers.ip.ip, |
|
|
|
|
|
&mut conn, |
|
|
|
|
|
) |
|
|
|
|
|
.await; |
|
|
|
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() { |
|
|
|
|
|
let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { |
|
|
|
|
|
Some(org) => org.name, |
|
|
|
|
|
None => err!("Error looking up organization"), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
mail::send_invite( |
|
|
|
|
|
&user, |
|
|
|
|
|
Some(String::from(org_id)), |
|
|
|
|
|
Some(new_user.uuid), |
|
|
|
|
|
&org_name, |
|
|
|
|
|
Some(headers.user.email.clone()), |
|
|
|
|
|
) |
|
|
|
|
|
.await?; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Ok(()) |
|
|
Ok(()) |
|
@ -1014,7 +1074,7 @@ async fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, co |
|
|
let invitation = Invitation::new(&user.email); |
|
|
let invitation = Invitation::new(&user.email); |
|
|
invitation.save(conn).await?; |
|
|
invitation.save(conn).await?; |
|
|
} else { |
|
|
} else { |
|
|
let _ = Invitation::take(&user.email, conn).await; |
|
|
Invitation::take(&user.email, conn).await; |
|
|
let mut user_org = user_org; |
|
|
let mut user_org = user_org; |
|
|
user_org.status = UserOrgStatus::Accepted as i32; |
|
|
user_org.status = UserOrgStatus::Accepted as i32; |
|
|
user_org.save(conn).await?; |
|
|
user_org.save(conn).await?; |
|
@ -1254,7 +1314,21 @@ async fn _confirm_invite( |
|
|
save_result |
|
|
save_result |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[get("/organizations/<org_id>/users/<org_user_id>?<data..>")] |
|
|
#[get("/organizations/<org_id>/users/mini-details", rank = 1)] |
|
|
|
|
|
async fn get_org_user_mini_details(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbConn) -> Json<Value> { |
|
|
|
|
|
let mut users_json = Vec::new(); |
|
|
|
|
|
for u in UserOrganization::find_by_org(org_id, &mut conn).await { |
|
|
|
|
|
users_json.push(u.to_json_mini_details(&mut conn).await); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Json(json!({ |
|
|
|
|
|
"data": users_json, |
|
|
|
|
|
"object": "list", |
|
|
|
|
|
"continuationToken": null, |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[get("/organizations/<org_id>/users/<org_user_id>?<data..>", rank = 2)] |
|
|
async fn get_user( |
|
|
async fn get_user( |
|
|
org_id: &str, |
|
|
org_id: &str, |
|
|
org_user_id: &str, |
|
|
org_user_id: &str, |
|
@ -1282,6 +1356,8 @@ struct EditUserData { |
|
|
groups: Option<Vec<String>>, |
|
|
groups: Option<Vec<String>>, |
|
|
#[serde(default)] |
|
|
#[serde(default)] |
|
|
access_all: bool, |
|
|
access_all: bool, |
|
|
|
|
|
#[serde(default)] |
|
|
|
|
|
permissions: HashMap<String, Value>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] |
|
|
#[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] |
|
@ -1303,14 +1379,30 @@ async fn edit_user( |
|
|
headers: AdminHeaders, |
|
|
headers: AdminHeaders, |
|
|
mut conn: DbConn, |
|
|
mut conn: DbConn, |
|
|
) -> EmptyResult { |
|
|
) -> EmptyResult { |
|
|
let data: EditUserData = data.into_inner(); |
|
|
let mut data: EditUserData = data.into_inner(); |
|
|
|
|
|
|
|
|
let Some(new_type) = UserOrgType::from_str(&data.r#type.into_string()) else { |
|
|
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
|
|
|
|
|
|
// The from_str() will convert the custom role type into a manager role type
|
|
|
|
|
|
let raw_type = &data.r#type.into_string(); |
|
|
|
|
|
// UserOrgType::from_str will convert custom (4) to manager (3)
|
|
|
|
|
|
let Some(new_type) = UserOrgType::from_str(raw_type) else { |
|
|
err!("Invalid type") |
|
|
err!("Invalid type") |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let Some(mut user_to_edit) = UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &mut conn).await else { |
|
|
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
|
|
|
err!("The specified user isn't member of the organization") |
|
|
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
|
|
|
|
|
|
// If the box is not checked, the user will still be a manager, but not with the access_all permission
|
|
|
|
|
|
if raw_type.eq("4") |
|
|
|
|
|
&& data.permissions.get("editAnyCollection") == Some(&json!(true)) |
|
|
|
|
|
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true)) |
|
|
|
|
|
&& data.permissions.get("createNewCollections") == Some(&json!(true)) |
|
|
|
|
|
{ |
|
|
|
|
|
data.access_all = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let mut user_to_edit = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &mut conn).await { |
|
|
|
|
|
Some(user) => user, |
|
|
|
|
|
None => err!("The specified user isn't member of the organization"), |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
if new_type != user_to_edit.atype |
|
|
if new_type != user_to_edit.atype |
|
@ -1901,6 +1993,12 @@ fn get_plans_tax_rates(_headers: Headers) -> Json<Value> { |
|
|
Json(_empty_data_json()) |
|
|
Json(_empty_data_json()) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[get("/organizations/<_org_id>/billing/metadata")] |
|
|
|
|
|
fn get_billing_metadata(_org_id: &str, _headers: Headers) -> Json<Value> { |
|
|
|
|
|
// Prevent a 404 error, which also causes Javascript errors.
|
|
|
|
|
|
Json(_empty_data_json()) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
fn _empty_data_json() -> Value { |
|
|
fn _empty_data_json() -> Value { |
|
|
json!({ |
|
|
json!({ |
|
|
"object": "list", |
|
|
"object": "list", |
|
@ -1938,6 +2036,9 @@ struct OrgImportData { |
|
|
users: Vec<OrgImportUserData>, |
|
|
users: Vec<OrgImportUserData>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// This function seems to be deprected
|
|
|
|
|
|
/// It is only used with older directory connectors
|
|
|
|
|
|
/// TODO: Cleanup Tech debt
|
|
|
#[post("/organizations/<org_id>/import", data = "<data>")] |
|
|
#[post("/organizations/<org_id>/import", data = "<data>")] |
|
|
async fn import(org_id: &str, data: Json<OrgImportData>, headers: Headers, mut conn: DbConn) -> EmptyResult { |
|
|
async fn import(org_id: &str, data: Json<OrgImportData>, headers: Headers, mut conn: DbConn) -> EmptyResult { |
|
|
let data = data.into_inner(); |
|
|
let data = data.into_inner(); |
|
@ -1981,23 +2082,10 @@ async fn import(org_id: &str, data: Json<OrgImportData>, headers: Headers, mut c |
|
|
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites
|
|
|
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let mut new_org_user = UserOrganization::new(user.uuid.clone(), String::from(org_id)); |
|
|
let mut new_member = UserOrganization::new(user.uuid.clone(), String::from(org_id)); |
|
|
new_org_user.access_all = false; |
|
|
new_member.access_all = false; |
|
|
new_org_user.atype = UserOrgType::User as i32; |
|
|
new_member.atype = UserOrgType::User as i32; |
|
|
new_org_user.status = user_org_status; |
|
|
new_member.status = user_org_status; |
|
|
|
|
|
|
|
|
new_org_user.save(&mut conn).await?; |
|
|
|
|
|
|
|
|
|
|
|
log_event( |
|
|
|
|
|
EventType::OrganizationUserInvited as i32, |
|
|
|
|
|
&new_org_user.uuid, |
|
|
|
|
|
org_id, |
|
|
|
|
|
&headers.user.uuid, |
|
|
|
|
|
headers.device.atype, |
|
|
|
|
|
&headers.ip.ip, |
|
|
|
|
|
&mut conn, |
|
|
|
|
|
) |
|
|
|
|
|
.await; |
|
|
|
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() { |
|
|
if CONFIG.mail_enabled() { |
|
|
let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { |
|
|
let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { |
|
@ -2008,12 +2096,27 @@ async fn import(org_id: &str, data: Json<OrgImportData>, headers: Headers, mut c |
|
|
mail::send_invite( |
|
|
mail::send_invite( |
|
|
&user, |
|
|
&user, |
|
|
Some(String::from(org_id)), |
|
|
Some(String::from(org_id)), |
|
|
Some(new_org_user.uuid), |
|
|
Some(new_member.uuid.clone()), |
|
|
&org_name, |
|
|
&org_name, |
|
|
Some(headers.user.email.clone()), |
|
|
Some(headers.user.email.clone()), |
|
|
) |
|
|
) |
|
|
.await?; |
|
|
.await?; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Save the member after sending an email
|
|
|
|
|
|
// If sending fails the member will not be saved to the database, and will not result in the admin needing to reinvite the users manually
|
|
|
|
|
|
new_member.save(&mut conn).await?; |
|
|
|
|
|
|
|
|
|
|
|
log_event( |
|
|
|
|
|
EventType::OrganizationUserInvited as i32, |
|
|
|
|
|
&new_member.uuid, |
|
|
|
|
|
org_id, |
|
|
|
|
|
&headers.user.uuid, |
|
|
|
|
|
headers.device.atype, |
|
|
|
|
|
&headers.ip.ip, |
|
|
|
|
|
&mut conn, |
|
|
|
|
|
) |
|
|
|
|
|
.await; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -2299,6 +2402,11 @@ async fn get_groups(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbCon |
|
|
}))) |
|
|
}))) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[get("/organizations/<org_id>/groups/details", rank = 1)] |
|
|
|
|
|
async fn get_groups_details(org_id: &str, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { |
|
|
|
|
|
get_groups(org_id, headers, conn).await |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
#[derive(Deserialize)] |
|
|
#[serde(rename_all = "camelCase")] |
|
|
#[serde(rename_all = "camelCase")] |
|
|
struct GroupRequest { |
|
|
struct GroupRequest { |
|
@ -2331,6 +2439,7 @@ struct SelectionReadOnly { |
|
|
id: String, |
|
|
id: String, |
|
|
read_only: bool, |
|
|
read_only: bool, |
|
|
hide_passwords: bool, |
|
|
hide_passwords: bool, |
|
|
|
|
|
manage: bool, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
impl SelectionReadOnly { |
|
|
impl SelectionReadOnly { |
|
@ -2339,18 +2448,31 @@ impl SelectionReadOnly { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> SelectionReadOnly { |
|
|
pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> SelectionReadOnly { |
|
|
|
|
|
// If both read_only and hide_passwords are false, then manage should be true
|
|
|
|
|
|
// You can't have an entry with read_only and manage, or hide_passwords and manage
|
|
|
|
|
|
// Or an entry with everything to false
|
|
|
SelectionReadOnly { |
|
|
SelectionReadOnly { |
|
|
id: collection_group.groups_uuid.clone(), |
|
|
id: collection_group.groups_uuid.clone(), |
|
|
read_only: collection_group.read_only, |
|
|
read_only: collection_group.read_only, |
|
|
hide_passwords: collection_group.hide_passwords, |
|
|
hide_passwords: collection_group.hide_passwords, |
|
|
|
|
|
manage: !collection_group.read_only && !collection_group.hide_passwords, |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn to_collection_user_details_read_only(collection_user: &CollectionUser) -> SelectionReadOnly { |
|
|
pub fn to_collection_user_details_read_only( |
|
|
|
|
|
collection_user: &CollectionUser, |
|
|
|
|
|
user_org_type: i32, |
|
|
|
|
|
) -> SelectionReadOnly { |
|
|
|
|
|
// Vaultwarden allows manage access for Admins and Owners by default
|
|
|
|
|
|
// For managers (Or custom role) it depends if they have read_ony or hide_passwords set to true or not
|
|
|
SelectionReadOnly { |
|
|
SelectionReadOnly { |
|
|
id: collection_user.user_uuid.clone(), |
|
|
id: collection_user.user_uuid.clone(), |
|
|
read_only: collection_user.read_only, |
|
|
read_only: collection_user.read_only, |
|
|
hide_passwords: collection_user.hide_passwords, |
|
|
hide_passwords: collection_user.hide_passwords, |
|
|
|
|
|
manage: user_org_type >= UserOrgType::Admin |
|
|
|
|
|
|| (user_org_type == UserOrgType::Manager |
|
|
|
|
|
&& !collection_user.read_only |
|
|
|
|
|
&& !collection_user.hide_passwords), |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -2534,7 +2656,7 @@ async fn bulk_delete_groups( |
|
|
Ok(()) |
|
|
Ok(()) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[get("/organizations/<org_id>/groups/<group_id>")] |
|
|
#[get("/organizations/<org_id>/groups/<group_id>", rank = 2)] |
|
|
async fn get_group(org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { |
|
|
async fn get_group(org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { |
|
|
if !CONFIG.org_groups_enabled() { |
|
|
if !CONFIG.org_groups_enabled() { |
|
|
err!("Group support is disabled"); |
|
|
err!("Group support is disabled"); |
|
@ -2904,7 +3026,7 @@ async fn put_reset_password_enrollment( |
|
|
if reset_request.reset_password_key.is_none() |
|
|
if reset_request.reset_password_key.is_none() |
|
|
&& OrgPolicy::org_is_reset_password_auto_enroll(org_id, &mut conn).await |
|
|
&& OrgPolicy::org_is_reset_password_auto_enroll(org_id, &mut conn).await |
|
|
{ |
|
|
{ |
|
|
err!("Reset password can't be withdrawed due to an enterprise policy"); |
|
|
err!("Reset password can't be withdrawn due to an enterprise policy"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if reset_request.reset_password_key.is_some() { |
|
|
if reset_request.reset_password_key.is_some() { |
|
|