Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-26 05:50:29 +01:00
Add Organization bulk actions support
For user management within the organization view you are able to select multiple users to re-invite, confirm or delete them. These actions were not working which this PR fixes by adding support for these endpoints. This will make it easier to confirm and delete multiple users at once instead of having to do this one-by-one.
Dieser Commit ist enthalten in:
Ursprung
8c10de3edd
Commit
f36bd72a7f
1 geänderte Dateien mit 165 neuen und 17 gelöschten Zeilen
|
@ -35,12 +35,15 @@ pub fn routes() -> Vec<Route> {
|
||||||
get_org_users,
|
get_org_users,
|
||||||
send_invite,
|
send_invite,
|
||||||
reinvite_user,
|
reinvite_user,
|
||||||
|
bulk_reinvite_user,
|
||||||
confirm_invite,
|
confirm_invite,
|
||||||
|
bulk_confirm_invite,
|
||||||
accept_invite,
|
accept_invite,
|
||||||
get_user,
|
get_user,
|
||||||
edit_user,
|
edit_user,
|
||||||
put_organization_user,
|
put_organization_user,
|
||||||
delete_user,
|
delete_user,
|
||||||
|
bulk_delete_user,
|
||||||
post_delete_user,
|
post_delete_user,
|
||||||
post_org_import,
|
post_org_import,
|
||||||
list_policies,
|
list_policies,
|
||||||
|
@ -52,6 +55,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
get_plans_tax_rates,
|
get_plans_tax_rates,
|
||||||
import,
|
import,
|
||||||
post_org_keys,
|
post_org_keys,
|
||||||
|
bulk_public_keys,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +91,12 @@ struct OrgKeyData {
|
||||||
PublicKey: String,
|
PublicKey: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgBulkIds {
|
||||||
|
Ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/organizations", data = "<data>")]
|
#[post("/organizations", data = "<data>")]
|
||||||
fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult {
|
fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult {
|
||||||
if !CONFIG.is_org_creation_allowed(&headers.user.email) {
|
if !CONFIG.is_org_creation_allowed(&headers.user.email) {
|
||||||
|
@ -615,8 +625,44 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<org_id>/users/reinvite", data = "<data>")]
|
||||||
|
fn bulk_reinvite_user(
|
||||||
|
org_id: String,
|
||||||
|
data: JsonUpcase<OrgBulkIds>,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> Json<Value> {
|
||||||
|
let data: OrgBulkIds = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
for org_user_id in data.Ids {
|
||||||
|
let err_msg = match _reinvite_user(&org_id, &org_user_id, &headers.user.email, &conn) {
|
||||||
|
Ok(_) => String::from(""),
|
||||||
|
Err(e) => format!("{:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "OrganizationBulkConfirmResponseModel",
|
||||||
|
"Id": org_user_id,
|
||||||
|
"Error": err_msg
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/users/<user_org>/reinvite")]
|
#[post("/organizations/<org_id>/users/<user_org>/reinvite")]
|
||||||
fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
|
fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
|
||||||
|
_reinvite_user(&org_id, &user_org, &headers.user.email, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &DbConn) -> EmptyResult {
|
||||||
if !CONFIG.invitations_allowed() {
|
if !CONFIG.invitations_allowed() {
|
||||||
err!("Invitations are not allowed.")
|
err!("Invitations are not allowed.")
|
||||||
}
|
}
|
||||||
|
@ -625,7 +671,7 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn:
|
||||||
err!("SMTP is not configured.")
|
err!("SMTP is not configured.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_org = match UserOrganization::find_by_uuid(&user_org, &conn) {
|
let user_org = match UserOrganization::find_by_uuid(user_org, conn) {
|
||||||
Some(user_org) => user_org,
|
Some(user_org) => user_org,
|
||||||
None => err!("The user hasn't been invited to the organization."),
|
None => err!("The user hasn't been invited to the organization."),
|
||||||
};
|
};
|
||||||
|
@ -634,12 +680,12 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn:
|
||||||
err!("The user is already accepted or confirmed to the organization")
|
err!("The user is already accepted or confirmed to the organization")
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = match User::find_by_uuid(&user_org.user_uuid, &conn) {
|
let user = match User::find_by_uuid(&user_org.user_uuid, conn) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("User not found."),
|
None => err!("User not found."),
|
||||||
};
|
};
|
||||||
|
|
||||||
let org_name = match Organization::find_by_uuid(&org_id, &conn) {
|
let org_name = match Organization::find_by_uuid(org_id, conn) {
|
||||||
Some(org) => org.name,
|
Some(org) => org.name,
|
||||||
None => err!("Error looking up organization."),
|
None => err!("Error looking up organization."),
|
||||||
};
|
};
|
||||||
|
@ -648,14 +694,14 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn:
|
||||||
mail::send_invite(
|
mail::send_invite(
|
||||||
&user.email,
|
&user.email,
|
||||||
&user.uuid,
|
&user.uuid,
|
||||||
Some(org_id),
|
Some(org_id.to_string()),
|
||||||
Some(user_org.uuid),
|
Some(user_org.uuid),
|
||||||
&org_name,
|
&org_name,
|
||||||
Some(headers.user.email),
|
Some(invited_by_email.to_string()),
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let invitation = Invitation::new(user.email);
|
let invitation = Invitation::new(user.email);
|
||||||
invitation.save(&conn)?;
|
invitation.save(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -728,6 +774,40 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<org_id>/users/confirm", data = "<data>")]
|
||||||
|
fn bulk_confirm_invite(org_id: String, data: JsonUpcase<Value>, headers: AdminHeaders, conn: DbConn) -> Json<Value> {
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
match data["Keys"].as_array() {
|
||||||
|
Some(keys) => {
|
||||||
|
for invite in keys {
|
||||||
|
let org_user_id = invite["Id"].as_str().unwrap_or_default();
|
||||||
|
let user_key = invite["Key"].as_str().unwrap_or_default();
|
||||||
|
let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &conn) {
|
||||||
|
Ok(_) => String::from(""),
|
||||||
|
Err(e) => format!("{:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "OrganizationBulkConfirmResponseModel",
|
||||||
|
"Id": org_user_id,
|
||||||
|
"Error": err_msg
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => error!("No keys to confirm"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")]
|
#[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")]
|
||||||
fn confirm_invite(
|
fn confirm_invite(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
|
@ -737,8 +817,16 @@ fn confirm_invite(
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let data = data.into_inner().data;
|
let data = data.into_inner().data;
|
||||||
|
let user_key = data["Key"].as_str().unwrap_or_default();
|
||||||
|
_confirm_invite(&org_id, &org_user_id, user_key, &headers, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
let mut user_to_confirm = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) {
|
fn _confirm_invite(org_id: &str, org_user_id: &str, key: &str, headers: &AdminHeaders, conn: &DbConn) -> EmptyResult {
|
||||||
|
if key.is_empty() || org_user_id.is_empty() {
|
||||||
|
err!("Key or UserId is not set, unable to process request");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut user_to_confirm = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("The specified user isn't a member of the organization"),
|
None => err!("The specified user isn't a member of the organization"),
|
||||||
};
|
};
|
||||||
|
@ -752,24 +840,21 @@ fn confirm_invite(
|
||||||
}
|
}
|
||||||
|
|
||||||
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
||||||
user_to_confirm.akey = match data["Key"].as_str() {
|
user_to_confirm.akey = key.to_string();
|
||||||
Some(key) => key.to_string(),
|
|
||||||
None => err!("Invalid key provided"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
let org_name = match Organization::find_by_uuid(&org_id, &conn) {
|
let org_name = match Organization::find_by_uuid(org_id, conn) {
|
||||||
Some(org) => org.name,
|
Some(org) => org.name,
|
||||||
None => err!("Error looking up organization."),
|
None => err!("Error looking up organization."),
|
||||||
};
|
};
|
||||||
let address = match User::find_by_uuid(&user_to_confirm.user_uuid, &conn) {
|
let address = match User::find_by_uuid(&user_to_confirm.user_uuid, conn) {
|
||||||
Some(user) => user.email,
|
Some(user) => user.email,
|
||||||
None => err!("Error looking up user."),
|
None => err!("Error looking up user."),
|
||||||
};
|
};
|
||||||
mail::send_invite_confirmed(&address, &org_name)?;
|
mail::send_invite_confirmed(&address, &org_name)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
user_to_confirm.save(&conn)
|
user_to_confirm.save(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/users/<org_user_id>")]
|
#[get("/organizations/<org_id>/users/<org_user_id>")]
|
||||||
|
@ -870,9 +955,40 @@ fn edit_user(
|
||||||
user_to_edit.save(&conn)
|
user_to_edit.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[delete("/organizations/<org_id>/users", data = "<data>")]
|
||||||
|
fn bulk_delete_user(org_id: String, data: JsonUpcase<OrgBulkIds>, headers: AdminHeaders, conn: DbConn) -> Json<Value> {
|
||||||
|
let data: OrgBulkIds = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
for org_user_id in data.Ids {
|
||||||
|
let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &conn) {
|
||||||
|
Ok(_) => String::from(""),
|
||||||
|
Err(e) => format!("{:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "OrganizationBulkConfirmResponseModel",
|
||||||
|
"Id": org_user_id,
|
||||||
|
"Error": err_msg
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[delete("/organizations/<org_id>/users/<org_user_id>")]
|
#[delete("/organizations/<org_id>/users/<org_user_id>")]
|
||||||
fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
|
fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
|
||||||
let user_to_delete = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) {
|
_delete_user(&org_id, &org_user_id, &headers, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, conn: &DbConn) -> EmptyResult {
|
||||||
|
let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("User to delete isn't member of the organization"),
|
None => err!("User to delete isn't member of the organization"),
|
||||||
};
|
};
|
||||||
|
@ -883,14 +999,14 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn:
|
||||||
|
|
||||||
if user_to_delete.atype == UserOrgType::Owner {
|
if user_to_delete.atype == UserOrgType::Owner {
|
||||||
// Removing owner, check that there are at least another owner
|
// Removing owner, check that there are at least another owner
|
||||||
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
let num_owners = UserOrganization::find_by_org_and_type(org_id, UserOrgType::Owner as i32, conn).len();
|
||||||
|
|
||||||
if num_owners <= 1 {
|
if num_owners <= 1 {
|
||||||
err!("Can't delete the last owner")
|
err!("Can't delete the last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_to_delete.delete(&conn)
|
user_to_delete.delete(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/users/<org_user_id>/delete")]
|
#[post("/organizations/<org_id>/users/<org_user_id>/delete")]
|
||||||
|
@ -898,6 +1014,38 @@ fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders,
|
||||||
delete_user(org_id, org_user_id, headers, conn)
|
delete_user(org_id, org_user_id, headers, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<org_id>/users/public-keys", data = "<data>")]
|
||||||
|
fn bulk_public_keys(org_id: String, data: JsonUpcase<OrgBulkIds>, _headers: AdminHeaders, conn: DbConn) -> Json<Value> {
|
||||||
|
let data: OrgBulkIds = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
// Check all received UserOrg UUID's and find the matching User to retreive the public-key.
|
||||||
|
// If the user does not exists, just ignore it, and do not return any information regarding that UserOrg UUID.
|
||||||
|
// The web-vault will then ignore that user for the folowing steps.
|
||||||
|
for user_org_id in data.Ids {
|
||||||
|
match UserOrganization::find_by_uuid_and_org(&user_org_id, &org_id, &conn) {
|
||||||
|
Some(user_org) => match User::find_by_uuid(&user_org.user_uuid, &conn) {
|
||||||
|
Some(user) => bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "organizationUserPublicKeyResponseModel",
|
||||||
|
"Id": user_org_id,
|
||||||
|
"UserId": user.uuid,
|
||||||
|
"Key": user.public_key
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
None => debug!("User doesn't exist"),
|
||||||
|
},
|
||||||
|
None => debug!("UserOrg doesn't exist"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
use super::ciphers::update_cipher_from_data;
|
use super::ciphers::update_cipher_from_data;
|
||||||
use super::ciphers::CipherData;
|
use super::ciphers::CipherData;
|
||||||
|
|
||||||
|
|
Laden …
In neuem Issue referenzieren