Merge branch 'master' into ws

pull/182/head
Daniel García 6 years ago
commit 69dcbdd3b2

@ -48,6 +48,7 @@ _*Note, that this project is not associated with the [Bitwarden](https://bitward
- [Changing user email](#changing-user-email) - [Changing user email](#changing-user-email)
- [Creating organization](#creating-organization) - [Creating organization](#creating-organization)
- [Inviting users into organization](#inviting-users-into-organization) - [Inviting users into organization](#inviting-users-into-organization)
- [Running on unencrypted connection](#running-on-unencrypted-connection)
- [Get in touch](#get-in-touch) - [Get in touch](#get-in-touch)
## Features ## 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. 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 ## 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. 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.

@ -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"), None => err!("User not found in organization"),
Some(user_org) => { Some(user_org) => {
match CollectionUser::find_by_collection_and_user(&collection.uuid, &user_org.user_uuid, &conn) { 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(()) Ok(())
} }
#[post("/organizations/<org_id>/users/<user_id>/confirm", data = "<data>")] #[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")]
fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase<Value>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { 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 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, 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 && if user_to_confirm.type_ != UserOrgType::User as i32 &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner as i32 {
err!("Only Owners can confirm Admins or Owners") 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(()) Ok(())
} }
#[get("/organizations/<org_id>/users/<user_id>")] #[get("/organizations/<org_id>/users/<org_user_id>")]
fn get_user(org_id: String, user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let user = match UserOrganization::find_by_uuid(&user_id, &conn) { let user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) {
Some(user) => user, 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))) Ok(Json(user.to_json_details(&conn)))
} }
@ -464,13 +456,13 @@ struct EditUserData {
AccessAll: bool, AccessAll: bool,
} }
#[put("/organizations/<org_id>/users/<user_id>", data = "<data>", rank = 1)] #[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)]
fn put_organization_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { fn put_organization_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
edit_user(org_id, user_id, data, headers, conn) edit_user(org_id, org_user_id, data, headers, conn)
} }
#[post("/organizations/<org_id>/users/<user_id>", data = "<data>", rank = 1)] #[post("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)]
fn edit_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { 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 data: EditUserData = data.into_inner().data;
let new_type = match UserOrgType::from_str(&data.Type.into_string()) { 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") 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, Some(user) => user,
None => err!("The specified user isn't member of the organization") 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 { 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 { 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 && 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(()) Ok(())
} }
#[delete("/organizations/<org_id>/users/<user_id>")] #[delete("/organizations/<org_id>/users/<org_user_id>")]
fn delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
let user_to_delete = match UserOrganization::find_by_uuid(&user_id, &conn) { let user_to_delete = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) {
Some(user) => user, Some(user) => user,
None => err!("User to delete isn't member of the organization") 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")] #[post("/organizations/<org_id>/users/<org_user_id>/delete")]
fn post_delete_user(org_id: String, user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
delete_user(org_id, user_id, headers, conn) delete_user(org_id, org_user_id, headers, conn)
} }

@ -270,6 +270,13 @@ impl UserOrganization {
.first::<Self>(&**conn).ok() .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> { pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))

Loading…
Cancel
Save