Merge branch 'master' into rocket-0.4

# Conflicts:
#	src/db/models/organization.rs
pull/284/head
Daniel García 6 years ago
commit f84cbeaaf8
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A

@ -4,7 +4,7 @@
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM node:8-alpine as vault FROM node:8-alpine as vault
ENV VAULT_VERSION "v2.4.0" ENV VAULT_VERSION "v2.5.0"
ENV URL "https://github.com/bitwarden/web.git" ENV URL "https://github.com/bitwarden/web.git"

@ -4,7 +4,7 @@
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM node:8-alpine as vault FROM node:8-alpine as vault
ENV VAULT_VERSION "v2.4.0" ENV VAULT_VERSION "v2.5.0"
ENV URL "https://github.com/bitwarden/web.git" ENV URL "https://github.com/bitwarden/web.git"

@ -4,7 +4,7 @@
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM node:8-alpine as vault FROM node:8-alpine as vault
ENV VAULT_VERSION "v2.4.0" ENV VAULT_VERSION "v2.5.0"
ENV URL "https://github.com/bitwarden/web.git" ENV URL "https://github.com/bitwarden/web.git"

@ -4,7 +4,7 @@
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM node:8-alpine as vault FROM node:8-alpine as vault
ENV VAULT_VERSION "v2.4.0" ENV VAULT_VERSION "v2.5.0"
ENV URL "https://github.com/bitwarden/web.git" ENV URL "https://github.com/bitwarden/web.git"

@ -195,17 +195,19 @@ docker run -d --name bitwarden \
``` ```
Note that you need to mount ssl files and you need to forward appropriate port. Note that you need to mount ssl files and you need to forward appropriate port.
Due to what is likely a certificate validation bug in Android, you need to make sure that your certificate includes the full chain of trust. In the case of certbot, this means using `fullchain.pem` instead of `cert.pem`.
Softwares used for getting certs are often using symlinks. If that is the case, both locations need to be accessible to the docker container. Softwares used for getting certs are often using symlinks. If that is the case, both locations need to be accessible to the docker container.
Example: [certbot](https://certbot.eff.org/) will create a folder that contains the needed `cert.pem` and `privacy.pem` files in `/etc/letsencrypt/live/mydomain/` Example: [certbot](https://certbot.eff.org/) will create a folder that contains the needed `fullchain.pem` and `privkey.pem` files in `/etc/letsencrypt/live/mydomain/`
These files are symlinked to `../../archive/mydomain/mykey.pem` These files are symlinked to `../../archive/mydomain/privkey.pem`
So to use from bitwarden container: So to use from bitwarden container:
```sh ```sh
docker run -d --name bitwarden \ docker run -d --name bitwarden \
-e ROCKET_TLS='{certs="/ssl/live/mydomain/cert.pem",key="/ssl/live/mydomain/privkey.pem"}' \ -e ROCKET_TLS='{certs="/ssl/live/mydomain/fullchain.pem",key="/ssl/live/mydomain/privkey.pem"}' \
-v /etc/letsencrypt/:/ssl/ \ -v /etc/letsencrypt/:/ssl/ \
-v /bw-data/:/data/ \ -v /bw-data/:/data/ \
-p 443:80 \ -p 443:80 \

@ -164,9 +164,18 @@ pub struct CipherData {
} }
#[post("/ciphers/admin", data = "<data>")] #[post("/ciphers/admin", data = "<data>")]
fn post_ciphers_admin(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult { fn post_ciphers_admin(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
// TODO: Implement this correctly let data: ShareCipherData = data.into_inner().data;
post_ciphers(data, headers, conn, ws)
let mut cipher = Cipher::new(data.Cipher.Type.clone(), data.Cipher.Name.clone());
cipher.user_uuid = Some(headers.user.uuid.clone());
match cipher.save(&conn) {
Ok(()) => (),
Err(_) => err!("Failed saving cipher")
};
share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &ws)
} }
#[post("/ciphers", data = "<data>")] #[post("/ciphers", data = "<data>")]

