vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

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:
Msrc/api/core/ciphers.rs | 12++++++------
Msrc/api/core/mod.rs | 1+
Msrc/api/core/organizations.rs | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
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