From 1bced97e0495023b2140396ec1370b3f93066c4d Mon Sep 17 00:00:00 2001 From: Miroslav Prasil Date: Mon, 3 Sep 2018 10:53:52 +0100 Subject: [PATCH 1/5] Add info on running over HTTP (documentation for #153) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3791821..1447c80 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ _*Note, that this project is not associated with the [Bitwarden](https://bitward - [Changing user email](#changing-user-email) - [Creating organization](#creating-organization) - [Inviting users into organization](#inviting-users-into-organization) + - [Running on unencrypted connection](#running-on-unencrypted-connection) - [Get in touch](#get-in-touch) ## Features @@ -366,6 +367,12 @@ We use upstream Vault interface directly without any (significant) changes, this The users must already be registered on your server to invite them, because we can't send the invitation via email. The invited users won't get the invitation email, instead they will appear in the interface as if they already accepted the invitation. Organization admin then just needs to confirm them to be proper Organization members and to give them access to the shared secrets. +### Running on unencrypted connection + +It is strongly recommended to run bitwarden_rs service over HTTPS. However the server itself while supporting it does not strictly require such setup. This makes it a bit easier to spin up the service in cases where you can generally trust the connection (internal and secure network, access over VPN,..) or when you want to put the service behind HTTP proxy, that will do the encryption on the proxy end. + +Running over HTTP is still reasonably secure provided you use really strong master password and that you avoid using web Vault over connection that is vulnerable to MITM attacks where attacker could inject javascript into your interface. However some forms of 2FA might not work in this setup and [Vault doesn't work in this configuration in Chrome](https://github.com/bitwarden/web/issues/254). + ## Get in touch To ask an question, [raising an issue](https://github.com/dani-garcia/bitwarden_rs/issues/new) is fine, also please report any bugs spotted here. From 53e8f78af600faf8eb813a3364ac7c48353dc8fc Mon Sep 17 00:00:00 2001 From: Miroslav Prasil Date: Mon, 3 Sep 2018 10:59:59 +0100 Subject: [PATCH 2/5] Link to the https setup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1447c80..358941a 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ The users must already be registered on your server to invite them, because we c ### Running on unencrypted connection -It is strongly recommended to run bitwarden_rs service over HTTPS. However the server itself while supporting it does not strictly require such setup. This makes it a bit easier to spin up the service in cases where you can generally trust the connection (internal and secure network, access over VPN,..) or when you want to put the service behind HTTP proxy, that will do the encryption on the proxy end. +It is strongly recommended to run bitwarden_rs service over HTTPS. However the server itself while [supporting it](#enabling-https) does not strictly require such setup. This makes it a bit easier to spin up the service in cases where you can generally trust the connection (internal and secure network, access over VPN,..) or when you want to put the service behind HTTP proxy, that will do the encryption on the proxy end. Running over HTTP is still reasonably secure provided you use really strong master password and that you avoid using web Vault over connection that is vulnerable to MITM attacks where attacker could inject javascript into your interface. However some forms of 2FA might not work in this setup and [Vault doesn't work in this configuration in Chrome](https://github.com/bitwarden/web/issues/254). From 049aa33f179076a6b1ba3b71351b5d7b95ef4fdc Mon Sep 17 00:00:00 2001 From: Miroslav Prasil Date: Tue, 4 Sep 2018 11:24:53 +0100 Subject: [PATCH 3/5] Fix editing users in Organization --- src/api/core/organizations.rs | 47 +++++++++++++++++++---------------- src/db/models/organization.rs | 7 ++++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index c2ecde2..dd0db2c 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -408,11 +408,11 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade Ok(()) } -#[post("/organizations//users//confirm", data = "")] -fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +#[post("/organizations//users//confirm", data = "")] +fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { let data = data.into_inner().data; - let mut user_to_confirm = match UserOrganization::find_by_uuid(&user_id, &conn) { + let mut user_to_confirm = match UserOrganization::find_by_uuid(&org_user_id, &conn) { Some(user) => user, None => err!("Failed to find user membership") }; @@ -441,9 +441,9 @@ fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase, head Ok(()) } -#[get("/organizations//users/")] -fn get_user(org_id: String, user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { - let user = match UserOrganization::find_by_uuid(&user_id, &conn) { +#[get("/organizations//users/")] +fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { + let user = match UserOrganization::find_by_uuid(&org_user_id, &conn) { Some(user) => user, None => err!("Failed to find user membership") }; @@ -464,13 +464,13 @@ struct EditUserData { AccessAll: bool, } -#[put("/organizations//users/", data = "", rank = 1)] -fn put_organization_user(org_id: String, user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - edit_user(org_id, user_id, data, headers, conn) +#[put("/organizations//users/", data = "", rank = 1)] +fn put_organization_user(org_id: String, org_user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + edit_user(org_id, org_user_id, data, headers, conn) } -#[post("/organizations//users/", data = "", rank = 1)] -fn edit_user(org_id: String, user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +#[post("/organizations//users/", data = "", rank = 1)] +fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { let data: EditUserData = data.into_inner().data; let new_type = match UserOrgType::from_str(&data.Type.into_string()) { @@ -478,19 +478,22 @@ fn edit_user(org_id: String, user_id: String, data: JsonUpcase, he None => err!("Invalid type") }; - let mut user_to_edit = match UserOrganization::find_by_uuid(&user_id, &conn) { + let mut user_to_edit = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { Some(user) => user, None => err!("The specified user isn't member of the organization") }; - if new_type != UserOrgType::User as i32 && + if new_type != user_to_edit.type_ as i32 && ( + user_to_edit.type_ >= UserOrgType::Admin as i32 || + new_type >= UserOrgType::Admin as i32 + ) && headers.org_user_type != UserOrgType::Owner as i32 { - err!("Only Owners can grant Admin or Owner type") + err!("Only Owners can grant and remove Admin or Owner privileges") } - if user_to_edit.type_ != UserOrgType::User as i32 && + if user_to_edit.type_ == UserOrgType::Owner as i32 && headers.org_user_type != UserOrgType::Owner as i32 { - err!("Only Owners can edit Admin or Owner") + err!("Only Owners can edit Owner users") } if user_to_edit.type_ == UserOrgType::Owner as i32 && @@ -535,9 +538,9 @@ fn edit_user(org_id: String, user_id: String, data: JsonUpcase, he Ok(()) } -#[delete("/organizations//users/")] -fn delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - let user_to_delete = match UserOrganization::find_by_uuid(&user_id, &conn) { +#[delete("/organizations//users/")] +fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + let user_to_delete = match UserOrganization::find_by_uuid(&org_user_id, &conn) { Some(user) => user, None => err!("User to delete isn't member of the organization") }; @@ -564,7 +567,7 @@ fn delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbC } } -#[post("/organizations//users//delete")] -fn post_delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - delete_user(org_id, user_id, headers, conn) +#[post("/organizations//users//delete")] +fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + delete_user(org_id, org_user_id, headers, conn) } \ No newline at end of file diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index d46d15d..18ac2a0 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -270,6 +270,13 @@ impl UserOrganization { .first::(&**conn).ok() } + pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option { + users_organizations::table + .filter(users_organizations::uuid.eq(uuid)) + .filter(users_organizations::org_uuid.eq(org_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)) From db111ae2a09f7921a0c81f49cd7538d7002f6139 Mon Sep 17 00:00:00 2001 From: Miroslav Prasil Date: Tue, 4 Sep 2018 13:37:44 +0100 Subject: [PATCH 4/5] Check properly the user membership in Organization --- src/api/core/organizations.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index dd0db2c..449f15f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -217,7 +217,7 @@ fn delete_organization_collection_user(org_id: String, col_id: String, org_user_ } }; - match UserOrganization::find_by_uuid(&org_user_id, &conn) { + match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { None => err!("User not found in organization"), Some(user_org) => { match CollectionUser::find_by_collection_and_user(&collection.uuid, &user_org.user_uuid, &conn) { @@ -412,15 +412,11 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase, headers: AdminHeaders, conn: DbConn) -> EmptyResult { let data = data.into_inner().data; - let mut user_to_confirm = match UserOrganization::find_by_uuid(&org_user_id, &conn) { + let mut user_to_confirm = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { Some(user) => user, - None => err!("Failed to find user membership") + None => err!("The specified user isn't a member of the organization") }; - if user_to_confirm.org_uuid != org_id { - err!("The specified user isn't a member of the organization") - } - if user_to_confirm.type_ != UserOrgType::User as i32 && headers.org_user_type != UserOrgType::Owner as i32 { err!("Only Owners can confirm Admins or Owners") @@ -443,15 +439,11 @@ fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase, #[get("/organizations//users/")] fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { - let user = match UserOrganization::find_by_uuid(&org_user_id, &conn) { + let user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { Some(user) => user, - None => err!("Failed to find user membership") + None => err!("The specified user isn't a member of the organization") }; - if user.org_uuid != org_id { - err!("The specified user isn't a member of the organization") - } - Ok(Json(user.to_json_details(&conn))) } @@ -540,7 +532,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase #[delete("/organizations//users/")] fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - let user_to_delete = match UserOrganization::find_by_uuid(&org_user_id, &conn) { + let user_to_delete = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { Some(user) => user, None => err!("User to delete isn't member of the organization") }; From c58682e3fbcf76ffa0e99ae6ab342d8c794ce0a5 Mon Sep 17 00:00:00 2001 From: Miroslav Prasil Date: Tue, 4 Sep 2018 16:10:26 +0100 Subject: [PATCH 5/5] Fix the logic in user edditing --- src/api/core/organizations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 449f15f..3253eb5 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -476,8 +476,8 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase }; if new_type != user_to_edit.type_ as i32 && ( - user_to_edit.type_ >= UserOrgType::Admin as i32 || - new_type >= UserOrgType::Admin as i32 + user_to_edit.type_ <= UserOrgType::Admin as i32 || + new_type <= UserOrgType::Admin as i32 ) && headers.org_user_type != UserOrgType::Owner as i32 { err!("Only Owners can grant and remove Admin or Owner privileges")