@ -130,7 +130,7 @@ fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> EmptyRe
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
None => err!("User not part of organization"), None => err!("User not part of organization"),
Some(user_org) => { Some(user_org) => {
if user_org.type_ == UserOrgType::Owner as i32 { if user_org.type_ == UserOrgType::Owner {
let num_owners = UserOrganization::find_by_org_and_type( let num_owners = UserOrganization::find_by_org_and_type(
&org_id, UserOrgType::Owner as i32, &conn) &org_id, UserOrgType::Owner as i32, &conn)
.len(); .len();
@ -417,9 +417,9 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
None => err!("Invalid type") None => err!("Invalid type")
}; };
if new_type != UserOrgType::User as i32 && if new_type != UserOrgType::User &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner {
err!("Only Owners can invite Admins or Owners") err!("Only Owners can invite Managers, Admins or Owners")
} }
for email in data.Emails.iter() { for email in data.Emails.iter() {
@ -491,9 +491,9 @@ fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase<Value>,
None => err!("The specified user isn't a member of the organization") None => 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 &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner {
err!("Only Owners can confirm Admins or Owners") err!("Only Owners can confirm Managers, Admins or Owners")
} }
if user_to_confirm.status != UserOrgStatus::Accepted as i32 { if user_to_confirm.status != UserOrgStatus::Accepted as i32 {
@ -541,7 +541,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>
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()) {
Some(new_type) => new_type as i32, Some(new_type) => new_type,
None => err!("Invalid type") None => err!("Invalid type")
}; };
@ -550,21 +550,21 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>
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 != user_to_edit.type_ as i32 && ( if new_type != user_to_edit.type_ && (
user_to_edit.type_ <= UserOrgType::Admin as i32 || user_to_edit.type_ >= UserOrgType::Admin ||
new_type <= UserOrgType::Admin as i32 new_type >= UserOrgType::Admin
) && ) &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner {
err!("Only Owners can grant and remove Admin or Owner privileges") err!("Only Owners can grant and remove Admin or Owner privileges")
} }
if user_to_edit.type_ == UserOrgType::Owner as i32 && if user_to_edit.type_ == UserOrgType::Owner &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner {
err!("Only Owners can edit Owner users") err!("Only Owners can edit Owner users")
} }
if user_to_edit.type_ == UserOrgType::Owner as i32 && if user_to_edit.type_ == UserOrgType::Owner &&
new_type != UserOrgType::Owner as i32 { new_type != UserOrgType::Owner {
// Removing owner permmission, check that there are at least another owner // Removing owner permmission, check that there are at least another owner
let num_owners = UserOrganization::find_by_org_and_type( let num_owners = UserOrganization::find_by_org_and_type(
@ -577,7 +577,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>
} }
user_to_edit.access_all = data.AccessAll; user_to_edit.access_all = data.AccessAll;
user_to_edit.type_ = new_type; user_to_edit.type_ = new_type as i32;
// Delete all the odd collections // Delete all the odd collections
for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) { for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) {
@ -630,12 +630,12 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn:
None => err!("User to delete isn't member of the organization") None => err!("User to delete isn't member of the organization")
}; };
if user_to_delete.type_ != UserOrgType::User as i32 && if user_to_delete.type_ != UserOrgType::User &&
headers.org_user_type != UserOrgType::Owner as i32 { headers.org_user_type != UserOrgType::Owner {
err!("Only Owners can delete Admins or Owners") err!("Only Owners can delete Admins or Owners")
} }
if user_to_delete.type_ == UserOrgType::Owner as i32 { if user_to_delete.type_ == UserOrgType::Owner {
// Removing owner, check that there are at least another owner // Removing owner, check that there are at least another owner
let num_owners = UserOrganization::find_by_org_and_type( let num_owners = UserOrganization::find_by_org_and_type(
&org_id, UserOrgType::Owner as i32, &conn) &org_id, UserOrgType::Owner as i32, &conn)
@ -692,7 +692,7 @@ fn post_org_import(query: Form<OrgIdData>, data: JsonUpcase<ImportData>, headers
None => err!("User is not part of the organization") None => err!("User is not part of the organization")
}; };
if org_user.type_ > UserOrgType::Admin as i32 { if org_user.type_ < UserOrgType::Admin {
err!("Only admins or owners can import into an organization") err!("Only admins or owners can import into an organization")
} }

@ -184,7 +184,7 @@ pub struct OrgHeaders {
pub host: String, pub host: String,
pub device: Device, pub device: Device,
pub user: User, pub user: User,
pub org_user_type: i32, pub org_user_type: UserOrgType,
} }
impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders { impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
@ -224,7 +224,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,
org_user_type: org_user.type_, org_user_type: {
if let Some(org_usr_type) = UserOrgType::from_i32(&org_user.type_) {
org_usr_type
} else { // This should only happen if the DB is corrupted
err_handler!("Unknown user type in the database")
}
},
}) })
}, },
_ => err_handler!("Error getting the organization id"), _ => err_handler!("Error getting the organization id"),
@ -238,7 +244,7 @@ pub struct AdminHeaders {
pub host: String, pub host: String,
pub device: Device, pub device: Device,
pub user: User, pub user: User,
pub org_user_type: i32, pub org_user_type: UserOrgType,
} }
impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders { impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
@ -249,15 +255,15 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
Outcome::Forward(f) => Outcome::Forward(f), Outcome::Forward(f) => Outcome::Forward(f),
Outcome::Failure(f) => Outcome::Failure(f), Outcome::Failure(f) => Outcome::Failure(f),
Outcome::Success(headers) => { Outcome::Success(headers) => {
if headers.org_user_type > UserOrgType::Admin as i32 { if headers.org_user_type >= UserOrgType::Admin {
err_handler!("You need to be Admin or Owner to call this endpoint")
} else {
Outcome::Success(Self{ Outcome::Success(Self{
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,
org_user_type: headers.org_user_type, org_user_type: headers.org_user_type,
}) })
} else {
err_handler!("You need to be Admin or Owner to call this endpoint")
} }
} }
} }
@ -278,14 +284,14 @@ impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
Outcome::Forward(f) => Outcome::Forward(f), Outcome::Forward(f) => Outcome::Forward(f),
Outcome::Failure(f) => Outcome::Failure(f), Outcome::Failure(f) => Outcome::Failure(f),
Outcome::Success(headers) => { Outcome::Success(headers) => {
if headers.org_user_type > UserOrgType::Owner as i32 { if headers.org_user_type == UserOrgType::Owner {
err_handler!("You need to be Owner to call this endpoint")
} else {
Outcome::Success(Self{ Outcome::Success(Self{
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,
}) })
} else {
err_handler!("You need to be Owner to call this endpoint")
} }
} }
} }

