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 69dcbdd3b26ba14d9d4327e75e2e3b97f2f2b7aa
parent f8ae5013cb803089dd1c476f3fe29cbed1aa6ca7
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Tue,  4 Sep 2018 17:46:38 +0200

Merge branch 'master' into ws

Diffstat:
MREADME.md | 7+++++++
Msrc/api/core/organizations.rs | 61++++++++++++++++++++++++++++---------------------------------
Msrc/db/models/organization.rs | 7+++++++
3 files changed, 42 insertions(+), 33 deletions(-)

diff --git 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](#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). + ## 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. diff --git 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) { @@ -408,19 +408,15 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade Ok(()) } -#[post("/organizations/<org_id>/users/<user_id>/confirm", data = "<data>")] -fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase<Value>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +#[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")] +fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase<Value>, 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_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") @@ -441,17 +437,13 @@ fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase<Value>, head Ok(()) } -#[get("/organizations/<org_id>/users/<user_id>")] -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/<org_id>/users/<org_user_id>")] +fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { + 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))) } @@ -464,13 +456,13 @@ struct EditUserData { AccessAll: bool, } -#[put("/organizations/<org_id>/users/<user_id>", data = "<data>", rank = 1)] -fn put_organization_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - edit_user(org_id, user_id, data, headers, conn) +#[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] +fn put_organization_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + edit_user(org_id, org_user_id, data, headers, conn) } -#[post("/organizations/<org_id>/users/<user_id>", data = "<data>", rank = 1)] -fn edit_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +#[post("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] +fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>, 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 +470,22 @@ fn edit_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, 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 +530,9 @@ fn edit_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, he Ok(()) } -#[delete("/organizations/<org_id>/users/<user_id>")] -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/<org_id>/users/<org_user_id>")] +fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + 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") }; @@ -564,7 +559,7 @@ fn delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbC } } -#[post("/organizations/<org_id>/users/<user_id>/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/<org_id>/users/<org_user_id>/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 @@ -270,6 +270,13 @@ impl UserOrganization { .first::<Self>(&**conn).ok() } + pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { + users_organizations::table + .filter(users_organizations::uuid.eq(uuid)) + .filter(users_organizations::org_uuid.eq(org_uuid)) + .first::<Self>(&**conn).ok() + } + pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid))