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 0cb58add54a85993ba9ff3665df170dfce04972e
parent 92236394e6e55e1078bb044651a4c2b08b6c3f42
Author: Daniel GarcĂ­a <dani1861994@hotmail.com>
Date:   Fri,  4 May 2018 19:25:50 +0200

Implemented some admin methods, inserted CollectionsUsers only when Org accessAll == false, and implemented find_collection when user has access_all in Org

Diffstat:
Msrc/api/core/ciphers.rs | 17+++++++++++++++--
Msrc/api/core/mod.rs | 2++
Msrc/api/core/organizations.rs | 76+++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/api/mod.rs | 16++++++++++++++++
Msrc/db/models/collection.rs | 57++++++++++++++++++++++++++++++---------------------------
5 files changed, 106 insertions(+), 62 deletions(-)

diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -69,6 +69,12 @@ fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) } +#[get("/ciphers/<uuid>/admin")] +fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { + // TODO: Implement this correctly + get_cipher(uuid, headers, conn) +} + #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct CipherData { @@ -100,6 +106,7 @@ struct CipherData { #[post("/ciphers/admin", data = "<data>")] fn post_ciphers_admin(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { + // TODO: Implement this correctly post_ciphers(data, headers, conn) } @@ -230,8 +237,8 @@ fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) - let data: ImportData = data.into_inner(); // Read and create the folders - let folders: Vec<_> = data.folders.iter().map(|folder| { - let mut folder = Folder::new(headers.user.uuid.clone(), folder.name.clone()); + let folders: Vec<_> = data.folders.into_iter().map(|folder| { + let mut folder = Folder::new(headers.user.uuid.clone(), folder.name); folder.save(&conn); folder }).collect(); @@ -261,6 +268,12 @@ fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) - Ok(()) } +#[post("/ciphers/<uuid>/admin", data = "<data>")] +fn post_cipher_admin(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { + // TODO: Implement this correctly + post_cipher(uuid, data, headers, conn) +} + #[post("/ciphers/<uuid>", data = "<data>")] fn post_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { put_cipher(uuid, data, headers, conn) diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -26,12 +26,14 @@ pub fn routes() -> Vec<Route> { get_ciphers, get_cipher, + get_cipher_admin, post_ciphers, post_ciphers_admin, post_ciphers_import, post_attachment, delete_attachment_post, delete_attachment, + post_cipher_admin, post_cipher, put_cipher, delete_cipher_post, diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -5,7 +5,7 @@ use rocket_contrib::{Json, Value}; use db::DbConn; use db::models::*; -use api::{PasswordData, JsonResult, EmptyResult}; +use api::{PasswordData, JsonResult, EmptyResult, NumberOrString}; use auth::Headers; @@ -41,10 +41,6 @@ fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> J headers.user.uuid.clone(), org.uuid.clone()); let mut collection = Collection::new( org.uuid.clone(), data.collectionName); - let mut collection_user = CollectionUsers::new( - headers.user.uuid.clone(), - collection.uuid.clone(), - ); user_org.key = data.key; user_org.access_all = true; @@ -54,7 +50,6 @@ fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> J org.save(&conn); user_org.save(&conn); collection.save(&conn); - collection_user.save(&conn); Ok(Json(org.to_json())) } @@ -64,6 +59,8 @@ fn delete_organization(org_id: String, data: Json<PasswordData>, headers: Header let data: PasswordData = data.into_inner(); let password_hash = data.masterPasswordHash; + // TODO: Delete ciphers from organization, collection_users, collections, organization_users and the org itself + unimplemented!() } @@ -109,7 +106,6 @@ fn get_user_collections(headers: Headers, conn: DbConn) -> JsonResult { Ok(Json(json!({ "Data": Collection::find_by_user_uuid(&headers.user.uuid, &conn) - .expect("Error loading collections") .iter() .map(|collection| { collection.to_json() @@ -123,7 +119,6 @@ fn get_org_collections(org_id: String, headers: Headers, conn: DbConn) -> JsonRe Ok(Json(json!({ "Data": Collection::find_by_organization_and_user_uuid(&org_id, &headers.user.uuid, &conn) - .unwrap_or(vec![]) .iter() .map(|collection| { collection.to_json() @@ -136,10 +131,12 @@ fn get_org_collections(org_id: String, headers: Headers, conn: DbConn) -> JsonRe fn post_organization_collections(org_id: String, headers: Headers, data: Json<NewCollectionData>, conn: DbConn) -> JsonResult { let data: NewCollectionData = data.into_inner(); - match UserOrganization::find_by_user_and_org( &headers.user.uuid, &org_id, &conn) { + let org_user = match UserOrganization::find_by_user_and_org( &headers.user.uuid, &org_id, &conn) { None => err!("User not in Organization or Organization doesn't exist"), - Some(org_user) => if org_user.type_ > 1 { // not owner or admin + Some(org_user) => if org_user.type_ == UserOrgType::User as i32 { err!("Only Organization owner and admin can add Collection") + } else { + org_user } }; @@ -149,13 +146,12 @@ fn post_organization_collections(org_id: String, headers: Headers, data: Json<Ne }; let mut collection = Collection::new(org.uuid.clone(), data.name); - let mut collection_user = CollectionUsers::new( - headers.user.uuid.clone(), - collection.uuid.clone(), - ); collection.save(&conn); - collection_user.save(&conn); + + if !org_user.access_all { + CollectionUsers::save(&headers.user.uuid, &collection.uuid, &conn); + } Ok(Json(collection.to_json())) } @@ -267,7 +263,7 @@ struct CollectionData { struct InviteData { emails: Vec<String>, #[serde(rename = "type")] - type_: String, + type_: NumberOrString, collections: Vec<CollectionData>, accessAll: bool, } @@ -285,7 +281,7 @@ fn send_invite(org_id: String, data: Json<InviteData>, headers: Headers, conn: D err!("Users can't invite other people. Ask an Admin or Owner") } - let new_type = match UserOrgType::from_str(data.type_.as_ref()) { + let new_type = match UserOrgType::from_str(&data.type_.to_string()) { Some(new_type) => new_type as i32, None => err!("Invalid type") }; @@ -304,18 +300,21 @@ fn send_invite(org_id: String, data: Json<InviteData>, headers: Headers, conn: D None => () } - let mut new_user = UserOrganization::new( - user.uuid, org_id.clone()); + let mut new_user = UserOrganization::new(user.uuid, org_id.clone()); + + new_user.access_all = data.accessAll; + new_user.type_ = new_type; - if data.accessAll { - new_user.access_all = data.accessAll; - } else { - err!("Select collections unimplemented") - // TODO create Users_collections + // If no accessAll, add the collections received + if !data.accessAll { + for collection in data.collections.iter() { + // TODO: Check that collection is in org + // TODO: Save the readOnly bit + + CollectionUsers::save(&headers.user.uuid, &collection.id, &conn); + } } - new_user.type_ = new_type; - new_user.save(&conn); } } @@ -381,7 +380,7 @@ fn get_user(org_id: String, user_id: String, headers: Headers, conn: DbConn) -> #[allow(non_snake_case)] struct EditUserData { #[serde(rename = "type")] - type_: String, + type_: NumberOrString, collections: Vec<CollectionData>, accessAll: bool, } @@ -396,7 +395,7 @@ fn edit_user(org_id: String, user_id: String, data: Json<EditUserData>, headers: None => err!("The current user isn't member of the organization") }; - let new_type = match UserOrgType::from_str(data.type_.as_ref()) { + let new_type = match UserOrgType::from_str(&data.type_.to_string()) { Some(new_type) => new_type as i32, None => err!("Invalid type") }; @@ -436,10 +435,19 @@ fn edit_user(org_id: String, user_id: String, data: Json<EditUserData>, headers: user_to_edit.access_all = data.accessAll; user_to_edit.type_ = new_type; - if data.accessAll { - // Remove users_collections if there is any - } else { - // TODO create users_collections + // Delete all the odd collections + for c in Collection::find_by_organization_and_user_uuid(&org_id, &current_user.uuid, &conn) { + CollectionUsers::delete(&current_user.uuid, &c.uuid, &conn); + } + + // If no accessAll, add the collections received + if !data.accessAll { + for collection in data.collections.iter() { + // TODO: Check that collection is in org + // TODO: Save the readOnly bit + + CollectionUsers::save(&current_user.uuid, &collection.id, &conn); + } } user_to_edit.save(&conn); @@ -482,7 +490,9 @@ fn delete_user(org_id: String, user_id: String, headers: Headers, conn: DbConn) user_to_delete.delete(&conn); - // TODO Delete users_collections from this org + for c in Collection::find_by_organization_and_user_uuid(&org_id, &current_user.uuid, &conn) { + CollectionUsers::delete(&current_user.uuid, &c.uuid, &conn); + } Ok(()) } \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs @@ -21,3 +21,19 @@ type EmptyResult = Result<(), BadRequest<Json>>; struct PasswordData { masterPasswordHash: String } + +#[derive(Deserialize)] +#[serde(untagged)] +enum NumberOrString { + Number(i32), + String(String), +} + +impl NumberOrString { + fn to_string(self) -> String { + match self { + NumberOrString::Number(n) => n.to_string(), + NumberOrString::String(s) => s + } + } +} diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs @@ -38,7 +38,7 @@ impl Collection { use diesel; use diesel::prelude::*; use db::DbConn; -use db::schema::collections; +use db::schema::*; /// Database methods impl Collection { @@ -66,19 +66,25 @@ impl Collection { .first::<Self>(&**conn).ok() } - pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Option<Vec<Self>> { - users_collections::table.inner_join(collections::table) - .filter(users_collections::user_uuid.eq(user_uuid)) + pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> { + let mut all_access_collections = users_organizations::table + .filter(users_organizations::user_uuid.eq(user_uuid)) + .filter(users_organizations::access_all.eq(true)) + .inner_join(collections::table.on(collections::org_uuid.eq(users_organizations::org_uuid))) .select(collections::all_columns) - .load::<Self>(&**conn).ok() - } + .load::<Self>(&**conn).expect("Error loading collections"); - pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Vec<Self>> { - users_collections::table.inner_join(collections::table) + let mut assigned_collections = users_collections::table.inner_join(collections::table) .filter(users_collections::user_uuid.eq(user_uuid)) - .filter(collections::org_uuid.eq(org_uuid)) .select(collections::all_columns) - .load::<Self>(&**conn).ok() + .load::<Self>(&**conn).expect("Error loading collections"); + + all_access_collections.append(&mut assigned_collections); + all_access_collections + } + + pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { + Self::find_by_user_uuid(user_uuid, conn).into_iter().filter(|c| c.org_uuid == org_uuid).collect() } pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { @@ -102,28 +108,25 @@ pub struct CollectionUsers { pub collection_uuid: String, } -/// Local methods +/// Database methods impl CollectionUsers { - pub fn new( - user_uuid: String, - collection_uuid: String, - ) -> Self { - Self { - user_uuid, - collection_uuid, + pub fn save(user_uuid: &str, collection_uuid: &str, conn: &DbConn) -> bool { + match diesel::replace_into(users_collections::table) + .values(( + users_collections::user_uuid.eq(user_uuid), + users_collections::collection_uuid.eq(collection_uuid) + )).execute(&**conn) { + Ok(1) => true, // One row inserted + _ => false, } } -} - -use db::schema::users_collections; -/// Database methods -impl CollectionUsers { - pub fn save(&mut self, conn: &DbConn) -> bool { - match diesel::replace_into(users_collections::table) - .values(&*self) + pub fn delete(user_uuid: &str, collection_uuid: &str, conn: &DbConn) -> bool { + match diesel::delete(users_collections::table + .filter(users_collections::user_uuid.eq(user_uuid)) + .filter(users_collections::collection_uuid.eq(collection_uuid))) .execute(&**conn) { - Ok(1) => true, // One row inserted + Ok(1) => true, // One row deleted _ => false, } }