From fb7b1c8c18aa6d335addf07ca2aadbdd0394a7b9 Mon Sep 17 00:00:00 2001
From: Kumar Ankur <kmrankur@outlook.com>
Date: Mon, 6 Aug 2018 03:29:44 +0530
Subject: [PATCH] Implemented bulk cipher share (share selected) #100

---
 src/api/core/ciphers.rs     | 66 ++++++++++++++++++++++++++++++++++---
 src/api/core/mod.rs         |  1 +
 src/db/models/attachment.rs |  6 ++++
 3 files changed, 68 insertions(+), 5 deletions(-)

diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
index 95df5a00..a253e201 100644
--- a/src/api/core/ciphers.rs
+++ b/src/api/core/ciphers.rs
@@ -87,6 +87,8 @@ fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResul
 #[derive(Deserialize, Debug)]
 #[allow(non_snake_case)]
 struct CipherData {
+    // Id is optional as it is included only in bulk share
+    Id: Option<String>,
     // Folder id is not included in import
     FolderId: Option<String>,
     // TODO: Some of these might appear all the time, no need for Option
@@ -332,6 +334,65 @@ struct ShareCipherData {
 fn post_cipher_share(uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn) -> JsonResult {
     let data: ShareCipherData = data.into_inner().data;
 
+    share_cipher_by_uuid(&uuid, data, &headers, &conn)
+}
+
+#[put("/ciphers/<uuid>/share", data = "<data>")]
+fn put_cipher_share(uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn) -> JsonResult {
+    let data: ShareCipherData = data.into_inner().data;
+
+    share_cipher_by_uuid(&uuid, data, &headers, &conn)
+}
+
+#[derive(Deserialize)]
+#[allow(non_snake_case)]
+struct ShareSelectedCipherData {
+    Ciphers: Vec<CipherData>,
+    CollectionIds: Vec<String>
+}
+
+#[put("/ciphers/share", data = "<data>")]
+fn put_cipher_share_seleted(data: JsonUpcase<ShareSelectedCipherData>, headers: Headers, conn: DbConn) -> EmptyResult {
+    let mut data: ShareSelectedCipherData = data.into_inner().data;
+    let mut cipher_ids: Vec<String> = Vec::new();
+
+    if data.Ciphers.len() == 0 {
+        err!("You must select at least one cipher.")
+    }
+    
+    if data.CollectionIds.len() == 0 {
+        err!("You must select at least one collection.")
+    }
+    
+    for cipher in data.Ciphers.iter() {
+        match cipher.Id {
+            Some(ref id) => cipher_ids.push(id.to_string()),
+            None => err!("Request missing ids field")
+        };
+    }
+
+    let attachments = Attachment::find_by_ciphers(cipher_ids, &conn);
+    
+    if attachments.len() > 0 {
+        err!("Ciphers should not have any attachments.")
+    }
+
+    while let Some(cipher) = data.Ciphers.pop() {
+        let mut shared_cipher_data = ShareCipherData {
+            Cipher: cipher,
+            CollectionIds: data.CollectionIds.clone()
+        };
+
+        match shared_cipher_data.Cipher.Id.take() {
+            Some(id) => share_cipher_by_uuid(&id, shared_cipher_data , &headers, &conn)?,
+            None => err!("Request missing ids field")
+        };
+    }
+
+    Ok(())
+}
+
+fn share_cipher_by_uuid(uuid: &str, data: ShareCipherData, headers: &Headers, conn: &DbConn) -> JsonResult {
     let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) {
         Some(cipher) => {
             if cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
@@ -365,11 +426,6 @@ fn post_cipher_share(uuid: String, data: JsonUpcase<ShareCipherData>, headers: H
     }
 }
 
-#[put("/ciphers/<uuid>/share", data = "<data>")]
-fn put_cipher_share(uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn) -> JsonResult {
-    post_cipher_share(uuid, data, headers, conn)
-}
-
 #[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
 fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers: Headers, conn: DbConn) -> JsonResult {
     let cipher = match Cipher::find_by_uuid(&uuid, &conn) {
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
index e06c36b4..a3d565cb 100644
--- a/src/api/core/mod.rs
+++ b/src/api/core/mod.rs
@@ -42,6 +42,7 @@ pub fn routes() -> Vec<Route> {
         post_cipher_admin,
         post_cipher_share,
         put_cipher_share,
+        put_cipher_share_seleted,
         post_cipher,
         put_cipher,
         delete_cipher_post,
diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs
index 1f5e29a7..66d5b723 100644
--- a/src/db/models/attachment.rs
+++ b/src/db/models/attachment.rs
@@ -111,4 +111,10 @@ impl Attachment {
             .filter(attachments::cipher_uuid.eq(cipher_uuid))
             .load::<Self>(&**conn).expect("Error loading attachments")
     }
+
+    pub fn find_by_ciphers(cipher_uuids: Vec<String>, conn: &DbConn) -> Vec<Self> {
+        attachments::table
+            .filter(attachments::cipher_uuid.eq_any(cipher_uuids))
+            .load::<Self>(&**conn).expect("Error loading attachments")
+    }
 }