From dd005910824929778f4d54b342f1c5ac8ac834bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
 <dani-garcia@users.noreply.github.com>
Date: Fri, 9 Nov 2018 16:24:45 +0100
Subject: [PATCH 1/6] Add info about how to fix #176

---
 README.md | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 2ecfc3be..ba4fed2b 100644
--- a/README.md
+++ b/README.md
@@ -195,17 +195,19 @@ docker run -d --name bitwarden \
 ```
 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.
 
-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:
 
 ```sh
 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 /bw-data/:/data/ \
   -p 443:80 \

From b4e222d59889b32fdbed2ad0c31249b1b497d245 Mon Sep 17 00:00:00 2001
From: Miroslav Prasil <miroslav@prasil.info>
Date: Wed, 7 Nov 2018 23:06:07 +0100
Subject: [PATCH 2/6] Bump vault version to 2.5.0

---
 Dockerfile         | 2 +-
 Dockerfile.aarch64 | 2 +-
 Dockerfile.alpine  | 2 +-
 Dockerfile.armv7   | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2a70f17d..dcb78506 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@
 ####################### VAULT BUILD IMAGE  #######################
 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"
 
diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64
index d4b8e815..1c19c8e4 100644
--- a/Dockerfile.aarch64
+++ b/Dockerfile.aarch64
@@ -4,7 +4,7 @@
 ####################### VAULT BUILD IMAGE  #######################
 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"
 
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
index edcfd59a..35626afd 100644
--- a/Dockerfile.alpine
+++ b/Dockerfile.alpine
@@ -4,7 +4,7 @@
 ####################### VAULT BUILD IMAGE  #######################
 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"
 
diff --git a/Dockerfile.armv7 b/Dockerfile.armv7
index d56de1e1..732a718f 100644
--- a/Dockerfile.armv7
+++ b/Dockerfile.armv7
@@ -4,7 +4,7 @@
 ####################### VAULT BUILD IMAGE  #######################
 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"
 

From 66a4c5d48bc6acdf6f30b15824992507dd3ca15e Mon Sep 17 00:00:00 2001
From: Miroslav Prasil <miroslav@prasil.info>
Date: Mon, 12 Nov 2018 17:13:25 +0000
Subject: [PATCH 3/6] Implement comparison between i32 and UserOrgType

---
 src/api/core/organizations.rs | 42 +++++++++----------
 src/auth.rs                   | 24 ++++++-----
 src/db/models/organization.rs | 76 ++++++++++++++++++++++++++++++++++-
 src/db/models/user.rs         |  2 +-
 4 files changed, 112 insertions(+), 32 deletions(-)

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index af8d9282..55a98b10 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -91,7 +91,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) {
         None => err!("User not part of organization"),
         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(
                     &org_id, UserOrgType::Owner as i32, &conn)
                     .len();
@@ -378,9 +378,9 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
         None => err!("Invalid type")
     };
 
-    if new_type != UserOrgType::User as i32 &&
-        headers.org_user_type != UserOrgType::Owner as i32 {
-        err!("Only Owners can invite Admins or Owners")
+    if new_type != UserOrgType::User &&
+        headers.org_user_type != UserOrgType::Owner {
+        err!("Only Owners can invite Managers, Admins or Owners")
     }
 
     for email in data.Emails.iter() {
@@ -452,9 +452,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")
     };
 
-    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")
+    if user_to_confirm.type_ != UserOrgType::User &&
+        headers.org_user_type != UserOrgType::Owner {
+        err!("Only Owners can confirm Managers, Admins or Owners")
     }
 
     if user_to_confirm.status != UserOrgStatus::Accepted as i32 {
@@ -502,7 +502,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>
     let data: EditUserData = data.into_inner().data;
 
     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")
     };
 
@@ -511,21 +511,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")
     };
 
-    if new_type != user_to_edit.type_ as i32 && (
-            user_to_edit.type_ <= UserOrgType::Admin as i32 ||
-            new_type <= UserOrgType::Admin as i32
+    if new_type != user_to_edit.type_ && (
+            user_to_edit.type_ >= UserOrgType::Admin ||
+            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")
     }
 
-    if user_to_edit.type_ == UserOrgType::Owner as i32 &&
-        headers.org_user_type != UserOrgType::Owner as i32 {
+    if user_to_edit.type_ == UserOrgType::Owner &&
+        headers.org_user_type != UserOrgType::Owner {
         err!("Only Owners can edit Owner users")
     }
 
-    if user_to_edit.type_ == UserOrgType::Owner as i32 &&
-        new_type != UserOrgType::Owner as i32 {
+    if user_to_edit.type_ == UserOrgType::Owner &&
+        new_type != UserOrgType::Owner {
 
         // Removing owner permmission, check that there are at least another owner
         let num_owners = UserOrganization::find_by_org_and_type(
@@ -538,7 +538,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>
     }
 
     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
     for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) {
@@ -591,12 +591,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")
     };
 
-    if user_to_delete.type_ != UserOrgType::User as i32 &&
-        headers.org_user_type != UserOrgType::Owner as i32 {
+    if user_to_delete.type_ != UserOrgType::User &&
+        headers.org_user_type != UserOrgType::Owner {
         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
         let num_owners = UserOrganization::find_by_org_and_type(
             &org_id, UserOrgType::Owner as i32, &conn)
@@ -653,7 +653,7 @@ fn post_org_import(query: OrgIdData, data: JsonUpcase<ImportData>, headers: Head
         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")
     }
 
diff --git a/src/auth.rs b/src/auth.rs
index 6b541a6b..727fb433 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -184,7 +184,7 @@ pub struct OrgHeaders {
     pub host: String,
     pub device: Device,
     pub user: User,
-    pub org_user_type: i32,
+    pub org_user_type: UserOrgType,
 }
 
 impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
@@ -225,7 +225,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
                             host: headers.host,
                             device: headers.device,
                             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")
+                                }
+                            },
                         })
                     }
                 }
