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 c5185ddb8386d7a5bf120d777a4a014a2f8b2f91
parent 5210f9b95191389216c3944fde6f1719b5180616
Author: Miroslav Prasil <miroslav@prasil.info>
Date:   Fri, 20 Apr 2018 17:35:11 +0100

Adding some oganization features

Diffstat:
Msrc/api/core/mod.rs | 8+++++++-
Msrc/api/core/organizations.rs | 140++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/db/models/collection.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/db/models/mod.rs | 4+---
Msrc/db/models/user.rs | 1+
5 files changed, 203 insertions(+), 12 deletions(-)

diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -54,11 +54,16 @@ pub fn routes() -> Vec<Route> { activate_authenticator, disable_authenticator, + get_organization, create_organization, delete_organization, get_user_collections, get_org_collections, + get_org_collection_detail, get_collection_users, + post_organization, + post_organization_collections, + post_organization_collection_update, get_org_details, get_org_users, send_invite, @@ -71,7 +76,8 @@ pub fn routes() -> Vec<Route> { put_device_token, get_eq_domains, - post_eq_domains + post_eq_domains, + ] } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -19,13 +19,32 @@ struct OrgData { planType: String, } +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct OrganizationUpdateData { + billingEmail: String, + name: String, +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct NewCollectionData { + name: String, +} + #[post("/organizations", data = "<data>")] fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> JsonResult { let data: OrgData = data.into_inner(); let mut org = Organization::new(data.name, data.billingEmail); let mut user_org = UserOrganization::new( - headers.user.uuid, org.uuid.clone()); + 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; @@ -34,6 +53,8 @@ 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())) } @@ -46,30 +67,133 @@ fn delete_organization(org_id: String, data: Json<PasswordData>, headers: Header unimplemented!() } +#[get("/organizations/<org_id>")] +fn get_organization(org_id: String, headers: Headers, conn: DbConn) -> JsonResult { + if UserOrganization::find_by_user_and_org( &headers.user.uuid, &org_id, &conn).is_none() { + err!("User not in Organization or Organization doesn't exist") + } + + match Organization::find_by_uuid(&org_id, &conn) { + Some(organization) => Ok(Json(organization.to_json())), + None => err!("Can't find organization details") + } +} + +#[post("/organizations/<org_id>", data = "<data>")] +fn post_organization(org_id: String, headers: Headers, data: Json<OrganizationUpdateData>, conn: DbConn) -> JsonResult { + let data: OrganizationUpdateData = data.into_inner(); + + 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_ != 0 { // not owner + err!("Only owner can change Organization details") + } + }; + + let mut org = match Organization::find_by_uuid(&org_id, &conn) { + Some(organization) => organization, + None => err!("Can't find organization details") + }; + + org.name = data.name; + org.billing_email = data.billingEmail; + org.save(&conn); + + Ok(Json(org.to_json())) +} // GET /api/collections?writeOnly=false #[get("/collections")] fn get_user_collections(headers: Headers, conn: DbConn) -> JsonResult { - // let collections_json = get_user_collections().map(|c|c.to_json()); - Ok(Json(json!({ - "Data": [], + "Data": + Collection::find_by_user_uuid(&headers.user.uuid, &conn) + .iter() + .map(|collection| { + collection.to_json() + }).collect::<Value>(), "Object": "list" }))) } #[get("/organizations/<org_id>/collections")] fn get_org_collections(org_id: String, headers: Headers, conn: DbConn) -> JsonResult { - // let org = get_org_by_id(org_id) - // let collections_json = org.collections().map(|c|c.to_json()); - Ok(Json(json!({ - "Data": [], + "Data": + Collection::find_by_user_uuid(&headers.user.uuid, &conn) + .iter() + .filter(|collection| { collection.org_uuid == org_id }) + .map(|collection| { + collection.to_json() + }).collect::<Value>(), "Object": "list" }))) } +#[post("/organizations/<org_id>/collections", data = "<data>")] +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) { + None => err!("User not in Organization or Organization doesn't exist"), + Some(org_user) => if org_user.type_ > 1 { // not owner or admin + err!("Only Organization owner and admin can add Collection") + } + }; + + let org = match Organization::find_by_uuid(&org_id, &conn) { + Some(organization) => organization, + None => err!("Can't find organization details") + }; + + 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); + + Ok(Json(collection.to_json())) +} + +#[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")] +fn post_organization_collection_update(org_id: String, col_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) { + None => err!("User not in Organization or Organization doesn't exist"), + Some(org_user) => if org_user.type_ > 1 { // not owner or admin + err!("Only Organization owner and admin can update Collection") + } + }; + + let org = match Organization::find_by_uuid(&org_id, &conn) { + Some(organization) => organization, + None => err!("Can't find organization details") + }; + + let mut collection = match Collection::find_by_uuid(&col_id, &conn) { + Some(collection) => collection, + None => err!("Collection not found") + }; + + collection.name = data.name.clone(); + collection.save(&conn); + + Ok(Json(collection.to_json())) +} + +#[get("/organizations/<org_id>/collections/<coll_id>/details")] +fn get_org_collection_detail(org_id: String, coll_id: String, headers: Headers, conn: DbConn) -> JsonResult { + match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) { + None => err!("Collection not found"), + Some(collection) => Ok(Json(collection.to_json())) + } +} + #[get("/organizations/<org_id>/collections/<coll_id>/users")] fn get_collection_users(org_id: String, coll_id: String, headers: Headers, conn: DbConn) -> JsonResult { // Get org and collection, check that collection is from org diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs @@ -65,4 +65,65 @@ impl Collection { .filter(collections::uuid.eq(uuid)) .first::<Self>(&**conn).ok() } + + pub fn find_by_user_uuid(uuid: &str, conn: &DbConn) -> Vec<Self> { + match users_collections::table + .filter(users_collections::user_uuid.eq(uuid)) + .select(users_collections::columns::collection_uuid) + .load(&**conn) { + Ok(uuids) => uuids.iter().map(|uuid: &String| { + Collection::find_by_uuid(uuid, &conn).unwrap() + }).collect(), + Err(list) => vec![] + } + } + + pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { + match users_collections::table + .filter(users_collections::collection_uuid.eq(uuid)) + .filter(users_collections::user_uuid.eq(user_uuid)) + .first::<CollectionUsers>(&**conn).ok() { + None => None, + Some(collection_user) => Collection::find_by_uuid(&collection_user.collection_uuid, &conn) + } + } +} + +use super::User; + +#[derive(Debug, Identifiable, Queryable, Insertable, Associations)] +#[table_name = "users_collections"] +#[belongs_to(User, foreign_key = "user_uuid")] +#[belongs_to(Collection, foreign_key = "collection_uuid")] +#[primary_key(user_uuid, collection_uuid)] +pub struct CollectionUsers { + pub user_uuid: String, + pub collection_uuid: String, +} + +/// Local methods +impl CollectionUsers { + pub fn new( + user_uuid: String, + collection_uuid: String, + ) -> Self { + Self { + user_uuid, + collection_uuid, + } + } } + +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) + .execute(&**conn) { + Ok(1) => true, // One row inserted + _ => false, + } + } +} +\ No newline at end of file diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs @@ -12,8 +12,6 @@ pub use self::cipher::Cipher; pub use self::device::Device; pub use self::folder::Folder; pub use self::user::User; - -pub use self::collection::Collection; pub use self::organization::Organization; - pub use self::organization::{UserOrganization, UserOrgStatus, UserOrgType}; +pub use self::collection::{Collection, CollectionUsers}; diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crypto; use CONFIG; + #[derive(Debug, Identifiable, Queryable, Insertable)] #[table_name = "users"] #[primary_key(uuid)]