1
0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2025-05-29 06:53:57 +00:00

Fixed cipher import, created missing data structs instead of using generic Value, and fixed some warnings

This commit is contained in:
Daniel García 2018-02-23 00:38:54 +01:00
parent c8b45f5fe5
commit dfefbf1f31
13 changed files with 190 additions and 131 deletions

View file

@ -1,9 +1,9 @@
use rocket_contrib::{Json, Value};
use rocket_contrib::Json;
use db::DbConn;
use db::models::*;
use api::{JsonResult, EmptyResult};
use api::{PasswordData, JsonResult, EmptyResult};
use auth::Headers;
use CONFIG;
@ -33,15 +33,12 @@ fn register(data: Json<RegisterData>, conn: DbConn) -> EmptyResult {
if !CONFIG.signups_allowed {
err!(format!("Signups not allowed"))
}
println!("DEBUG - {:#?}", data);
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 {
@ -81,32 +78,36 @@ fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult
Ok(Json(user.to_json()))
}
#[post("/accounts/password", data = "<data>")]
fn post_password(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let key = data["key"].as_str().unwrap();
let password_hash = data["masterPasswordHash"].as_str().unwrap();
let new_password_hash = data["newMasterPasswordHash"].as_str().unwrap();
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct ChangePassData {
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();
let mut user = headers.user;
if !user.check_valid_password(password_hash) {
if !user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password")
}
user.set_password(new_password_hash);
user.key = key.to_string();
user.set_password(&data.newMasterPasswordHash);
user.key = data.key;
user.save(&conn);
Ok(())
}
#[post("/accounts/security-stamp", data = "<data>")]
fn post_sstamp(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
fn post_sstamp(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordData = data.into_inner();
let mut user = headers.user;
if !user.check_valid_password(password_hash) {
if !user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password")
}
@ -116,34 +117,39 @@ fn post_sstamp(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult
Ok(())
}
#[post("/accounts/email-token", data = "<data>")]
fn post_email(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
let new_email = data["newEmail"].as_str().unwrap();
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct ChangeEmailData {
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();
let mut user = headers.user;
if !user.check_valid_password(password_hash) {
if !user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password")
}
if User::find_by_mail(new_email, &conn).is_some() {
if User::find_by_mail(&data.newEmail, &conn).is_some() {
err!("Email already in use");
}
user.email = new_email.to_string();
user.email = data.newEmail;
user.save(&conn);
Ok(())
}
#[post("/accounts/delete", data = "<data>")]
fn delete_account(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
fn delete_account(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordData = data.into_inner();
let user = headers.user;
if !user.check_valid_password(password_hash) {
if !user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password")
}

View file

@ -16,7 +16,7 @@ use db::models::*;
use util;
use crypto;
use api::{self, JsonResult, EmptyResult};
use api::{self, PasswordData, JsonResult, EmptyResult};
use auth::Headers;
use CONFIG;
@ -74,7 +74,9 @@ fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
struct CipherData {
#[serde(rename = "type")]
type_: i32,
// Folder id is not included in import
folderId: Option<String>,
// TODO: Some of these might appear all the time, no need for Option
organizationId: Option<String>,
name: Option<String>,
notes: Option<String>,
@ -182,38 +184,62 @@ fn copy_values(from: &Value, to: &mut Value) -> bool {
true
}
use super::folders::FolderData;
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct ImportData {
ciphers: Vec<CipherData>,
folders: Vec<FolderData>,
folderRelationships: Vec<RelationsData>,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct RelationsData {
// Cipher id
key: u32,
// Folder id
value: u32,
}
#[post("/ciphers/import", data = "<data>")]
fn post_ciphers_import(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: Value = data.into_inner();
let folders_value = data["folders"].as_array().unwrap();
let ciphers_value = data["ciphers"].as_array().unwrap();
let relations_value = data["folderRelationships"].as_array().unwrap();
fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: ImportData = data.into_inner();
// Read and create the folders
let folders: Vec<_> = folders_value.iter().map(|f| {
let name = f["name"].as_str().unwrap().to_string();
let mut folder = Folder::new(headers.user.uuid.clone(), name);
let folders: Vec<_> = data.folders.iter().map(|folder| {
let mut folder = Folder::new(headers.user.uuid.clone(), folder.name.clone());
folder.save(&conn);
folder
}).collect();
// Read the relations between folders and ciphers
let relations = relations_value.iter().map(|r| r["value"].as_u64().unwrap() as usize);
use std::collections::HashMap;
let mut relations_map = HashMap::new();
for relation in data.folderRelationships {
relations_map.insert(relation.key, relation.value);
}
// Read and create the ciphers
use serde::Deserialize;
ciphers_value.iter().zip(relations).map(|(c, fp)| {
let folder_uuid = folders[fp].uuid.clone();
let data = CipherData::deserialize(c.clone()).unwrap();
let mut index = 0;
for cipher_data in data.ciphers {
let folder_uuid = relations_map.get(&index)
.map(|i| folders[*i as usize].uuid.clone());
let user_uuid = headers.user.uuid.clone();
let favorite = data.favorite.unwrap_or(false);
let mut cipher = Cipher::new(user_uuid, data.type_, favorite);
let favorite = cipher_data.favorite.unwrap_or(false);
let mut cipher = Cipher::new(user_uuid, cipher_data.type_, favorite);
if update_cipher_from_data(&mut cipher, data, &headers, &conn).is_err() { return; }
if update_cipher_from_data(&mut cipher, cipher_data, &headers, &conn).is_err() { err!("Error creating cipher") }
cipher.folder_uuid = folder_uuid;
cipher.save(&conn);
});
index += 1;
}
Ok(())
}
@ -279,7 +305,7 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers
let attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
println!("Attachment: {:#?}", attachment);
attachment.save(&conn);
});
}).expect("Error processing multipart data");
Ok(Json(cipher.to_json(&headers.host, &conn)))
}
@ -340,13 +366,14 @@ fn delete_cipher(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
Ok(())
}
#[post("/ciphers/delete", data = "<data>")]
fn delete_all(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
#[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;
let user = headers.user;
if !user.check_valid_password(password_hash) {
if !user.check_valid_password(&password_hash) {
err!("Invalid password")
}

View file

@ -32,15 +32,16 @@ fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
Ok(Json(folder.to_json()))
}
#[derive(Deserialize)]
pub struct FolderData {
pub name: String
}
#[post("/folders", data = "<data>")]
fn post_folders(data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
let name = &data["name"].as_str();
fn post_folders(data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: FolderData = data.into_inner();
if name.is_none() {
err!("Invalid name")
}
let mut folder = Folder::new(headers.user.uuid.clone(), name.unwrap().into());
let mut folder = Folder::new(headers.user.uuid.clone(), data.name);
folder.save(&conn);
@ -48,12 +49,14 @@ fn post_folders(data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult
}
#[post("/folders/<uuid>", data = "<data>")]
fn post_folder(uuid: String, data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
fn post_folder(uuid: String, data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
put_folder(uuid, data, headers, conn)
}
#[put("/folders/<uuid>", data = "<data>")]
fn put_folder(uuid: String, data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
fn put_folder(uuid: String, data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: FolderData = data.into_inner();
let mut folder = match Folder::find_by_uuid(&uuid, &conn) {
Some(folder) => folder,
_ => err!("Invalid folder")
@ -63,13 +66,7 @@ fn put_folder(uuid: String, data: Json<Value>, headers: Headers, conn: DbConn) -
err!("Folder belongs to another user")
}
let name = &data["name"].as_str();
if name.is_none() {
err!("Invalid name")
}
folder.name = name.unwrap().into();
folder.name = data.name;
folder.save(&conn);

View file

@ -67,7 +67,7 @@ pub fn routes() -> Vec<Route> {
use rocket::Route;
use rocket_contrib::{Json, Value};
use rocket_contrib::Json;
use db::DbConn;
@ -84,14 +84,6 @@ fn put_device_token(uuid: String, conn: DbConn) -> JsonResult {
err!("Not implemented")
}
#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct EquivDomainData {
ExcludedGlobalEquivalentDomains: Option<Vec<i32>>,
EquivalentDomains: Option<Vec<Vec<String>>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[allow(non_snake_case)]
struct GlobalDomain {
@ -123,6 +115,14 @@ fn get_eq_domains(headers: Headers) -> JsonResult {
})))
}
#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct EquivDomainData {
ExcludedGlobalEquivalentDomains: Option<Vec<i32>>,
EquivalentDomains: Option<Vec<Vec<String>>>,
}
#[post("/settings/domains", data = "<data>")]
fn post_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: EquivDomainData = data.into_inner();

View file

@ -1,3 +1,5 @@
#![allow(unused_imports)]
use rocket_contrib::{Json, Value};
use db::DbConn;

View file

@ -4,13 +4,11 @@ use data_encoding::BASE32;
use db::DbConn;
use util;
use crypto;
use api::{JsonResult, EmptyResult};
use api::{PasswordData, JsonResult};
use auth::Headers;
#[get("/two-factor")]
fn get_twofactor(headers: Headers) -> JsonResult {
let data = if headers.user.totp_secret.is_none() {
@ -30,10 +28,10 @@ fn get_twofactor(headers: Headers) -> JsonResult {
}
#[post("/two-factor/get-recover", data = "<data>")]
fn get_recover(data: Json<Value>, headers: Headers) -> JsonResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
fn get_recover(data: Json<PasswordData>, headers: Headers) -> JsonResult {
let data: PasswordData = data.into_inner();
if !headers.user.check_valid_password(password_hash) {
if !headers.user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password");
}
@ -43,29 +41,33 @@ fn get_recover(data: Json<Value>, headers: Headers) -> JsonResult {
})))
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct RecoverTwoFactor {
masterPasswordHash: String,
email: String,
recoveryCode: String,
}
#[post("/two-factor/recover", data = "<data>")]
fn recover(data: Json<Value>, conn: DbConn) -> JsonResult {
println!("{:#?}", data);
fn recover(data: Json<RecoverTwoFactor>, conn: DbConn) -> JsonResult {
let data: RecoverTwoFactor = data.into_inner();
use db::models::User;
// Get the user
let username = data["email"].as_str().unwrap();
let mut user = match User::find_by_mail(username, &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
let password = data["masterPasswordHash"].as_str().unwrap();
if !user.check_valid_password(password) {
if !user.check_valid_password(&data.masterPasswordHash) {
err!("Username or password is incorrect. Try again.")
}
// Check if recovery code is correct
let recovery_code = data["recoveryCode"].as_str().unwrap();
if !user.check_valid_recovery_code(recovery_code) {
if !user.check_valid_recovery_code(&data.recoveryCode) {
err!("Recovery code is incorrect. Try again.")
}
@ -77,10 +79,10 @@ fn recover(data: Json<Value>, conn: DbConn) -> JsonResult {
}
#[post("/two-factor/get-authenticator", data = "<data>")]
fn generate_authenticator(data: Json<Value>, headers: Headers) -> JsonResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
fn generate_authenticator(data: Json<PasswordData>, headers: Headers) -> JsonResult {
let data: PasswordData = data.into_inner();
if !headers.user.check_valid_password(password_hash) {
if !headers.user.check_valid_password(&data.masterPasswordHash) {
err!("Invalid password");
}
@ -96,15 +98,24 @@ fn generate_authenticator(data: Json<Value>, headers: Headers) -> JsonResult {
})))
}
#[post("/two-factor/authenticator", data = "<data>")]
fn activate_authenticator(data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
let password_hash = data["masterPasswordHash"].as_str().unwrap();
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct EnableTwoFactorData {
masterPasswordHash: String,
key: String,
token: u64,
}
if !headers.user.check_valid_password(password_hash) {
#[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 = data.token;
if !headers.user.check_valid_password(&password_hash) {
err!("Invalid password");
}
let token = data["token"].as_str();
let key = data["key"].as_str().unwrap();
// Validate key as base32 and 20 bytes length
let decoded_key: Vec<u8> = match BASE32.decode(key.as_bytes()) {
@ -121,7 +132,7 @@ fn activate_authenticator(data: Json<Value>, headers: Headers, conn: DbConn) ->
user.totp_secret = Some(key.to_uppercase());
// Validate the token provided with the key
if !user.check_totp_code(util::parse_option_string(token)) {
if !user.check_totp_code(Some(token)) {
err!("Invalid totp code")
}
@ -138,12 +149,20 @@ fn activate_authenticator(data: Json<Value>, headers: Headers, conn: DbConn) ->
})))
}
#[post("/two-factor/disable", data = "<data>")]
fn disable_authenticator(data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
let _type = &data["type"];
let password_hash = data["masterPasswordHash"].as_str().unwrap();
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct DisableTwoFactorData {
masterPasswordHash: String,
#[serde(rename = "type")]
type_: u32,
}
if !headers.user.check_valid_password(password_hash) {
#[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;
if !headers.user.check_valid_password(&password_hash) {
err!("Invalid password");
}

View file

@ -10,7 +10,7 @@ use db::models::*;
use util;
use api::{JsonResult, EmptyResult};
use api::JsonResult;
pub fn routes() -> Vec<Route> {
routes![ login]

View file

@ -11,5 +11,13 @@ pub use self::web::routes as web_routes;
use rocket::response::status::BadRequest;
use rocket_contrib::Json;
// Type aliases for API methods results
type JsonResult = Result<Json, BadRequest<Json>>;
type EmptyResult = Result<(), BadRequest<Json>>;
// Common structs representing JSON data received
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct PasswordData {
masterPasswordHash: String
}

View file

@ -124,7 +124,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
// Check JWT token is valid and get device and user from it
let claims: JWTClaims = match decode_jwt(access_token) {
Ok(claims) => claims,
Err(msg) => err_handler!("Invalid claim")
Err(_) => err_handler!("Invalid claim")
};
let device_uuid = claims.device;

View file

@ -30,7 +30,7 @@ pub fn get_random_64() -> Vec<u8> {
pub fn get_random(mut array: Vec<u8>) -> Vec<u8> {
use ring::rand::{SecureRandom, SystemRandom};
SystemRandom::new().fill(&mut array);
SystemRandom::new().fill(&mut array).expect("Error generating random values");
array
}

View file

@ -1,4 +1,4 @@
#![allow(unused)]
#![allow(unused_variables, dead_code)]
#![feature(plugin, custom_derive)]
#![cfg_attr(test, plugin(stainless))]
@ -31,9 +31,7 @@ extern crate lazy_static;
use std::{io, env};
use rocket::{Data, Request, Rocket};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::Rocket;
#[macro_use]
mod util;

View file

@ -61,7 +61,9 @@ pub fn delete_file(path: &str) -> bool {
let res = fs::remove_file(path).is_ok();
if let Some(parent) = Path::new(path).parent() {
fs::remove_dir(parent); // Only removes if the directory is empty
// If the directory isn't empty, this returns an error, which we ignore
// We only want to delete the folder if it's empty
fs::remove_dir(parent).ok();
}
res