commit f397f0cbd0a5d32b78c6e0f139acef598e737ad6
parent 4d2c6e39b27ea2a755f7f28efb4ee636b5323373
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Thu, 13 Sep 2018 15:16:24 +0200
Implement organization import for admins and owners (Fixes #178)
Diffstat:
3 files changed, 80 insertions(+), 14 deletions(-)
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -86,7 +86,7 @@ fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResul
#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
-struct CipherData {
+pub struct CipherData {
// Id is optional as it is included only in bulk share
Id: Option<String>,
// Folder id is not included in import
@@ -100,8 +100,8 @@ struct CipherData {
Card = 3,
Identity = 4
*/
- Type: i32, // TODO: Change this to NumberOrString
- Name: String,
+ pub Type: i32, // TODO: Change this to NumberOrString
+ pub Name: String,
Notes: Option<String>,
Fields: Option<Value>,
@@ -132,7 +132,7 @@ fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) ->
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
}
-fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Headers, shared_to_collection: bool, conn: &DbConn) -> EmptyResult {
+pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Headers, shared_to_collection: bool, conn: &DbConn) -> EmptyResult {
if let Some(org_id) = data.OrganizationId {
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
None => err!("You don't have permission to add item to organization"),
@@ -560,7 +560,7 @@ fn delete_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbCon
let uuids = match data.get("Ids") {
Some(ids) => match ids.as_array() {
- Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }),
+ Some(ids) => ids.iter().filter_map(Value::as_str),
None => err!("Posted ids field is not an array")
},
None => err!("Request missing ids field")
@@ -606,7 +606,7 @@ fn move_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn)
let uuids = match data.get("Ids") {
Some(ids) => match ids.as_array() {
- Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }),
+ Some(ids) => ids.iter().filter_map(Value::as_str),
None => err!("Posted ids field is not an array")
},
None => err!("Request missing ids field")
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -110,6 +110,7 @@ pub fn routes() -> Vec<Route> {
put_organization_user,
delete_user,
post_delete_user,
+ post_org_import,
clear_device_token,
put_device_token,
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -1,5 +1,3 @@
-#![allow(unused_imports)]
-
use rocket_contrib::{Json, Value};
use CONFIG;
use db::DbConn;
@@ -140,9 +138,8 @@ fn get_user_collections(headers: Headers, conn: DbConn) -> JsonResult {
"Data":
Collection::find_by_user_uuid(&headers.user.uuid, &conn)
.iter()
- .map(|collection| {
- collection.to_json()
- }).collect::<Value>(),
+ .map(Collection::to_json)
+ .collect::<Value>(),
"Object": "list"
})))
}
@@ -153,9 +150,8 @@ fn get_org_collections(org_id: String, _headers: AdminHeaders, conn: DbConn) ->
"Data":
Collection::find_by_organization(&org_id, &conn)
.iter()
- .map(|collection| {
- collection.to_json()
- }).collect::<Value>(),
+ .map(Collection::to_json)
+ .collect::<Value>(),
"Object": "list"
})))
}
@@ -582,4 +578,73 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn:
#[post("/organizations/<org_id>/users/<org_user_id>/delete")]
fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult {
delete_user(org_id, org_user_id, headers, conn)
+}
+
+use super::ciphers::CipherData;
+use super::ciphers::update_cipher_from_data;
+
+#[derive(Deserialize)]
+#[allow(non_snake_case)]
+struct ImportData {
+ Ciphers: Vec<CipherData>,
+ Collections: Vec<NewCollectionData>,
+ CollectionRelationships: Vec<RelationsData>,
+}
+
+#[derive(Deserialize)]
+#[allow(non_snake_case)]
+struct RelationsData {
+ // Cipher index
+ Key: usize,
+ // Collection index
+ Value: usize,
+}
+
+#[post("/ciphers/import-organization?<query>", data = "<data>")]
+fn post_org_import(query: OrgIdData, data: JsonUpcase<ImportData>, headers: Headers, conn: DbConn) -> EmptyResult {
+ let data: ImportData = data.into_inner().data;
+ let org_id = query.organizationId;
+
+ let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
+ Some(user) => user,
+ None => err!("User is not part of the organization")
+ };
+
+ if org_user.type_ > UserOrgType::Admin as i32 {
+ err!("Only admins or owners can import into an organization")
+ }
+
+ // Read and create the collections
+ let collections: Vec<_> = data.Collections.into_iter().map(|coll| {
+ let mut collection = Collection::new(org_id.clone(), coll.Name);
+ collection.save(&conn);
+ collection
+ }).collect();
+
+ // Read the relations between collections and ciphers
+ let mut relations = Vec::new();
+ for relation in data.CollectionRelationships {
+ relations.push((relation.Key, relation.Value));
+ }
+
+ // Read and create the ciphers
+ let ciphers: Vec<_> = data.Ciphers.into_iter().map(|cipher_data| {
+ let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
+ update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn).ok();
+ cipher
+ }).collect();
+
+ // Assign the collections
+ for (cipher_index, coll_index) in relations {
+ let cipher_id = &ciphers[cipher_index].uuid;
+ let coll_id = &collections[coll_index].uuid;
+
+ CollectionCipher::save(cipher_id, coll_id, &conn);
+ }
+
+ let mut user = headers.user;
+ match user.update_revision(&conn) {
+ Ok(()) => Ok(()),
+ Err(_) => err!("Failed to update the revision, please log out and log back in to finish import.")
+ }
}
\ No newline at end of file