mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 05:41:13 +00:00 
			
		
		
		
	Fix manager in web-vault v2024.6.2 for collections (#4860)
The web-vault v2024.6.2 we use needs some extra information to allow managers to actually be able to manage collections. The v2024.6.2 web-vault has somewhat of a mixture of the newer roles and older manager roles. To at least fix this for the web-vault we bundle these changes will make the manager able to manage. For future web-vaults we would need a lot more changes to be done to fix this in a better way though. Fixes #4844
This commit is contained in:
		
					parent
					
						
							
								339612c917
							
						
					
				
			
			
				commit
				
					
						9e26014b4d
					
				
			
		
					 4 changed files with 85 additions and 31 deletions
				
			
		|  | @ -509,7 +509,7 @@ async fn post_organization_collection_update( | |||
|         CollectionUser::save(&org_user.user_uuid, col_id, user.read_only, user.hide_passwords, &mut conn).await?; | ||||
|     } | ||||
| 
 | ||||
|     Ok(Json(collection.to_json())) | ||||
|     Ok(Json(collection.to_json_details(&headers.user.uuid, None, &mut conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[delete("/organizations/<org_id>/collections/<col_id>/user/<org_user_id>")] | ||||
|  | @ -2276,13 +2276,14 @@ async fn _restore_organization_user( | |||
| } | ||||
| 
 | ||||
| #[get("/organizations/<org_id>/groups")] | ||||
| async fn get_groups(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { | ||||
| async fn get_groups(org_id: &str, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { | ||||
|     let groups: Vec<Value> = if CONFIG.org_groups_enabled() { | ||||
|         // Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
 | ||||
|         let groups = Group::find_by_organization(org_id, &mut conn).await; | ||||
|         let mut groups_json = Vec::with_capacity(groups.len()); | ||||
| 
 | ||||
|         for g in groups { | ||||
|             groups_json.push(g.to_json_details(&mut conn).await) | ||||
|             groups_json.push(g.to_json_details(&headers.org_user.atype, &mut conn).await) | ||||
|         } | ||||
|         groups_json | ||||
|     } else { | ||||
|  | @ -2470,7 +2471,7 @@ async fn add_update_group( | |||
| } | ||||
| 
 | ||||
| #[get("/organizations/<_org_id>/groups/<group_id>/details")] | ||||
| async fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { | ||||
| async fn get_group_details(_org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.org_groups_enabled() { | ||||
|         err!("Group support is disabled"); | ||||
|     } | ||||
|  | @ -2480,7 +2481,7 @@ async fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders | |||
|         _ => err!("Group could not be found!"), | ||||
|     }; | ||||
| 
 | ||||
|     Ok(Json(group.to_json_details(&mut conn).await)) | ||||
|     Ok(Json(group.to_json_details(&(headers.org_user_type as i32), &mut conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[post("/organizations/<org_id>/groups/<group_id>/delete")] | ||||
|  |  | |||
|  | @ -78,28 +78,46 @@ impl Collection { | |||
|         cipher_sync_data: Option<&crate::api::core::CipherSyncData>, | ||||
|         conn: &mut DbConn, | ||||
|     ) -> Value { | ||||
|         let (read_only, hide_passwords) = if let Some(cipher_sync_data) = cipher_sync_data { | ||||
|         let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data { | ||||
|             match cipher_sync_data.user_organizations.get(&self.org_uuid) { | ||||
|                 Some(uo) if uo.has_full_access() => (false, false), | ||||
|                 Some(_) => { | ||||
|                 // Only for Manager types Bitwarden returns true for the can_manage option
 | ||||
|                 // Owners and Admins always have false, but they can manage all collections anyway
 | ||||
|                 Some(uo) if uo.has_full_access() => (false, false, uo.atype == UserOrgType::Manager), | ||||
|                 Some(uo) => { | ||||
|                     // Only let a manager manage collections when the have full read/write access
 | ||||
|                     let is_manager = uo.atype == UserOrgType::Manager; | ||||
|                     if let Some(uc) = cipher_sync_data.user_collections.get(&self.uuid) { | ||||
|                         (uc.read_only, uc.hide_passwords) | ||||
|                         (uc.read_only, uc.hide_passwords, is_manager && !uc.read_only && !uc.hide_passwords) | ||||
|                     } else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) { | ||||
|                         (cg.read_only, cg.hide_passwords) | ||||
|                         (cg.read_only, cg.hide_passwords, is_manager && !cg.read_only && !cg.hide_passwords) | ||||
|                     } else { | ||||
|                         (false, false) | ||||
|                         (false, false, false) | ||||
|                     } | ||||
|                 } | ||||
|                 _ => (true, true), | ||||
|                 _ => (true, true, false), | ||||
|             } | ||||
|         } else { | ||||
|             (!self.is_writable_by_user(user_uuid, conn).await, self.hide_passwords_for_user(user_uuid, conn).await) | ||||
|             match UserOrganization::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { | ||||
|                 Some(ou) if ou.has_full_access() => (false, false, ou.atype == UserOrgType::Manager), | ||||
|                 Some(ou) => { | ||||
|                     let is_manager = ou.atype == UserOrgType::Manager; | ||||
|                     let read_only = !self.is_writable_by_user(user_uuid, conn).await; | ||||
|                     let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await; | ||||
|                     (read_only, hide_passwords, is_manager && !read_only && !hide_passwords) | ||||
|                 } | ||||
|                 _ => ( | ||||
|                     !self.is_writable_by_user(user_uuid, conn).await, | ||||
|                     self.hide_passwords_for_user(user_uuid, conn).await, | ||||
|                     false, | ||||
|                 ), | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let mut json_object = self.to_json(); | ||||
|         json_object["object"] = json!("collectionDetails"); | ||||
|         json_object["readOnly"] = json!(read_only); | ||||
|         json_object["hidePasswords"] = json!(hide_passwords); | ||||
|         json_object["manage"] = json!(can_manage); | ||||
|         json_object | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| use super::{User, UserOrgType, UserOrganization}; | ||||
| use crate::api::EmptyResult; | ||||
| use crate::db::DbConn; | ||||
| use crate::error::MapResult; | ||||
| use chrono::{NaiveDateTime, Utc}; | ||||
| use serde_json::Value; | ||||
| 
 | ||||
|  | @ -69,7 +73,7 @@ impl Group { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn to_json_details(&self, conn: &mut DbConn) -> Value { | ||||
|     pub async fn to_json_details(&self, user_org_type: &i32, conn: &mut DbConn) -> Value { | ||||
|         let collections_groups: Vec<Value> = CollectionGroup::find_by_group(&self.uuid, conn) | ||||
|             .await | ||||
|             .iter() | ||||
|  | @ -77,7 +81,8 @@ impl Group { | |||
|                 json!({ | ||||
|                     "id": entry.collections_uuid, | ||||
|                     "readOnly": entry.read_only, | ||||
|                     "hidePasswords": entry.hide_passwords | ||||
|                     "hidePasswords": entry.hide_passwords, | ||||
|                     "manage": *user_org_type == UserOrgType::Manager && !entry.read_only && !entry.hide_passwords | ||||
|                 }) | ||||
|             }) | ||||
|             .collect(); | ||||
|  | @ -122,13 +127,6 @@ impl GroupUser { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| use crate::db::DbConn; | ||||
| 
 | ||||
| use crate::api::EmptyResult; | ||||
| use crate::error::MapResult; | ||||
| 
 | ||||
| use super::{User, UserOrganization}; | ||||
| 
 | ||||
| /// Database methods
 | ||||
| impl Group { | ||||
|     pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { | ||||
|  |  | |||
|  | @ -1,9 +1,13 @@ | |||
| use chrono::{NaiveDateTime, Utc}; | ||||
| use num_traits::FromPrimitive; | ||||
| use serde_json::Value; | ||||
| use std::cmp::Ordering; | ||||
| use std::{ | ||||
|     cmp::Ordering, | ||||
|     collections::{HashMap, HashSet}, | ||||
| }; | ||||
| 
 | ||||
| use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User}; | ||||
| use crate::db::models::{Collection, CollectionGroup}; | ||||
| use crate::CONFIG; | ||||
| 
 | ||||
| db_object! { | ||||
|  | @ -453,15 +457,47 @@ impl UserOrganization { | |||
|         }; | ||||
| 
 | ||||
|         let collections: Vec<Value> = if include_collections { | ||||
|             CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn) | ||||
|             // Get all collections for the user here already to prevent more queries
 | ||||
|             let cu: HashMap<String, CollectionUser> = | ||||
|                 CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn) | ||||
|                     .await | ||||
|                     .into_iter() | ||||
|                     .map(|cu| (cu.collection_uuid.clone(), cu)) | ||||
|                     .collect(); | ||||
| 
 | ||||
|             // Get all collection groups for this user to prevent there inclusion
 | ||||
|             let cg: HashSet<String> = CollectionGroup::find_by_user(&self.user_uuid, conn) | ||||
|                 .await | ||||
|                 .iter() | ||||
|                 .map(|cu| { | ||||
|                     json!({ | ||||
|                         "id": cu.collection_uuid, | ||||
|                         "readOnly": cu.read_only, | ||||
|                         "hidePasswords": cu.hide_passwords, | ||||
|                     }) | ||||
|                 .into_iter() | ||||
|                 .map(|cg| cg.collections_uuid) | ||||
|                 .collect(); | ||||
| 
 | ||||
|             Collection::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn) | ||||
|                 .await | ||||
|                 .into_iter() | ||||
|                 .filter_map(|c| { | ||||
|                     let (read_only, hide_passwords, can_manage) = if self.has_full_access() { | ||||
|                         (false, false, self.atype == UserOrgType::Manager) | ||||
|                     } else if let Some(cu) = cu.get(&c.uuid) { | ||||
|                         ( | ||||
|                             cu.read_only, | ||||
|                             cu.hide_passwords, | ||||
|                             self.atype == UserOrgType::Manager && !cu.read_only && !cu.hide_passwords, | ||||
|                         ) | ||||
|                     // If previous checks failed it might be that this user has access via a group, but we should not return those elements here
 | ||||
|                     // Those are returned via a special group endpoint
 | ||||
|                     } else if cg.contains(&c.uuid) { | ||||
|                         return None; | ||||
|                     } else { | ||||
|                         (true, true, false) | ||||
|                     }; | ||||
| 
 | ||||
|                     Some(json!({ | ||||
|                         "id": c.uuid, | ||||
|                         "readOnly": read_only, | ||||
|                         "hidePasswords": hide_passwords, | ||||
|                         "manage": can_manage, | ||||
|                     })) | ||||
|                 }) | ||||
|                 .collect() | ||||
|         } else { | ||||
|  | @ -474,6 +510,7 @@ impl UserOrganization { | |||
|             "name": user.name, | ||||
|             "email": user.email, | ||||
|             "externalId": self.external_id, | ||||
|             "avatarColor": user.avatar_color, | ||||
|             "groups": groups, | ||||
|             "collections": collections, | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue