From b71d9dd53e447eb66786d0890fb4b595210de158 Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Tue, 21 Jun 2022 17:36:07 +0200
Subject: [PATCH] Fix for issue #2566

This PR fixes #2566
If Organizational syncs returned a FolderId it would cause the web-vault
to hide the cipher because there is a FolderId set. Upstream seems to
not return FolderId and Favorite. When set to null/false it will behave
the same.

In this PR I have added a new CipherSyncType enum to select which type
of sync to execute, and return an empty list for both Folders and Favorites if this is for Orgs.
This also reduces the database load a bit since it will not execute those queries.
---
 src/api/core/ciphers.rs          | 36 ++++++++++++++++++++++----------
 src/api/core/emergency_access.rs |  8 +++++--
 src/api/core/mod.rs              |  2 +-
 src/api/core/organizations.rs    |  6 +++---
 4 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
index 71417097..d70dd906 100644
--- a/src/api/core/ciphers.rs
+++ b/src/api/core/ciphers.rs
@@ -104,7 +104,7 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
     // Get all ciphers which are visible by the user
     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
 
-    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
+    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await;
 
     // Lets generate the ciphers_json using all the gathered info
     let ciphers_json: Vec<Value> = stream::iter(ciphers)
@@ -154,7 +154,7 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
 #[get("/ciphers")]
 async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
-    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
+    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await;
 
     let ciphers_json = stream::iter(ciphers)
         .then(|c| async {
@@ -1486,25 +1486,39 @@ pub struct CipherSyncData {
     pub user_collections: HashMap<String, CollectionUser>,
 }
 
+pub enum CipherSyncType {
+    User,
+    Organization,
+}
+
 impl CipherSyncData {
-    pub async fn new(user_uuid: &str, ciphers: &Vec<Cipher>, conn: &DbConn) -> Self {
+    pub async fn new(user_uuid: &str, ciphers: &Vec<Cipher>, sync_type: CipherSyncType, conn: &DbConn) -> Self {
         // Generate a list of Cipher UUID's to be used during a query filter with an eq_any.
         let cipher_uuids = stream::iter(ciphers).map(|c| c.uuid.to_string()).collect::<Vec<String>>().await;
 
+        let mut cipher_folders: HashMap<String, String> = HashMap::new();
+        let mut cipher_favorites: HashSet<String> = HashSet::new();
+        match sync_type {
+            // User Sync supports Folders and Favorits
+            CipherSyncType::User => {
+                // Generate a HashMap with the Cipher UUID as key and the Folder UUID as value
+                cipher_folders = stream::iter(FolderCipher::find_by_user(user_uuid, conn).await).collect().await;
+
+                // Generate a HashSet of all the Cipher UUID's which are marked as favorite
+                cipher_favorites =
+                    stream::iter(Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await).collect().await;
+            }
+            // Organization Sync does not support Folders and Favorits.
+            // If these are set, it will cause issues in the web-vault.
+            CipherSyncType::Organization => {}
+        }
+
         // Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
         let mut cipher_attachments: HashMap<String, Vec<Attachment>> = HashMap::new();
         for attachment in Attachment::find_all_by_ciphers(&cipher_uuids, conn).await {
             cipher_attachments.entry(attachment.cipher_uuid.to_string()).or_default().push(attachment);
         }
 
-        // Generate a HashMap with the Cipher UUID as key and the Folder UUID as value
-        let cipher_folders: HashMap<String, String> =
-            stream::iter(FolderCipher::find_by_user(user_uuid, conn).await).collect().await;
-
-        // Generate a HashSet of all the Cipher UUID's which are marked as favorite
-        let cipher_favorites: HashSet<String> =
-            stream::iter(Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await).collect().await;
-
         // Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
         let mut cipher_collections: HashMap<String, Vec<String>> = HashMap::new();
         for (cipher, collection) in Cipher::get_collections_with_cipher_by_user(user_uuid, conn).await {
diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs
index 7ca501cb..74197020 100644
--- a/src/api/core/emergency_access.rs
+++ b/src/api/core/emergency_access.rs
@@ -5,7 +5,10 @@ use serde_json::Value;
 use std::borrow::Borrow;
 
 use crate::{
-    api::{core::CipherSyncData, EmptyResult, JsonResult, JsonUpcase, NumberOrString},
+    api::{
+        core::{CipherSyncData, CipherSyncType},
+        EmptyResult, JsonResult, JsonUpcase, NumberOrString,
+    },
     auth::{decode_emergency_access_invite, Headers},
     db::{models::*, DbConn, DbPool},
     mail, CONFIG,
@@ -596,7 +599,8 @@ async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn)
     }
 
     let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await;
-    let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, &conn).await;
+    let cipher_sync_data =
+        CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, CipherSyncType::User, &conn).await;
 
     let ciphers_json = stream::iter(ciphers)
         .then(|c| async {
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
index 3a6208dd..c54ebeb7 100644
--- a/src/api/core/mod.rs
+++ b/src/api/core/mod.rs
@@ -7,7 +7,7 @@ mod sends;
 pub mod two_factor;
 
 pub use ciphers::purge_trashed_ciphers;
-pub use ciphers::CipherSyncData;
+pub use ciphers::{CipherSyncData, CipherSyncType};
 pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job};
 pub use sends::purge_sends;
 pub use two_factor::send_incomplete_2fa_notifications;
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index c4ae909c..7064f221 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -5,8 +5,8 @@ use serde_json::Value;
 
 use crate::{
     api::{
-        core::CipherSyncData, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData,
-        UpdateType,
+        core::{CipherSyncData, CipherSyncType},
+        EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
     },
     auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
     db::{models::*, DbConn},
@@ -487,7 +487,7 @@ struct OrgIdData {
 #[get("/ciphers/organization-details?<data..>")]
 async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
     let ciphers = Cipher::find_by_org(&data.organization_id, &conn).await;
-    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
+    let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::Organization, &conn).await;
 
     let ciphers_json = stream::iter(ciphers)
         .then(|c| async {