@@ -238,7 +244,7 @@ pub struct AdminHeaders {
     pub host: String,
     pub device: Device,
     pub user: User,
-    pub org_user_type: i32,
+    pub org_user_type: UserOrgType,
 }
 
 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::Failure(f) => Outcome::Failure(f),
             Outcome::Success(headers) => {
-                if headers.org_user_type > UserOrgType::Admin as i32 {
-                    err_handler!("You need to be Admin or Owner to call this endpoint")
-                } else {
+                if headers.org_user_type >= UserOrgType::Admin {
                     Outcome::Success(Self{
                         host: headers.host,
                         device: headers.device,
                         user: headers.user,
                         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::Failure(f) => Outcome::Failure(f),
             Outcome::Success(headers) => {
-                if headers.org_user_type > UserOrgType::Owner as i32 {
-                    err_handler!("You need to be Owner to call this endpoint")
-                } else {
+                if headers.org_user_type == UserOrgType::Owner {
                     Outcome::Success(Self{
                         host: headers.host,
                         device: headers.device,
                         user: headers.user,
                     })
+                } else {
+                    err_handler!("You need to be Owner to call this endpoint")
                 }
             }
         }
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
index 7f55cd89..00f0a898 100644
--- a/src/db/models/organization.rs
+++ b/src/db/models/organization.rs
@@ -1,3 +1,4 @@
+use std::cmp::Ordering;
 use serde_json::Value as JsonValue;
 
 use uuid::Uuid;
@@ -32,10 +33,71 @@ pub enum UserOrgStatus {
     Confirmed = 2,
 }
 
+#[derive(Copy, Clone)]
+#[derive(PartialEq)]
+#[derive(Eq)]
 pub enum UserOrgType {
     Owner = 0,
     Admin = 1,
     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
+    }
+}
+
+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
+    }
 }
 
 impl UserOrgType {
@@ -44,9 +106,21 @@ impl UserOrgType {
             "0" | "Owner" => Some(UserOrgType::Owner),
             "1" | "Admin" => Some(UserOrgType::Admin),
             "2" | "User" => Some(UserOrgType::User),
+            "3" | "Manager" => Some(UserOrgType::Manager),
             _ => 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
@@ -302,7 +376,7 @@ impl UserOrganization {
     }
 
     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> {
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
index d39c0350..7948db4f 100644
--- a/src/db/models/user.rs
+++ b/src/db/models/user.rs
@@ -157,7 +157,7 @@ impl User {
 
     pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
         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(
                     &user_org.org_uuid, 
                     UserOrgType::Owner as i32, &conn

From b94f4db52a2baa6010f7baa4181dd582c2ff5f6b Mon Sep 17 00:00:00 2001
From: Miroslav Prasil <miroslav@prasil.info>
Date: Tue, 13 Nov 2018 15:34:37 +0000
Subject: [PATCH 4/6] Fix #242

---
 src/api/core/ciphers.rs | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
index d86e41dc..6a2ecd5a 100644
--- a/src/api/core/ciphers.rs
+++ b/src/api/core/ciphers.rs
@@ -132,9 +132,18 @@ pub struct CipherData {
 }
 
 #[post("/ciphers/admin", data = "<data>")]
-fn post_ciphers_admin(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
-    // TODO: Implement this correctly
-    post_ciphers(data, headers, conn, ws)
+fn post_ciphers_admin(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
+    let data: ShareCipherData = data.into_inner().data;
+
+    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>")]

From f3e6cc6ffd85a62004020268c6e78bab785c7600 Mon Sep 17 00:00:00 2001
From: Miroslav Prasil <miroslav@prasil.info>
Date: Tue, 13 Nov 2018 16:34:21 +0000
Subject: [PATCH 5/6] Set PartialOrd to consider invalid i32 UserOrgType lower
 than anything

---
 src/db/models/organization.rs | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
index 00f0a898..912264a6 100644
--- a/src/db/models/organization.rs
+++ b/src/db/models/organization.rs
@@ -83,6 +83,21 @@ impl PartialOrd<i32> for UserOrgType {
         }
         return None
     }
+
+    fn gt(&self, other: &i32) -> bool {
+        match self.partial_cmp(other) {
+            Some(Ordering::Less) => false,
+            _ => true,
+        }
+    }
+
+    fn ge(&self, other: &i32) -> bool {
+        match self.partial_cmp(other) {
+            Some(Ordering::Less) => false,
+            _ => true,
+        }
+    }
+
 }
 
 impl PartialEq<UserOrgType> for i32 {
@@ -98,6 +113,21 @@ impl PartialOrd<UserOrgType> for i32 {
         }
         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 {

From dd684753d06ee28bf126721f6b94519960da4397 Mon Sep 17 00:00:00 2001
From: Miroslav Prasil <miroslav@prasil.info>
Date: Tue, 13 Nov 2018 21:38:56 +0000
Subject: [PATCH 6/6] Fix gt()

---
 src/db/models/organization.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
index 912264a6..c29a841d 100644
--- a/src/db/models/organization.rs
+++ b/src/db/models/organization.rs
@@ -86,7 +86,7 @@ impl PartialOrd<i32> for UserOrgType {
 
     fn gt(&self, other: &i32) -> bool {
         match self.partial_cmp(other) {
-            Some(Ordering::Less) => false,
+            Some(Ordering::Less) | Some(Ordering::Equal) => false,
             _ => true,
         }
     }