@ -1,3 +1,4 @@
use std::cmp::Ordering;
use serde_json::Value; use serde_json::Value;
use uuid::Uuid; use uuid::Uuid;
@ -32,10 +33,101 @@ pub enum UserOrgStatus {
Confirmed = 2, Confirmed = 2,
} }
#[derive(Copy, Clone)]
#[derive(PartialEq)]
#[derive(Eq)]
pub enum UserOrgType { pub enum UserOrgType {
Owner = 0, Owner = 0,
Admin = 1, Admin = 1,
User = 2, User = 2,
Manager = 3,
}
impl Ord for UserOrgType {
fn cmp(&self, other: &UserOrgType) -> Ordering {
if self == other {
Ordering::Equal
} else {
match self {
UserOrgType::Owner => Ordering::Greater,
UserOrgType::Admin => match other {
UserOrgType::Owner => Ordering::Less,
_ => Ordering::Greater
},
UserOrgType::Manager => match other {
UserOrgType::Owner | UserOrgType::Admin => Ordering::Less,
_ => Ordering::Greater
},
UserOrgType::User => Ordering::Less
}
}
}
}
impl PartialOrd for UserOrgType {
fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<i32> for UserOrgType {
fn eq(&self, other: &i32) -> bool {
*other == *self as i32
}
}
impl PartialOrd<i32> for UserOrgType {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
if let Some(other) = Self::from_i32(other) {
return Some(self.cmp(&other))
}
return None
}
fn gt(&self, other: &i32) -> bool {
match self.partial_cmp(other) {
Some(Ordering::Less) | Some(Ordering::Equal) => false,
_ => true,
}
}
fn ge(&self, other: &i32) -> bool {
match self.partial_cmp(other) {
Some(Ordering::Less) => false,
_ => true,
}
}
}
impl PartialEq<UserOrgType> for i32 {
fn eq(&self, other: &UserOrgType) -> bool {
*self == *other as i32
}
}
impl PartialOrd<UserOrgType> for i32 {
fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
if let Some(self_type) = UserOrgType::from_i32(self) {
return Some(self_type.cmp(other))
}
return None
}
fn lt(&self, other: &UserOrgType) -> bool {
match self.partial_cmp(other) {
Some(Ordering::Less) | None => true,
_ => false,
}
}
fn le(&self, other: &UserOrgType) -> bool {
match self.partial_cmp(other) {
Some(Ordering::Less) | Some(Ordering::Equal) | None => true,
_ => false,
}
}
} }
impl UserOrgType { impl UserOrgType {
@ -44,9 +136,21 @@ impl UserOrgType {
"0" | "Owner" => Some(UserOrgType::Owner), "0" | "Owner" => Some(UserOrgType::Owner),
"1" | "Admin" => Some(UserOrgType::Admin), "1" | "Admin" => Some(UserOrgType::Admin),
"2" | "User" => Some(UserOrgType::User), "2" | "User" => Some(UserOrgType::User),
"3" | "Manager" => Some(UserOrgType::Manager),
_ => None, _ => None,
} }
} }
pub fn from_i32(i: &i32) -> Option<Self> {
match i {
0 => Some(UserOrgType::Owner),
1 => Some(UserOrgType::Admin),
2 => Some(UserOrgType::User),
3 => Some(UserOrgType::Manager),
_ => None,
}
}
} }
/// Local methods /// Local methods
@ -302,7 +406,7 @@ impl UserOrganization {
} }
pub fn has_full_access(self) -> bool { pub fn has_full_access(self) -> bool {
self.access_all || self.type_ < UserOrgType::User as i32 self.access_all || self.type_ >= UserOrgType::Admin
} }
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {

@ -157,7 +157,7 @@ impl User {
pub fn delete(self, conn: &DbConn) -> QueryResult<()> { pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) { for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) {
if user_org.type_ == UserOrgType::Owner as i32 { if user_org.type_ == UserOrgType::Owner {
if UserOrganization::find_by_org_and_type( if UserOrganization::find_by_org_and_type(
&user_org.org_uuid, &user_org.org_uuid,
UserOrgType::Owner as i32, &conn UserOrgType::Owner as i32, &conn

Loading…
Cancel
Save