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 5ec728683eed6326da2eb5af28136c1fc93ffa96
parent faa26ab8f539d5cb19ff7382e8fd4075c2eea564
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Fri,  1 Jun 2018 00:18:50 +0200

Make sure the inputs are always in the same case (PascalCase, which is what upstream seems to prefer most of the time)

Diffstat:
Msrc/api/core/accounts.rs | 74+++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/api/core/ciphers.rs | 134++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/api/core/folders.rs | 20+++++++++++---------
Msrc/api/core/mod.rs | 6+++---
Msrc/api/core/organizations.rs | 108++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/api/core/two_factor.rs | 58+++++++++++++++++++++++++++++-----------------------------
Msrc/api/mod.rs | 5++++-
Msrc/util.rs | 26++++++++++++++++++++++++++
8 files changed, 232 insertions(+), 199 deletions(-)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -3,7 +3,7 @@ use rocket_contrib::Json; use db::DbConn; use db::models::*; -use api::{PasswordData, JsonResult, EmptyResult}; +use api::{PasswordData, JsonResult, EmptyResult, JsonUpcase}; use auth::Headers; use CONFIG; @@ -11,12 +11,12 @@ use CONFIG; #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct RegisterData { - email: String, - key: String, - keys: Option<KeysData>, - masterPasswordHash: String, - masterPasswordHint: Option<String>, - name: Option<String>, + Email: String, + Key: String, + Keys: Option<KeysData>, + MasterPasswordHash: String, + MasterPasswordHint: Option<String>, + Name: Option<String>, } #[derive(Deserialize, Debug)] @@ -27,29 +27,29 @@ struct KeysData { } #[post("/accounts/register", data = "<data>")] -fn register(data: Json<RegisterData>, conn: DbConn) -> EmptyResult { - let data: RegisterData = data.into_inner(); +fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { + let data: RegisterData = data.into_inner().data; if !CONFIG.signups_allowed { err!(format!("Signups not allowed")) } - if let Some(_) = User::find_by_mail(&data.email, &conn) { + if let Some(_) = User::find_by_mail(&data.Email, &conn) { err!("Email already exists") } - let mut user = User::new(data.email, data.key, data.masterPasswordHash); + let mut user = User::new(data.Email, data.Key, data.MasterPasswordHash); // Add extra fields if present - if let Some(name) = data.name { + if let Some(name) = data.Name { user.name = name; } - if let Some(hint) = data.masterPasswordHint { + if let Some(hint) = data.MasterPasswordHint { user.password_hint = Some(hint); } - if let Some(keys) = data.keys { + if let Some(keys) = data.Keys { user.private_key = Some(keys.encryptedPrivateKey); user.public_key = Some(keys.publicKey); } @@ -79,8 +79,8 @@ fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult } #[post("/accounts/keys", data = "<data>")] -fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: KeysData = data.into_inner(); +fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: KeysData = data.into_inner().data; let mut user = headers.user; @@ -95,33 +95,33 @@ fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult #[derive(Deserialize)] #[allow(non_snake_case)] struct ChangePassData { - masterPasswordHash: String, - newMasterPasswordHash: String, - key: String, + MasterPasswordHash: String, + NewMasterPasswordHash: String, + Key: String, } #[post("/accounts/password", data = "<data>")] -fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: ChangePassData = data.into_inner(); +fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: ChangePassData = data.into_inner().data; let mut user = headers.user; - if !user.check_valid_password(&data.masterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } - user.set_password(&data.newMasterPasswordHash); - user.key = data.key; + user.set_password(&data.NewMasterPasswordHash); + user.key = data.Key; user.save(&conn); Ok(()) } #[post("/accounts/security-stamp", data = "<data>")] -fn post_sstamp(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: PasswordData = data.into_inner(); +fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: PasswordData = data.into_inner().data; let mut user = headers.user; - if !user.check_valid_password(&data.masterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } @@ -134,36 +134,36 @@ fn post_sstamp(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> Empt #[derive(Deserialize)] #[allow(non_snake_case)] struct ChangeEmailData { - masterPasswordHash: String, - newEmail: String, + MasterPasswordHash: String, + NewEmail: String, } #[post("/accounts/email-token", data = "<data>")] -fn post_email(data: Json<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: ChangeEmailData = data.into_inner(); +fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: ChangeEmailData = data.into_inner().data; let mut user = headers.user; - if !user.check_valid_password(&data.masterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } - if User::find_by_mail(&data.newEmail, &conn).is_some() { + if User::find_by_mail(&data.NewEmail, &conn).is_some() { err!("Email already in use"); } - user.email = data.newEmail; + user.email = data.NewEmail; user.save(&conn); Ok(()) } #[post("/accounts/delete", data = "<data>")] -fn delete_account(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: PasswordData = data.into_inner(); +fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: PasswordData = data.into_inner().data; let user = headers.user; - if !user.check_valid_password(&data.masterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -17,7 +17,7 @@ use db::models::*; use util; use crypto; -use api::{self, PasswordData, JsonResult, EmptyResult}; +use api::{self, PasswordData, JsonResult, EmptyResult, JsonUpcase}; use auth::Headers; use CONFIG; @@ -85,9 +85,9 @@ fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResul #[allow(non_snake_case)] struct CipherData { // Folder id is not included in import - folderId: Option<String>, + FolderId: Option<String>, // TODO: Some of these might appear all the time, no need for Option - organizationId: Option<String>, + OrganizationId: Option<String>, /* Login = 1, @@ -95,32 +95,31 @@ struct CipherData { Card = 3, Identity = 4 */ - #[serde(rename = "type")] - type_: i32, - name: String, - notes: Option<String>, - fields: Option<Value>, + Type: i32, // TODO: Change this to NumberOrString + Name: String, + Notes: Option<String>, + Fields: Option<Value>, // Only one of these should exist, depending on type - login: Option<Value>, - secureNote: Option<Value>, - card: Option<Value>, - identity: Option<Value>, + Login: Option<Value>, + SecureNote: Option<Value>, + Card: Option<Value>, + Identity: Option<Value>, - favorite: Option<bool>, + Favorite: Option<bool>, } #[post("/ciphers/admin", data = "<data>")] -fn post_ciphers_admin(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { +fn post_ciphers_admin(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { // TODO: Implement this correctly post_ciphers(data, headers, conn) } #[post("/ciphers", data = "<data>")] -fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: CipherData = data.into_inner(); +fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: CipherData = data.into_inner().data; - let mut cipher = Cipher::new(data.type_, data.name.clone()); + let mut cipher = Cipher::new(data.Type, data.Name.clone()); update_cipher_from_data(&mut cipher, data, &headers, true, &conn)?; Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) @@ -128,7 +127,7 @@ fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonR fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Headers, is_new_or_shared: bool, conn: &DbConn) -> EmptyResult { if is_new_or_shared { - if let Some(org_id) = data.organizationId { + 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"), Some(org_user) => if org_user.has_full_access() { @@ -143,7 +142,7 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head } } - if let Some(ref folder_id) = data.folderId { + if let Some(ref folder_id) = data.FolderId { match Folder::find_by_uuid(folder_id, conn) { Some(folder) => { if folder.user_uuid != headers.user.uuid { @@ -154,7 +153,7 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head } } - let uppercase_fields = data.fields.map(|f| { + let uppercase_fields = data.Fields.map(|f| { let mut value = json!({}); // Copy every field object and change the names to the correct case copy_values(&f, &mut value); @@ -165,18 +164,18 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head // To remove backwards compatibility, just create an empty values object, // and remove the compat code from cipher::to_json let mut values = json!({ - "Name": data.name, - "Notes": data.notes + "Name": data.Name, + "Notes": data.Notes }); values["Fields"] = uppercase_fields.clone().unwrap_or(Value::Null); // TODO: ******* Backwards compat end ********** - let type_data_opt = match data.type_ { - 1 => data.login, - 2 => data.secureNote, - 3 => data.card, - 4 => data.identity, + let type_data_opt = match data.Type { + 1 => data.Login, + 2 => data.SecureNote, + 3 => data.Card, + 4 => data.Identity, _ => err!("Invalid type") }; @@ -188,15 +187,15 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head // Copy the type data and change the names to the correct case copy_values(&type_data, &mut values); - cipher.favorite = data.favorite.unwrap_or(false); - cipher.name = data.name; - cipher.notes = data.notes; + cipher.favorite = data.Favorite.unwrap_or(false); + cipher.name = data.Name; + cipher.notes = data.Notes; cipher.fields = uppercase_fields.map(|f| f.to_string()); cipher.data = values.to_string(); cipher.save(&conn); - if cipher.move_to_folder(data.folderId, &headers.user.uuid, &conn).is_err() { + if cipher.move_to_folder(data.FolderId, &headers.user.uuid, &conn).is_err() { err!("Error saving the folder information") } @@ -225,9 +224,9 @@ use super::folders::FolderData; #[derive(Deserialize)] #[allow(non_snake_case)] struct ImportData { - ciphers: Vec<CipherData>, - folders: Vec<FolderData>, - folderRelationships: Vec<RelationsData>, + Ciphers: Vec<CipherData>, + Folders: Vec<FolderData>, + FolderRelationships: Vec<RelationsData>, } #[derive(Deserialize)] @@ -241,12 +240,12 @@ struct RelationsData { #[post("/ciphers/import", data = "<data>")] -fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: ImportData = data.into_inner(); +fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: ImportData = data.into_inner().data; // Read and create the folders - let folders: Vec<_> = data.folders.into_iter().map(|folder| { - let mut folder = Folder::new(headers.user.uuid.clone(), folder.name); + let folders: Vec<_> = data.Folders.into_iter().map(|folder| { + let mut folder = Folder::new(headers.user.uuid.clone(), folder.Name); folder.save(&conn); folder }).collect(); @@ -255,17 +254,17 @@ fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) - use std::collections::HashMap; let mut relations_map = HashMap::new(); - for relation in data.folderRelationships { + for relation in data.FolderRelationships { relations_map.insert(relation.key, relation.value); } // Read and create the ciphers let mut index = 0; - for cipher_data in data.ciphers { + for cipher_data in data.Ciphers { let folder_uuid = relations_map.get(&index) .map(|i| folders[*i as usize].uuid.clone()); - let mut cipher = Cipher::new(cipher_data.type_, cipher_data.name.clone()); + let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); update_cipher_from_data(&mut cipher, cipher_data, &headers, true, &conn)?; cipher.move_to_folder(folder_uuid, &headers.user.uuid.clone(), &conn).ok(); @@ -277,19 +276,19 @@ fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) - } #[post("/ciphers/<uuid>/admin", data = "<data>")] -fn post_cipher_admin(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { +fn post_cipher_admin(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { // TODO: Implement this correctly post_cipher(uuid, data, headers, conn) } #[post("/ciphers/<uuid>", data = "<data>")] -fn post_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { +fn post_cipher(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { put_cipher(uuid, data, headers, conn) } #[put("/ciphers/<uuid>", data = "<data>")] -fn put_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: CipherData = data.into_inner(); +fn put_cipher(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: CipherData = data.into_inner().data; let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => cipher, @@ -308,17 +307,17 @@ fn put_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbCo #[derive(Deserialize)] #[allow(non_snake_case)] struct CollectionsAdminData { - collectionIds: Vec<String>, + CollectionIds: Vec<String>, } #[post("/ciphers/<uuid>/collections", data = "<data>")] -fn post_collections_update(uuid: String, data: Json<CollectionsAdminData>, headers: Headers, conn: DbConn) -> EmptyResult { +fn post_collections_update(uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, conn: DbConn) -> EmptyResult { post_collections_admin(uuid, data, headers, conn) } #[post("/ciphers/<uuid>/collections-admin", data = "<data>")] -fn post_collections_admin(uuid: String, data: Json<CollectionsAdminData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: CollectionsAdminData = data.into_inner(); +fn post_collections_admin(uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: CollectionsAdminData = data.into_inner().data; let cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => cipher, @@ -329,7 +328,7 @@ fn post_collections_admin(uuid: String, data: Json<CollectionsAdminData>, header err!("Cipher is not write accessible") } - let posted_collections: HashSet<String> = data.collectionIds.iter().cloned().collect(); + let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect(); let current_collections: HashSet<String> = cipher.get_collections(&headers.user.uuid ,&conn).iter().cloned().collect(); for collection in posted_collections.symmetric_difference(&current_collections) { @@ -355,13 +354,14 @@ fn post_collections_admin(uuid: String, data: Json<CollectionsAdminData>, header #[derive(Deserialize)] #[allow(non_snake_case)] struct ShareCipherData { - cipher: CipherData, - collectionIds: Vec<String>, + #[serde(deserialize_with = "util::upcase_deserialize")] + Cipher: CipherData, + CollectionIds: Vec<String>, } #[post("/ciphers/<uuid>/share", data = "<data>")] -fn post_cipher_share(uuid: String, data: Json<ShareCipherData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: ShareCipherData = data.into_inner(); +fn post_cipher_share(uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: ShareCipherData = data.into_inner().data; let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => { @@ -374,11 +374,11 @@ fn post_cipher_share(uuid: String, data: Json<ShareCipherData>, headers: Headers None => err!("Cipher doesn't exist") }; - match data.cipher.organizationId { + match data.Cipher.OrganizationId { None => err!("Organization id not provided"), Some(_) => { - update_cipher_from_data(&mut cipher, data.cipher, &headers, true, &conn)?; - for collection in data.collectionIds.iter() { + update_cipher_from_data(&mut cipher, data.Cipher, &headers, true, &conn)?; + for collection in data.CollectionIds.iter() { match Collection::find_by_uuid(&collection, &conn) { None => err!("Invalid collection ID provided"), Some(collection) => { @@ -478,10 +478,10 @@ fn delete_cipher(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult { } #[post("/ciphers/delete", data = "<data>")] -fn delete_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: Value = data.into_inner(); +fn delete_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: Value = data.into_inner().data; - let uuids = match data.get("ids") { + let uuids = match data.get("Ids") { Some(ids) => match ids.as_array() { Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }), None => err!("Posted ids field is not an array") @@ -499,8 +499,10 @@ fn delete_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> } #[post("/ciphers/move", data = "<data>")] -fn move_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult { - let folder_id = match data.get("folderId") { +fn move_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn) -> EmptyResult { + let data = data.into_inner().data; + + let folder_id = match data.get("FolderId") { Some(folder_id) => { match folder_id.as_str() { Some(folder_id) => { @@ -520,7 +522,7 @@ fn move_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> Em None => None }; - let uuids = match data.get("ids") { + let uuids = match data.get("Ids") { Some(ids) => match ids.as_array() { Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }), None => err!("Posted ids field is not an array") @@ -549,9 +551,9 @@ fn move_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> Em } #[post("/ciphers/purge", data = "<data>")] -fn delete_all(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: PasswordData = data.into_inner(); - let password_hash = data.masterPasswordHash; +fn delete_all(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: PasswordData = data.into_inner().data; + let password_hash = data.MasterPasswordHash; let user = headers.user; diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs @@ -3,7 +3,7 @@ use rocket_contrib::{Json, Value}; use db::DbConn; use db::models::*; -use api::{JsonResult, EmptyResult}; +use api::{JsonResult, EmptyResult, JsonUpcase}; use auth::Headers; #[get("/folders")] @@ -33,15 +33,17 @@ fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { } #[derive(Deserialize)] +#[allow(non_snake_case)] + pub struct FolderData { - pub name: String + pub Name: String } #[post("/folders", data = "<data>")] -fn post_folders(data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: FolderData = data.into_inner(); +fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: FolderData = data.into_inner().data; - let mut folder = Folder::new(headers.user.uuid.clone(), data.name); + let mut folder = Folder::new(headers.user.uuid.clone(), data.Name); folder.save(&conn); @@ -49,13 +51,13 @@ fn post_folders(data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonR } #[post("/folders/<uuid>", data = "<data>")] -fn post_folder(uuid: String, data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { +fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { put_folder(uuid, data, headers, conn) } #[put("/folders/<uuid>", data = "<data>")] -fn put_folder(uuid: String, data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: FolderData = data.into_inner(); +fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: FolderData = data.into_inner().data; let mut folder = match Folder::find_by_uuid(&uuid, &conn) { Some(folder) => folder, @@ -66,7 +68,7 @@ fn put_folder(uuid: String, data: Json<FolderData>, headers: Headers, conn: DbCo err!("Folder belongs to another user") } - folder.name = data.name; + folder.name = data.Name; folder.save(&conn); diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -100,7 +100,7 @@ use rocket_contrib::Json; use db::DbConn; -use api::{JsonResult, EmptyResult}; +use api::{JsonResult, EmptyResult, JsonUpcase}; use auth::Headers; #[put("/devices/identifier/<uuid>/clear-token")] @@ -155,8 +155,8 @@ struct EquivDomainData { } #[post("/settings/domains", data = "<data>")] -fn post_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: DbConn) -> EmptyResult { - let data: EquivDomainData = data.into_inner(); +fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: EquivDomainData = data.into_inner().data; let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or(Vec::new()); let equivalent_domains = data.EquivalentDomains.unwrap_or(Vec::new()); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -5,45 +5,45 @@ use rocket_contrib::{Json, Value}; use db::DbConn; use db::models::*; -use api::{PasswordData, JsonResult, EmptyResult, NumberOrString}; +use api::{PasswordData, JsonResult, EmptyResult, NumberOrString, JsonUpcase}; use auth::{Headers, AdminHeaders, OwnerHeaders}; #[derive(Deserialize)] #[allow(non_snake_case)] struct OrgData { - billingEmail: String, - collectionName: String, - key: String, - name: String, - #[serde(rename = "planType")] - _planType: String, // Ignored, always use the same plan + BillingEmail: String, + CollectionName: String, + Key: String, + Name: String, + #[serde(rename = "PlanType")] + _PlanType: String, // Ignored, always use the same plan } #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct OrganizationUpdateData { - billingEmail: String, - name: String, + BillingEmail: String, + Name: String, } #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct NewCollectionData { - name: String, + Name: String, } #[post("/organizations", data = "<data>")] -fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> JsonResult { - let data: OrgData = data.into_inner(); +fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult { + let data: OrgData = data.into_inner().data; - let mut org = Organization::new(data.name, data.billingEmail); + let mut org = Organization::new(data.Name, data.BillingEmail); let mut user_org = UserOrganization::new( headers.user.uuid.clone(), org.uuid.clone()); let mut collection = Collection::new( - org.uuid.clone(), data.collectionName); + org.uuid.clone(), data.CollectionName); - user_org.key = data.key; + user_org.key = data.Key; user_org.access_all = true; user_org.type_ = UserOrgType::Owner as i32; user_org.status = UserOrgStatus::Confirmed as i32; @@ -56,9 +56,9 @@ fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> J } #[post("/organizations/<org_id>/delete", data = "<data>")] -fn delete_organization(org_id: String, data: Json<PasswordData>, headers: OwnerHeaders, conn: DbConn) -> EmptyResult { - let data: PasswordData = data.into_inner(); - let password_hash = data.masterPasswordHash; +fn delete_organization(org_id: String, data: JsonUpcase<PasswordData>, headers: OwnerHeaders, conn: DbConn) -> EmptyResult { + let data: PasswordData = data.into_inner().data; + let password_hash = data.MasterPasswordHash; if !headers.user.check_valid_password(&password_hash) { err!("Invalid password") @@ -82,16 +82,16 @@ fn get_organization(org_id: String, _headers: OwnerHeaders, conn: DbConn) -> Jso } #[post("/organizations/<org_id>", data = "<data>")] -fn post_organization(org_id: String, _headers: OwnerHeaders, data: Json<OrganizationUpdateData>, conn: DbConn) -> JsonResult { - let data: OrganizationUpdateData = data.into_inner(); +fn post_organization(org_id: String, _headers: OwnerHeaders, data: JsonUpcase<OrganizationUpdateData>, conn: DbConn) -> JsonResult { + let data: OrganizationUpdateData = data.into_inner().data; let mut org = match Organization::find_by_uuid(&org_id, &conn) { Some(organization) => organization, None => err!("Can't find organization details") }; - org.name = data.name; - org.billing_email = data.billingEmail; + org.name = data.Name; + org.billing_email = data.BillingEmail; org.save(&conn); Ok(Json(org.to_json())) @@ -126,15 +126,15 @@ fn get_org_collections(org_id: String, _headers: AdminHeaders, conn: DbConn) -> } #[post("/organizations/<org_id>/collections", data = "<data>")] -fn post_organization_collections(org_id: String, _headers: AdminHeaders, data: Json<NewCollectionData>, conn: DbConn) -> JsonResult { - let data: NewCollectionData = data.into_inner(); +fn post_organization_collections(org_id: String, _headers: AdminHeaders, data: JsonUpcase<NewCollectionData>, conn: DbConn) -> JsonResult { + let data: NewCollectionData = data.into_inner().data; let org = match Organization::find_by_uuid(&org_id, &conn) { Some(organization) => organization, None => err!("Can't find organization details") }; - let mut collection = Collection::new(org.uuid.clone(), data.name); + let mut collection = Collection::new(org.uuid.clone(), data.Name); collection.save(&conn); @@ -142,8 +142,8 @@ fn post_organization_collections(org_id: String, _headers: AdminHeaders, data: J } #[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")] -fn post_organization_collection_update(org_id: String, col_id: String, _headers: AdminHeaders, data: Json<NewCollectionData>, conn: DbConn) -> JsonResult { - let data: NewCollectionData = data.into_inner(); +fn post_organization_collection_update(org_id: String, col_id: String, _headers: AdminHeaders, data: JsonUpcase<NewCollectionData>, conn: DbConn) -> JsonResult { + let data: NewCollectionData = data.into_inner().data; let org = match Organization::find_by_uuid(&org_id, &conn) { Some(organization) => organization, @@ -159,7 +159,7 @@ fn post_organization_collection_update(org_id: String, col_id: String, _headers: err!("Collection is not owned by organization"); } - collection.name = data.name.clone(); + collection.name = data.Name.clone(); collection.save(&conn); Ok(Json(collection.to_json())) @@ -195,13 +195,13 @@ fn post_organization_collection_delete_user(org_id: String, col_id: String, org_ #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct DeleteCollectionData { - id: String, - orgId: String, + Id: String, + OrgId: String, } #[post("/organizations/<org_id>/collections/<col_id>/delete", data = "<data>")] -fn post_organization_collection_delete(org_id: String, col_id: String, _headers: AdminHeaders, data: Json<DeleteCollectionData>, conn: DbConn) -> EmptyResult { - let _data: DeleteCollectionData = data.into_inner(); +fn post_organization_collection_delete(org_id: String, col_id: String, _headers: AdminHeaders, data: JsonUpcase<DeleteCollectionData>, conn: DbConn) -> EmptyResult { + let _data: DeleteCollectionData = data.into_inner().data; match Collection::find_by_uuid(&col_id, &conn) { None => err!("Collection not found"), @@ -295,18 +295,17 @@ struct CollectionData { #[derive(Deserialize)] #[allow(non_snake_case)] struct InviteData { - emails: Vec<String>, - #[serde(rename = "type")] - type_: NumberOrString, - collections: Vec<CollectionData>, - accessAll: Option<bool>, + Emails: Vec<String>, + Type: NumberOrString, + Collections: Vec<CollectionData>, + AccessAll: Option<bool>, } #[post("/organizations/<org_id>/users/invite", data = "<data>")] -fn send_invite(org_id: String, data: Json<InviteData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - let data: InviteData = data.into_inner(); +fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + let data: InviteData = data.into_inner().data; - let new_type = match UserOrgType::from_str(&data.type_.to_string()) { + let new_type = match UserOrgType::from_str(&data.Type.to_string()) { Some(new_type) => new_type as i32, None => err!("Invalid type") }; @@ -316,7 +315,7 @@ fn send_invite(org_id: String, data: Json<InviteData>, headers: AdminHeaders, co err!("Only Owners can invite Admins or Owners") } - for user_opt in data.emails.iter().map(|email| User::find_by_mail(email, &conn)) { + for user_opt in data.Emails.iter().map(|email| User::find_by_mail(email, &conn)) { match user_opt { None => err!("User email does not exist"), Some(user) => { @@ -326,13 +325,13 @@ fn send_invite(org_id: String, data: Json<InviteData>, headers: AdminHeaders, co } let mut new_user = UserOrganization::new(user.uuid.clone(), org_id.clone()); - let access_all = data.accessAll.unwrap_or(false); + let access_all = data.AccessAll.unwrap_or(false); new_user.access_all = access_all; new_user.type_ = new_type; // If no accessAll, add the collections received if !access_all { - for col in data.collections.iter() { + for col in data.Collections.iter() { match Collection::find_by_uuid_and_org(&col.id, &org_id, &conn) { None => err!("Collection not found in Organization"), Some(collection) => { @@ -354,7 +353,9 @@ fn send_invite(org_id: String, data: Json<InviteData>, headers: AdminHeaders, co } #[post("/organizations/<org_id>/users/<user_id>/confirm", data = "<data>")] -fn confirm_invite(org_id: String, user_id: String, data: Json<Value>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +fn confirm_invite(org_id: String, user_id: String, data: JsonUpcase<Value>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + let data = data.into_inner().data; + let mut user_to_confirm = match UserOrganization::find_by_uuid(&user_id, &conn) { Some(user) => user, None => err!("Failed to find user membership") @@ -401,17 +402,16 @@ fn get_user(org_id: String, user_id: String, _headers: AdminHeaders, conn: DbCon #[derive(Deserialize)] #[allow(non_snake_case)] struct EditUserData { - #[serde(rename = "type")] - type_: NumberOrString, - collections: Vec<CollectionData>, - accessAll: bool, + Type: NumberOrString, + Collections: Vec<CollectionData>, + AccessAll: bool, } #[post("/organizations/<org_id>/users/<user_id>", data = "<data>", rank = 1)] -fn edit_user(org_id: String, user_id: String, data: Json<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - let data: EditUserData = data.into_inner(); +fn edit_user(org_id: String, user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + let data: EditUserData = data.into_inner().data; - let new_type = match UserOrgType::from_str(&data.type_.to_string()) { + let new_type = match UserOrgType::from_str(&data.Type.to_string()) { Some(new_type) => new_type as i32, None => err!("Invalid type") }; @@ -444,7 +444,7 @@ fn edit_user(org_id: String, user_id: String, data: Json<EditUserData>, headers: } } - user_to_edit.access_all = data.accessAll; + user_to_edit.access_all = data.AccessAll; user_to_edit.type_ = new_type; // Delete all the odd collections @@ -456,8 +456,8 @@ fn edit_user(org_id: String, user_id: String, data: Json<EditUserData>, headers: } // If no accessAll, add the collections received - if !data.accessAll { - for col in data.collections.iter() { + if !data.AccessAll { + for col in data.Collections.iter() { match Collection::find_by_uuid_and_org(&col.id, &org_id, &conn) { None => err!("Collection not found in Organization"), Some(collection) => { diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs @@ -6,7 +6,7 @@ use db::DbConn; use crypto; -use api::{PasswordData, JsonResult, NumberOrString}; +use api::{PasswordData, JsonResult, NumberOrString, JsonUpcase}; use auth::Headers; #[get("/two-factor")] @@ -28,10 +28,10 @@ fn get_twofactor(headers: Headers) -> JsonResult { } #[post("/two-factor/get-recover", data = "<data>")] -fn get_recover(data: Json<PasswordData>, headers: Headers) -> JsonResult { - let data: PasswordData = data.into_inner(); +fn get_recover(data: JsonUpcase<PasswordData>, headers: Headers) -> JsonResult { + let data: PasswordData = data.into_inner().data; - if !headers.user.check_valid_password(&data.masterPasswordHash) { + if !headers.user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password"); } @@ -44,30 +44,30 @@ fn get_recover(data: Json<PasswordData>, headers: Headers) -> JsonResult { #[derive(Deserialize)] #[allow(non_snake_case)] struct RecoverTwoFactor { - masterPasswordHash: String, - email: String, - recoveryCode: String, + MasterPasswordHash: String, + Email: String, + RecoveryCode: String, } #[post("/two-factor/recover", data = "<data>")] -fn recover(data: Json<RecoverTwoFactor>, conn: DbConn) -> JsonResult { - let data: RecoverTwoFactor = data.into_inner(); +fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { + let data: RecoverTwoFactor = data.into_inner().data; use db::models::User; // Get the user - let mut user = match User::find_by_mail(&data.email, &conn) { + let mut user = match User::find_by_mail(&data.Email, &conn) { Some(user) => user, None => err!("Username or password is incorrect. Try again.") }; // Check password - if !user.check_valid_password(&data.masterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Username or password is incorrect. Try again.") } // Check if recovery code is correct - if !user.check_valid_recovery_code(&data.recoveryCode) { + if !user.check_valid_recovery_code(&data.RecoveryCode) { err!("Recovery code is incorrect. Try again.") } @@ -79,10 +79,10 @@ fn recover(data: Json<RecoverTwoFactor>, conn: DbConn) -> JsonResult { } #[post("/two-factor/get-authenticator", data = "<data>")] -fn generate_authenticator(data: Json<PasswordData>, headers: Headers) -> JsonResult { - let data: PasswordData = data.into_inner(); +fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers) -> JsonResult { + let data: PasswordData = data.into_inner().data; - if !headers.user.check_valid_password(&data.masterPasswordHash) { + if !headers.user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password"); } @@ -101,17 +101,17 @@ fn generate_authenticator(data: Json<PasswordData>, headers: Headers) -> JsonRes #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct EnableTwoFactorData { - masterPasswordHash: String, - key: String, - token: NumberOrString, + MasterPasswordHash: String, + Key: String, + Token: NumberOrString, } #[post("/two-factor/authenticator", data = "<data>")] -fn activate_authenticator(data: Json<EnableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: EnableTwoFactorData = data.into_inner(); - let password_hash = data.masterPasswordHash; - let key = data.key; - let token = match data.token.to_i32() { +fn activate_authenticator(data: JsonUpcase<EnableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: EnableTwoFactorData = data.into_inner().data; + let password_hash = data.MasterPasswordHash; + let key = data.Key; + let token = match data.Token.to_i32() { Some(n) => n as u64, None => err!("Malformed token") }; @@ -155,15 +155,15 @@ fn activate_authenticator(data: Json<EnableTwoFactorData>, headers: Headers, con #[derive(Deserialize)] #[allow(non_snake_case)] struct DisableTwoFactorData { - masterPasswordHash: String, - #[serde(rename = "type")] - _type: NumberOrString, + MasterPasswordHash: String, + Type: NumberOrString, } #[post("/two-factor/disable", data = "<data>")] -fn disable_authenticator(data: Json<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { - let data: DisableTwoFactorData = data.into_inner(); - let password_hash = data.masterPasswordHash; +fn disable_authenticator(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { + let data: DisableTwoFactorData = data.into_inner().data; + let password_hash = data.MasterPasswordHash; + let _type = data.Type; if !headers.user.check_valid_password(&password_hash) { err!("Invalid password"); diff --git a/src/api/mod.rs b/src/api/mod.rs @@ -15,11 +15,14 @@ use rocket_contrib::Json; type JsonResult = Result<Json, BadRequest<Json>>; type EmptyResult = Result<(), BadRequest<Json>>; +use util; +type JsonUpcase<T> = Json<util::UpCase<T>>; + // Common structs representing JSON data received #[derive(Deserialize)] #[allow(non_snake_case)] struct PasswordData { - masterPasswordHash: String + MasterPasswordHash: String } #[derive(Deserialize, Debug)] diff --git a/src/util.rs b/src/util.rs @@ -124,3 +124,29 @@ const DATETIME_FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S%.6fZ"; pub fn format_date(date: &NaiveDateTime) -> String { date.format(DATETIME_FORMAT).to_string() } + +/// +/// Deserialization methods +/// + +use std::collections::BTreeMap as Map; + +use serde::de::{self, Deserialize, DeserializeOwned, Deserializer}; +use serde_json::Value; + +/// https://github.com/serde-rs/serde/issues/586 +pub fn upcase_deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error> + where T: DeserializeOwned, + D: Deserializer<'de> +{ + let map = Map::<String, Value>::deserialize(deserializer)?; + let lower = map.into_iter().map(|(k, v)| (upcase_first(&k), v)).collect(); + T::deserialize(Value::Object(lower)).map_err(de::Error::custom) +} + +#[derive(PartialEq, Serialize, Deserialize)] +pub struct UpCase<T: DeserializeOwned> { + #[serde(deserialize_with = "upcase_deserialize")] + #[serde(flatten)] + pub data: T, +}