From 5210f9b95191389216c3944fde6f1719b5180616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Wed, 25 Apr 2018 00:34:40 +0200 Subject: [PATCH] Added org user editing --- src/api/core/mod.rs | 5 +- src/api/core/organizations.rs | 257 ++++++++++++++++++++++++---------- src/db/models/organization.rs | 42 +++++- 3 files changed, 228 insertions(+), 76 deletions(-) diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 4bf4674..b42a676 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -55,13 +55,16 @@ pub fn routes() -> Vec { disable_authenticator, create_organization, + delete_organization, get_user_collections, get_org_collections, + get_collection_users, get_org_details, get_org_users, - get_collection_users, send_invite, confirm_invite, + get_user, + edit_user, delete_user, clear_device_token, diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index bb35569..356d2cb 100644 --- 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::{JsonResult, EmptyResult}; +use api::{PasswordData, JsonResult, EmptyResult}; use auth::Headers; @@ -38,6 +38,14 @@ fn create_organization(headers: Headers, data: Json, conn: DbConn) -> J Ok(Json(org.to_json())) } +#[post("/organizations//delete", data = "")] +fn delete_organization(org_id: String, data: Json, headers: Headers, conn: DbConn) -> JsonResult { + let data: PasswordData = data.into_inner(); + let password_hash = data.masterPasswordHash; + + unimplemented!() +} + // GET /api/collections?writeOnly=false #[get("/collections")] @@ -62,39 +70,6 @@ fn get_org_collections(org_id: String, headers: Headers, conn: DbConn) -> JsonRe }))) } -#[derive(FromForm)] -#[allow(non_snake_case)] -struct OrgIdData { - organizationId: String -} - -#[get("/ciphers/organization-details?")] -fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult { - - // Get list of ciphers in org? - - Ok(Json(json!({ - "Data": [], - "Object": "list" - }))) -} - -#[get("/organizations//users")] -fn get_org_users(org_id: String, headers: Headers, conn: DbConn) -> JsonResult { - let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { - Some(user_org) => user_org, - None => err!("User isn't member of organization") - } - - let users = UserOrganization::find_by_org(&org_id, &conn); - let users_json: Vec = users.iter().map(|c| c.to_json_details(&conn)).collect(); - - Ok(Json(json!({ - "Data": users_json, - "Object": "list" - }))) -} - #[get("/organizations//collections//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 @@ -122,9 +97,42 @@ fn get_collection_users(org_id: String, coll_id: String, headers: Headers, conn: }))) } +#[derive(FromForm)] +#[allow(non_snake_case)] +struct OrgIdData { + organizationId: String +} + +#[get("/ciphers/organization-details?")] +fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult { + + // Get list of ciphers in org? + + Ok(Json(json!({ + "Data": [], + "Object": "list" + }))) +} + +#[get("/organizations//users")] +fn get_org_users(org_id: String, headers: Headers, conn: DbConn) -> JsonResult { + match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { + Some(_) => (), + None => err!("User isn't member of organization") + } + + let users = UserOrganization::find_by_org(&org_id, &conn); + let users_json: Vec = users.iter().map(|c| c.to_json_user_details(&conn)).collect(); + + Ok(Json(json!({ + "Data": users_json, + "Object": "list" + }))) +} + #[derive(Deserialize)] #[allow(non_snake_case)] -struct InviteCollectionData { +struct CollectionData { id: String, readOnly: bool, } @@ -135,25 +143,30 @@ struct InviteData { emails: Vec, #[serde(rename = "type")] type_: String, - collections: Vec, + collections: Vec, accessAll: bool, - } #[post("/organizations//users/invite", data = "")] fn send_invite(org_id: String, data: Json, headers: Headers, conn: DbConn) -> EmptyResult { let data: InviteData = data.into_inner(); - let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { - Some(user_org) => user_org, - None => err!("User isn't member of organization") + let current_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { + Some(user) => user, + None => err!("The current user isn't member of the organization") }; - if user_org.type_ == UserOrgType::User { + if current_user.type_ == UserOrgType::User as i32 { err!("Users can't invite other people. Ask an Admin or Owner") } - if type_ != UserOrgType::User && user_org.type_ != UserOrgType::Owner { + let new_type = match UserOrgType::from_str(data.type_.as_ref()) { + Some(new_type) => new_type as i32, + None => err!("Invalid type") + }; + + if new_type != UserOrgType::User as i32 && + current_user.type_ != UserOrgType::Owner as i32 { err!("Only Owners can invite Admins or Owners") } @@ -166,24 +179,19 @@ fn send_invite(org_id: String, data: Json, headers: Headers, conn: D None => () } - let mut new_user_org = UserOrganization::new( + let mut new_user = UserOrganization::new( user.uuid, org_id.clone()); if data.accessAll { - new_user_org.access_all = data.accessAll; + new_user.access_all = data.accessAll; } else { err!("Select collections unimplemented") // TODO create Users_collections } - new_user_org.type_ = match data.type_.as_ref() { - "Owner" => UserOrgType::Owner, - "Admin" => UserOrgType::Admin, - "User" => UserOrgType::User, - _ => err!("Invalid type") - } as i32; + new_user.type_ = new_type; - new_user_org.save(&conn); + new_user.save(&conn); } } } @@ -193,58 +201,161 @@ fn send_invite(org_id: String, data: Json, headers: Headers, conn: D #[post("/organizations//users//confirm", data = "")] fn confirm_invite(org_id: String, user_id: String, data: Json, headers: Headers, conn: DbConn) -> EmptyResult { - let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { - Some(user_org) => user_org, - None => err!("User isn't member of organization") + let current_user = match UserOrganization::find_by_user_and_org( + &headers.user.uuid, &org_id, &conn) { + Some(user) => user, + None => err!("The current user isn't member of the organization") }; - if user_org.type_ == UserOrgType::User { + if current_user.type_ == UserOrgType::User as i32 { err!("Users can't confirm other people. Ask an Admin or Owner") } - if type_ != UserOrgType::User && user_org.type_ != UserOrgType::Owner { + let mut user_to_confirm = match UserOrganization::find_by_uuid(&user_id, &conn) { + Some(user) => user, + None => err!("User to confirm isn't member of the organization") + }; + + if user_to_confirm.type_ != UserOrgType::User as i32 && + current_user.type_ != UserOrgType::Owner as i32 { err!("Only Owners can confirm Admins or Owners") } - let mut user_org = match UserOrganization::find_by_user_and_org( - &user_id, &org_id, &conn) { - Some(user_org) => user_org, - None => err!("Can't find user") - }; - - if user_org.status != UserOrgStatus::Accepted as i32 { + if user_to_confirm.status != UserOrgStatus::Accepted as i32 { err!("User in invalid state") } - user_org.status = UserOrgStatus::Confirmed as i32; - user_org.key = match data["key"].as_str() { + user_to_confirm.status = UserOrgStatus::Confirmed as i32; + user_to_confirm.key = match data["key"].as_str() { Some(key) => key.to_string(), None => err!("Invalid key provided") }; - user_org.save(&conn); + user_to_confirm.save(&conn); + + Ok(()) +} + +#[get("/organizations//users/")] +fn get_user(org_id: String, user_id: String, headers: Headers, conn: DbConn) -> JsonResult { + let current_user = match UserOrganization::find_by_user_and_org( + &headers.user.uuid, &org_id, &conn) { + Some(user) => user, + None => err!("The current user isn't member of the organization") + }; + + let user = match UserOrganization::find_by_uuid(&user_id, &conn) { + Some(user) => user, + None => err!("The specified user isn't member of the organization") + }; + + Ok(Json(user.to_json_details())) +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct EditUserData { + #[serde(rename = "type")] + type_: String, + collections: Vec, + accessAll: bool, +} + +#[post("/organizations//users/", data = "", rank = 1)] +fn edit_user(org_id: String, user_id: String, data: Json, headers: Headers, conn: DbConn) -> EmptyResult { + let data: EditUserData = data.into_inner(); + + let current_user = match UserOrganization::find_by_user_and_org( + &headers.user.uuid, &org_id, &conn) { + Some(user) => user, + None => err!("The current user isn't member of the organization") + }; + + let new_type = match UserOrgType::from_str(data.type_.as_ref()) { + Some(new_type) => new_type as i32, + None => err!("Invalid type") + }; + + let mut user_to_edit = match UserOrganization::find_by_uuid(&user_id, &conn) { + Some(user) => user, + None => err!("The specified user isn't member of the organization") + }; + + if current_user.type_ == UserOrgType::User as i32 { + err!("Users can't edit users. Ask an Admin or Owner") + } + + if new_type != UserOrgType::User as i32 && + current_user.type_ != UserOrgType::Owner as i32 { + err!("Only Owners can grant Admin or Owner type") + } + + if user_to_edit.type_ != UserOrgType::User as i32 && + current_user.type_ != UserOrgType::Owner as i32 { + err!("Only Owners can edit Admin or Owner") + } + + if user_to_edit.type_ == UserOrgType::Owner as i32 && + new_type != UserOrgType::Owner as i32 { + + // Removing owner permmission, check that there are at least another owner + let num_owners = UserOrganization::find_by_org_and_type( + &org_id, UserOrgType::Owner as i32, &conn) + .len(); + + if num_owners <= 1 { + err!("Can't delete the last owner") + } + } + + 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 + } + + user_to_edit.save(&conn); Ok(()) } #[post("/organizations//users//delete")] fn delete_user(org_id: String, user_id: String, headers: Headers, conn: DbConn) -> EmptyResult { - let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { - Some(user_org) => user_org, - None => err!("User isn't member of organization") + let current_user = match UserOrganization::find_by_user_and_org( + &headers.user.uuid, &org_id, &conn) { + Some(user) => user, + None => err!("The current user isn't member of the organization") }; - if user_org.type_ == UserOrgType::User { + if current_user.type_ == UserOrgType::User as i32 { err!("Users can't delete other people. Ask an Admin or Owner") } - if type_ != UserOrgType::User && user_org.type_ != UserOrgType::Owner { + let user_to_delete = match UserOrganization::find_by_uuid(&user_id, &conn) { + Some(user) => user, + None => err!("User to delete isn't member of the organization") + }; + + if user_to_delete.type_ != UserOrgType::User as i32 && + current_user.type_ != UserOrgType::Owner as i32 { err!("Only Owners can delete Admins or Owners") } - // TODO Don't delete the last owner + if user_to_delete.type_ == UserOrgType::Owner as i32 { + // Removing owner, check that there are at least another owner + let num_owners = UserOrganization::find_by_org_and_type( + &org_id, UserOrgType::Owner as i32, &conn) + .len(); - user_org.delete(&conn); + if num_owners <= 1 { + err!("Can't delete the last owner") + } + } + + user_to_delete.delete(&conn); // TODO Delete users_collections from this org diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index e4029a8..9389360 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -37,6 +37,17 @@ pub enum UserOrgType { User = 2, } +impl UserOrgType { + pub fn from_str(s: &str) -> Option { + match s { + "0" | "Owner" => Some(UserOrgType::Owner), + "1" | "Admin" => Some(UserOrgType::Admin), + "2" | "User" => Some(UserOrgType::User), + _ => None, + } + } +} + /// Local methods impl Organization { pub fn new(name: String, billing_email: String) -> Self { @@ -155,13 +166,13 @@ impl UserOrganization { }) } - pub fn to_json_details(&self, conn: &DbConn) -> JsonValue { + pub fn to_json_user_details(&self, conn: &DbConn) -> JsonValue { use super::User; let user = User::find_by_uuid(&self.user_uuid, conn).unwrap(); json!({ "Id": self.uuid, - "UserId": user.uuid, + "UserId": self.user_uuid, "Name": user.name, "Email": user.email, @@ -173,6 +184,20 @@ impl UserOrganization { }) } + pub fn to_json_details(&self) -> JsonValue { + json!({ + "Id": self.uuid, + "UserId": self.user_uuid, + + "Status": self.status, + "Type": self.type_, + "AccessAll": true, + "Collections": [], + + "Object": "organizationUserDetails", + }) + } + pub fn save(&mut self, conn: &DbConn) -> bool { match diesel::replace_into(users_organizations::table) .values(&*self) @@ -191,6 +216,12 @@ impl UserOrganization { } } + pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option { + users_organizations::table + .filter(users_organizations::uuid.eq(uuid)) + .first::(&**conn).ok() + } + pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -203,6 +234,13 @@ impl UserOrganization { .load::(&**conn).expect("Error loading user organizations") } + pub fn find_by_org_and_type(org_uuid: &str, type_: i32, conn: &DbConn) -> Vec { + users_organizations::table + .filter(users_organizations::org_uuid.eq(org_uuid)) + .filter(users_organizations::type_.eq(type_)) + .load::(&**conn).expect("Error loading user organizations") + } + pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid))