vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit d15d24f4ff98c59df4a4c7f624503666cc2ffec1
parent 8d992d637e89fac9036009bda36b2d764bcfd3a1
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Tue,  8 Dec 2020 16:11:55 +0100

Merge pull request #1242 from BlackDex/allow-manager-role

Adding Manager Role support
Diffstat:
Msrc/api/core/organizations.rs | 33+++++++++++++++++++++++----------
Msrc/auth.rs | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 153 insertions(+), 11 deletions(-)

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -5,7 +5,7 @@ use serde_json::Value; use crate::{ 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}, mail, CONFIG, }; @@ -217,7 +217,7 @@ fn get_org_collections(org_id: String, _headers: AdminHeaders, conn: DbConn) -> #[post("/organizations/<org_id>/collections", data = "<data>")] fn post_organization_collections( org_id: String, - _headers: AdminHeaders, + headers: ManagerHeadersLoose, data: JsonUpcase<NewCollectionData>, conn: DbConn, ) -> JsonResult { @@ -228,9 +228,22 @@ fn post_organization_collections( 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); 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())) } @@ -238,7 +251,7 @@ fn post_organization_collections( fn put_organization_collection_update( org_id: String, col_id: String, - headers: AdminHeaders, + headers: ManagerHeaders, data: JsonUpcase<NewCollectionData>, conn: DbConn, ) -> JsonResult { @@ -249,7 +262,7 @@ fn put_organization_collection_update( fn post_organization_collection_update( org_id: String, col_id: String, - _headers: AdminHeaders, + _headers: ManagerHeaders, data: JsonUpcase<NewCollectionData>, conn: DbConn, ) -> JsonResult { @@ -317,7 +330,7 @@ fn post_organization_collection_delete_user( } #[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) { None => err!("Collection not found"), Some(collection) => { @@ -341,7 +354,7 @@ struct DeleteCollectionData { fn post_organization_collection_delete( org_id: String, col_id: String, - headers: AdminHeaders, + headers: ManagerHeaders, _data: JsonUpcase<DeleteCollectionData>, conn: DbConn, ) -> EmptyResult { @@ -349,7 +362,7 @@ fn post_organization_collection_delete( } #[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) { None => err!("Collection not found"), 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")] -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 let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) { None => err!("Collection not found in Organization"), @@ -388,7 +401,7 @@ fn put_collection_users( org_id: String, coll_id: String, data: JsonUpcaseVec<CollectionData>, - _headers: AdminHeaders, + _headers: ManagerHeaders, conn: DbConn, ) -> EmptyResult { // 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")] -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_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect(); diff --git a/src/auth.rs b/src/auth.rs @@ -220,7 +220,7 @@ use rocket::{ }; use crate::db::{ - models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization}, + models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization, CollectionUser}, DbConn, }; @@ -310,6 +310,8 @@ pub struct OrgHeaders { pub device: Device, pub user: User, pub org_user_type: UserOrgType, + pub org_user: UserOrganization, + pub org_id: String, } // 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") } }, + org_user, + org_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 host: String, pub device: Device,