mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-02-05 02:38:09 -05:00
add bulk-access endpoint for collections (#5542)
This commit is contained in:
parent
d2b36642a6
commit
3b6bccde97
@ -38,6 +38,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
post_organization_collections,
|
post_organization_collections,
|
||||||
delete_organization_collection_member,
|
delete_organization_collection_member,
|
||||||
post_organization_collection_delete_member,
|
post_organization_collection_delete_member,
|
||||||
|
post_bulk_access_collections,
|
||||||
post_organization_collection_update,
|
post_organization_collection_update,
|
||||||
put_organization_collection_update,
|
put_organization_collection_update,
|
||||||
delete_organization_collection,
|
delete_organization_collection,
|
||||||
@ -129,17 +130,17 @@ struct OrganizationUpdateData {
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct NewCollectionData {
|
struct FullCollectionData {
|
||||||
name: String,
|
name: String,
|
||||||
groups: Vec<NewCollectionGroupData>,
|
groups: Vec<CollectionGroupData>,
|
||||||
users: Vec<NewCollectionMemberData>,
|
users: Vec<CollectionMembershipData>,
|
||||||
id: Option<CollectionId>,
|
id: Option<CollectionId>,
|
||||||
external_id: Option<String>,
|
external_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct NewCollectionGroupData {
|
struct CollectionGroupData {
|
||||||
hide_passwords: bool,
|
hide_passwords: bool,
|
||||||
id: GroupId,
|
id: GroupId,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
@ -148,7 +149,7 @@ struct NewCollectionGroupData {
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct NewCollectionMemberData {
|
struct CollectionMembershipData {
|
||||||
hide_passwords: bool,
|
hide_passwords: bool,
|
||||||
id: MembershipId,
|
id: MembershipId,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
@ -429,13 +430,13 @@ async fn _get_org_collections(org_id: &OrganizationId, conn: &mut DbConn) -> Val
|
|||||||
async fn post_organization_collections(
|
async fn post_organization_collections(
|
||||||
org_id: OrganizationId,
|
org_id: OrganizationId,
|
||||||
headers: ManagerHeadersLoose,
|
headers: ManagerHeadersLoose,
|
||||||
data: Json<NewCollectionData>,
|
data: Json<FullCollectionData>,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
if org_id != headers.membership.org_uuid {
|
if org_id != headers.membership.org_uuid {
|
||||||
err!("Organization not found", "Organization id's do not match");
|
err!("Organization not found", "Organization id's do not match");
|
||||||
}
|
}
|
||||||
let data: NewCollectionData = data.into_inner();
|
let data: FullCollectionData = data.into_inner();
|
||||||
|
|
||||||
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
|
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
|
||||||
err!("Can't find organization details")
|
err!("Can't find organization details")
|
||||||
@ -488,29 +489,104 @@ async fn post_organization_collections(
|
|||||||
Ok(Json(collection.to_json_details(&headers.membership.user_uuid, None, &mut conn).await))
|
Ok(Json(collection.to_json_details(&headers.membership.user_uuid, None, &mut conn).await))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct BulkCollectionAccessData {
|
||||||
|
collection_ids: Vec<CollectionId>,
|
||||||
|
groups: Vec<CollectionGroupData>,
|
||||||
|
users: Vec<CollectionMembershipData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<org_id>/collections/bulk-access", data = "<data>", rank = 1)]
|
||||||
|
async fn post_bulk_access_collections(
|
||||||
|
org_id: OrganizationId,
|
||||||
|
headers: ManagerHeadersLoose,
|
||||||
|
data: Json<BulkCollectionAccessData>,
|
||||||
|
mut conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
if org_id != headers.membership.org_uuid {
|
||||||
|
err!("Organization not found", "Organization id's do not match");
|
||||||
|
}
|
||||||
|
let data: BulkCollectionAccessData = data.into_inner();
|
||||||
|
|
||||||
|
if Organization::find_by_uuid(&org_id, &mut conn).await.is_none() {
|
||||||
|
err!("Can't find organization details")
|
||||||
|
};
|
||||||
|
|
||||||
|
for col_id in data.collection_ids {
|
||||||
|
let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
|
||||||
|
err!("Collection not found")
|
||||||
|
};
|
||||||
|
|
||||||
|
// update collection modification date
|
||||||
|
collection.save(&mut conn).await?;
|
||||||
|
|
||||||
|
log_event(
|
||||||
|
EventType::CollectionUpdated as i32,
|
||||||
|
&collection.uuid,
|
||||||
|
&org_id,
|
||||||
|
&headers.user.uuid,
|
||||||
|
headers.device.atype,
|
||||||
|
&headers.ip.ip,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
CollectionGroup::delete_all_by_collection(&col_id, &mut conn).await?;
|
||||||
|
for group in &data.groups {
|
||||||
|
CollectionGroup::new(col_id.clone(), group.id.clone(), group.read_only, group.hide_passwords, group.manage)
|
||||||
|
.save(&mut conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionUser::delete_all_by_collection(&col_id, &mut conn).await?;
|
||||||
|
for user in &data.users {
|
||||||
|
let Some(member) = Membership::find_by_uuid_and_org(&user.id, &org_id, &mut conn).await else {
|
||||||
|
err!("User is not part of organization")
|
||||||
|
};
|
||||||
|
|
||||||
|
if member.access_all {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionUser::save(
|
||||||
|
&member.user_uuid,
|
||||||
|
&col_id,
|
||||||
|
user.read_only,
|
||||||
|
user.hide_passwords,
|
||||||
|
user.manage,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[put("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
|
#[put("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
|
||||||
async fn put_organization_collection_update(
|
async fn put_organization_collection_update(
|
||||||
org_id: OrganizationId,
|
org_id: OrganizationId,
|
||||||
col_id: CollectionId,
|
col_id: CollectionId,
|
||||||
headers: ManagerHeaders,
|
headers: ManagerHeaders,
|
||||||
data: Json<NewCollectionData>,
|
data: Json<FullCollectionData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
post_organization_collection_update(org_id, col_id, headers, data, conn).await
|
post_organization_collection_update(org_id, col_id, headers, data, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
|
#[post("/organizations/<org_id>/collections/<col_id>", data = "<data>", rank = 2)]
|
||||||
async fn post_organization_collection_update(
|
async fn post_organization_collection_update(
|
||||||
org_id: OrganizationId,
|
org_id: OrganizationId,
|
||||||
col_id: CollectionId,
|
col_id: CollectionId,
|
||||||
headers: ManagerHeaders,
|
headers: ManagerHeaders,
|
||||||
data: Json<NewCollectionData>,
|
data: Json<FullCollectionData>,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
if org_id != headers.org_id {
|
if org_id != headers.org_id {
|
||||||
err!("Organization not found", "Organization id's do not match");
|
err!("Organization not found", "Organization id's do not match");
|
||||||
}
|
}
|
||||||
let data: NewCollectionData = data.into_inner();
|
let data: FullCollectionData = data.into_inner();
|
||||||
|
|
||||||
if Organization::find_by_uuid(&org_id, &mut conn).await.is_none() {
|
if Organization::find_by_uuid(&org_id, &mut conn).await.is_none() {
|
||||||
err!("Can't find organization details")
|
err!("Can't find organization details")
|
||||||
@ -781,7 +857,7 @@ async fn get_collection_users(
|
|||||||
async fn put_collection_users(
|
async fn put_collection_users(
|
||||||
org_id: OrganizationId,
|
org_id: OrganizationId,
|
||||||
col_id: CollectionId,
|
col_id: CollectionId,
|
||||||
data: Json<Vec<MembershipData>>,
|
data: Json<Vec<CollectionMembershipData>>,
|
||||||
headers: ManagerHeaders,
|
headers: ManagerHeaders,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
@ -913,24 +989,6 @@ async fn post_org_keys(
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct CollectionData {
|
|
||||||
id: CollectionId,
|
|
||||||
read_only: bool,
|
|
||||||
hide_passwords: bool,
|
|
||||||
manage: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MembershipData {
|
|
||||||
id: MembershipId,
|
|
||||||
read_only: bool,
|
|
||||||
hide_passwords: bool,
|
|
||||||
manage: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct InviteData {
|
struct InviteData {
|
||||||
@ -1754,7 +1812,7 @@ use super::ciphers::CipherData;
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ImportData {
|
struct ImportData {
|
||||||
ciphers: Vec<CipherData>,
|
ciphers: Vec<CipherData>,
|
||||||
collections: Vec<NewCollectionData>,
|
collections: Vec<FullCollectionData>,
|
||||||
collection_relationships: Vec<RelationsData>,
|
collection_relationships: Vec<RelationsData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2549,7 +2607,7 @@ struct GroupRequest {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
access_all: bool,
|
access_all: bool,
|
||||||
external_id: Option<String>,
|
external_id: Option<String>,
|
||||||
collections: Vec<SelectedCollection>,
|
collections: Vec<CollectionData>,
|
||||||
users: Vec<MembershipId>,
|
users: Vec<MembershipId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2570,14 +2628,14 @@ impl GroupRequest {
|
|||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct SelectedCollection {
|
struct CollectionData {
|
||||||
id: CollectionId,
|
id: CollectionId,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
hide_passwords: bool,
|
hide_passwords: bool,
|
||||||
manage: bool,
|
manage: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedCollection {
|
impl CollectionData {
|
||||||
pub fn to_collection_group(&self, groups_uuid: GroupId) -> CollectionGroup {
|
pub fn to_collection_group(&self, groups_uuid: GroupId) -> CollectionGroup {
|
||||||
CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords, self.manage)
|
CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords, self.manage)
|
||||||
}
|
}
|
||||||
@ -2660,7 +2718,7 @@ async fn put_group(
|
|||||||
|
|
||||||
async fn add_update_group(
|
async fn add_update_group(
|
||||||
mut group: Group,
|
mut group: Group,
|
||||||
collections: Vec<SelectedCollection>,
|
collections: Vec<CollectionData>,
|
||||||
members: Vec<MembershipId>,
|
members: Vec<MembershipId>,
|
||||||
org_id: OrganizationId,
|
org_id: OrganizationId,
|
||||||
headers: &AdminHeaders,
|
headers: &AdminHeaders,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user