mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-28 00:40:01 +02:00 
			
		
		
		
	Adding Manager Role support
This has been requested a few times (#1136 & #246 & forum), and there already were two (1:1 duplicate) PR's (#1222 & #1223) which needed some changes and no followups or further comments unfortunally. This PR adds two auth headers. - ManagerHeaders Checks if the user-type is Manager or higher and if the manager is part of that collection or not. - ManagerHeadersLoose Check if the user-type is Manager or higher, but does not check if the user is part of the collection, needed for a few features like retreiving all the users of an org. I think this is the safest way to implement this instead of having to check this within every function which needs this manually. Also some extra checks if a manager has access to all collections or just a selection. fixes #1136
This commit is contained in:
		| @@ -5,7 +5,7 @@ use serde_json::Value; | |||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType}, |     api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType}, | ||||||
|     auth::{decode_invite, AdminHeaders, Headers, OwnerHeaders}, |     auth::{decode_invite, AdminHeaders, Headers, OwnerHeaders, ManagerHeaders, ManagerHeadersLoose}, | ||||||
|     db::{models::*, DbConn}, |     db::{models::*, DbConn}, | ||||||
|     mail, CONFIG, |     mail, CONFIG, | ||||||
| }; | }; | ||||||
| @@ -217,7 +217,7 @@ fn get_org_collections(org_id: String, _headers: AdminHeaders, conn: DbConn) -> | |||||||
| #[post("/organizations/<org_id>/collections", data = "<data>")] | #[post("/organizations/<org_id>/collections", data = "<data>")] | ||||||
| fn post_organization_collections( | fn post_organization_collections( | ||||||
|     org_id: String, |     org_id: String, | ||||||
|     _headers: AdminHeaders, |     headers: ManagerHeadersLoose, | ||||||
|     data: JsonUpcase<NewCollectionData>, |     data: JsonUpcase<NewCollectionData>, | ||||||
|     conn: DbConn, |     conn: DbConn, | ||||||
| ) -> JsonResult { | ) -> JsonResult { | ||||||
| @@ -228,9 +228,22 @@ fn post_organization_collections( | |||||||
|         None => err!("Can't find organization details"), |         None => err!("Can't find organization details"), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     // Get the user_organization record so that we can check if the user has access to all collections. | ||||||
|  |     let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { | ||||||
|  |         Some(u) => u, | ||||||
|  |         None => err!("User is not part of organization"), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     let collection = Collection::new(org.uuid, data.Name); |     let collection = Collection::new(org.uuid, data.Name); | ||||||
|     collection.save(&conn)?; |     collection.save(&conn)?; | ||||||
|  |  | ||||||
|  |     // If the user doesn't have access to all collections, only in case of a Manger, | ||||||
|  |     // then we need to save the creating user uuid (Manager) to the users_collection table. | ||||||
|  |     // Else the user will not have access to his own created collection. | ||||||
|  |     if !user_org.access_all { | ||||||
|  |         CollectionUser::save(&headers.user.uuid, &collection.uuid, false, false, &conn)?; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     Ok(Json(collection.to_json())) |     Ok(Json(collection.to_json())) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -238,7 +251,7 @@ fn post_organization_collections( | |||||||
| fn put_organization_collection_update( | fn put_organization_collection_update( | ||||||
|     org_id: String, |     org_id: String, | ||||||
|     col_id: String, |     col_id: String, | ||||||
|     headers: AdminHeaders, |     headers: ManagerHeaders, | ||||||
|     data: JsonUpcase<NewCollectionData>, |     data: JsonUpcase<NewCollectionData>, | ||||||
|     conn: DbConn, |     conn: DbConn, | ||||||
| ) -> JsonResult { | ) -> JsonResult { | ||||||
| @@ -249,7 +262,7 @@ fn put_organization_collection_update( | |||||||
| fn post_organization_collection_update( | fn post_organization_collection_update( | ||||||
|     org_id: String, |     org_id: String, | ||||||
|     col_id: String, |     col_id: String, | ||||||
|     _headers: AdminHeaders, |     _headers: ManagerHeaders, | ||||||
|     data: JsonUpcase<NewCollectionData>, |     data: JsonUpcase<NewCollectionData>, | ||||||
|     conn: DbConn, |     conn: DbConn, | ||||||
| ) -> JsonResult { | ) -> JsonResult { | ||||||
| @@ -317,7 +330,7 @@ fn post_organization_collection_delete_user( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[delete("/organizations/<org_id>/collections/<col_id>")] | #[delete("/organizations/<org_id>/collections/<col_id>")] | ||||||
| fn delete_organization_collection(org_id: String, col_id: String, _headers: AdminHeaders, conn: DbConn) -> EmptyResult { | fn delete_organization_collection(org_id: String, col_id: String, _headers: ManagerHeaders, conn: DbConn) -> EmptyResult { | ||||||
|     match Collection::find_by_uuid(&col_id, &conn) { |     match Collection::find_by_uuid(&col_id, &conn) { | ||||||
|         None => err!("Collection not found"), |         None => err!("Collection not found"), | ||||||
|         Some(collection) => { |         Some(collection) => { | ||||||
| @@ -341,7 +354,7 @@ struct DeleteCollectionData { | |||||||
| fn post_organization_collection_delete( | fn post_organization_collection_delete( | ||||||
|     org_id: String, |     org_id: String, | ||||||
|     col_id: String, |     col_id: String, | ||||||
|     headers: AdminHeaders, |     headers: ManagerHeaders, | ||||||
|     _data: JsonUpcase<DeleteCollectionData>, |     _data: JsonUpcase<DeleteCollectionData>, | ||||||
|     conn: DbConn, |     conn: DbConn, | ||||||
| ) -> EmptyResult { | ) -> EmptyResult { | ||||||
| @@ -349,7 +362,7 @@ fn post_organization_collection_delete( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/organizations/<org_id>/collections/<coll_id>/details")] | #[get("/organizations/<org_id>/collections/<coll_id>/details")] | ||||||
| fn get_org_collection_detail(org_id: String, coll_id: String, headers: AdminHeaders, conn: DbConn) -> JsonResult { | fn get_org_collection_detail(org_id: String, coll_id: String, headers: ManagerHeaders, conn: DbConn) -> JsonResult { | ||||||
|     match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) { |     match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) { | ||||||
|         None => err!("Collection not found"), |         None => err!("Collection not found"), | ||||||
|         Some(collection) => { |         Some(collection) => { | ||||||
| @@ -363,7 +376,7 @@ fn get_org_collection_detail(org_id: String, coll_id: String, headers: AdminHead | |||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/organizations/<org_id>/collections/<coll_id>/users")] | #[get("/organizations/<org_id>/collections/<coll_id>/users")] | ||||||
| fn get_collection_users(org_id: String, coll_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { | fn get_collection_users(org_id: String, coll_id: String, _headers: ManagerHeaders, conn: DbConn) -> JsonResult { | ||||||
|     // Get org and collection, check that collection is from org |     // Get org and collection, check that collection is from org | ||||||
|     let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) { |     let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) { | ||||||
|         None => err!("Collection not found in Organization"), |         None => err!("Collection not found in Organization"), | ||||||
| @@ -388,7 +401,7 @@ fn put_collection_users( | |||||||
|     org_id: String, |     org_id: String, | ||||||
|     coll_id: String, |     coll_id: String, | ||||||
|     data: JsonUpcaseVec<CollectionData>, |     data: JsonUpcaseVec<CollectionData>, | ||||||
|     _headers: AdminHeaders, |     _headers: ManagerHeaders, | ||||||
|     conn: DbConn, |     conn: DbConn, | ||||||
| ) -> EmptyResult { | ) -> EmptyResult { | ||||||
|     // Get org and collection, check that collection is from org |     // Get org and collection, check that collection is from org | ||||||
| @@ -440,7 +453,7 @@ fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Jso | |||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/organizations/<org_id>/users")] | #[get("/organizations/<org_id>/users")] | ||||||
| fn get_org_users(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { | fn get_org_users(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { | ||||||
|     let users = UserOrganization::find_by_org(&org_id, &conn); |     let users = UserOrganization::find_by_org(&org_id, &conn); | ||||||
|     let users_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect(); |     let users_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								src/auth.rs
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								src/auth.rs
									
									
									
									
									
								
							| @@ -220,7 +220,7 @@ use rocket::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
| use crate::db::{ | use crate::db::{ | ||||||
|     models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization}, |     models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization, CollectionUser}, | ||||||
|     DbConn, |     DbConn, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -310,6 +310,8 @@ pub struct OrgHeaders { | |||||||
|     pub device: Device, |     pub device: Device, | ||||||
|     pub user: User, |     pub user: User, | ||||||
|     pub org_user_type: UserOrgType, |     pub org_user_type: UserOrgType, | ||||||
|  |     pub org_user: UserOrganization, | ||||||
|  |     pub org_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| // org_id is usually the second param ("/organizations/<org_id>") | // org_id is usually the second param ("/organizations/<org_id>") | ||||||
| @@ -370,6 +372,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders { | |||||||
|                                     err_handler!("Unknown user type in the database") |                                     err_handler!("Unknown user type in the database") | ||||||
|                                 } |                                 } | ||||||
|                             }, |                             }, | ||||||
|  |                             org_user, | ||||||
|  |                             org_id, | ||||||
|                         }) |                         }) | ||||||
|                     } |                     } | ||||||
|                     _ => err_handler!("Error getting the organization id"), |                     _ => err_handler!("Error getting the organization id"), | ||||||
| @@ -419,6 +423,131 @@ impl Into<Headers> for AdminHeaders { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // col_id is usually the forth param ("/organizations/<org_id>/collections/<col_id>") | ||||||
|  | // But there cloud be cases where it is located in a query value. | ||||||
|  | // First check the param, if this is not a valid uuid, we will try the query value. | ||||||
|  | fn get_col_id(request: &Request) -> Option<String> { | ||||||
|  |     if let Some(Ok(col_id)) = request.get_param::<String>(3) { | ||||||
|  |         if uuid::Uuid::parse_str(&col_id).is_ok() { | ||||||
|  |             return Some(col_id); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if let Some(Ok(col_id)) = request.get_query_value::<String>("collectionId") { | ||||||
|  |         if uuid::Uuid::parse_str(&col_id).is_ok() { | ||||||
|  |             return Some(col_id); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     None | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// The ManagerHeaders are used to check if you are at least a Manager | ||||||
|  | /// and have access to the specific collection provided via the <col_id>/collections/collectionId. | ||||||
|  | /// This does strict checking on the collection_id, ManagerHeadersLoose does not. | ||||||
|  | pub struct ManagerHeaders { | ||||||
|  |     pub host: String, | ||||||
|  |     pub device: Device, | ||||||
|  |     pub user: User, | ||||||
|  |     pub org_user_type: UserOrgType, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeaders { | ||||||
|  |     type Error = &'static str; | ||||||
|  |  | ||||||
|  |     fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> { | ||||||
|  |         match request.guard::<OrgHeaders>() { | ||||||
|  |             Outcome::Forward(_) => Outcome::Forward(()), | ||||||
|  |             Outcome::Failure(f) => Outcome::Failure(f), | ||||||
|  |             Outcome::Success(headers) => { | ||||||
|  |                 if headers.org_user_type >= UserOrgType::Manager { | ||||||
|  |                     match get_col_id(request) { | ||||||
|  |                         Some(col_id) => { | ||||||
|  |                             let conn = match request.guard::<DbConn>() { | ||||||
|  |                                 Outcome::Success(conn) => conn, | ||||||
|  |                                 _ => err_handler!("Error getting DB"), | ||||||
|  |                             }; | ||||||
|  |  | ||||||
|  |                             if !headers.org_user.access_all { | ||||||
|  |                                 match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) { | ||||||
|  |                                     Some(_) => (), | ||||||
|  |                                     None => err_handler!("The current user isn't a manager for this collection"), | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         _ => err_handler!("Error getting the collection id"), | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     Outcome::Success(Self { | ||||||
|  |                         host: headers.host, | ||||||
|  |                         device: headers.device, | ||||||
|  |                         user: headers.user, | ||||||
|  |                         org_user_type: headers.org_user_type, | ||||||
|  |                     }) | ||||||
|  |                 } else { | ||||||
|  |                     err_handler!("You need to be a Manager, Admin or Owner to call this endpoint") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<Headers> for ManagerHeaders { | ||||||
|  |     fn into(self) -> Headers { | ||||||
|  |         Headers { | ||||||
|  |             host: self.host, | ||||||
|  |             device: self.device, | ||||||
|  |             user: self.user, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// The ManagerHeadersLoose is used when you at least need to be a Manager, | ||||||
|  | /// but there is no collection_id sent with the request (either in the path or as form data). | ||||||
|  | pub struct ManagerHeadersLoose { | ||||||
|  |     pub host: String, | ||||||
|  |     pub device: Device, | ||||||
|  |     pub user: User, | ||||||
|  |     pub org_user_type: UserOrgType, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeadersLoose { | ||||||
|  |     type Error = &'static str; | ||||||
|  |  | ||||||
|  |     fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> { | ||||||
|  |         match request.guard::<OrgHeaders>() { | ||||||
|  |             Outcome::Forward(_) => Outcome::Forward(()), | ||||||
|  |             Outcome::Failure(f) => Outcome::Failure(f), | ||||||
|  |             Outcome::Success(headers) => { | ||||||
|  |                 if headers.org_user_type >= UserOrgType::Manager { | ||||||
|  |                     Outcome::Success(Self { | ||||||
|  |                         host: headers.host, | ||||||
|  |                         device: headers.device, | ||||||
|  |                         user: headers.user, | ||||||
|  |                         org_user_type: headers.org_user_type, | ||||||
|  |                     }) | ||||||
|  |                 } else { | ||||||
|  |                     err_handler!("You need to be a Manager, Admin or Owner to call this endpoint") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<Headers> for ManagerHeadersLoose { | ||||||
|  |     fn into(self) -> Headers { | ||||||
|  |         Headers { | ||||||
|  |             host: self.host, | ||||||
|  |             device: self.device, | ||||||
|  |             user: self.user, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub struct OwnerHeaders { | pub struct OwnerHeaders { | ||||||
|     pub host: String, |     pub host: String, | ||||||
|     pub device: Device, |     pub device: Device, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user