mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-05-16 01:36:39 +00:00
Updated Cipher API with breaking changes, and included backwards compatibility
This commit is contained in:
80
Cargo.lock
generated
80
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@ r2d2-diesel = "1.0.0"
|
||||
ring = { version = "0.11.0", features = ["rsa_signing"] }
|
||||
|
||||
# UUID generation
|
||||
uuid = { version = "0.6.0", features = ["v4"] }
|
||||
uuid = { version = "0.6.1", features = ["v4"] }
|
||||
|
||||
# Date and time library for Rust
|
||||
chrono = "0.4.0"
|
||||
|
@ -37,6 +37,9 @@ CREATE TABLE ciphers (
|
||||
folder_uuid TEXT REFERENCES folders (uuid),
|
||||
organization_uuid TEXT,
|
||||
type INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
fields TEXT,
|
||||
data TEXT NOT NULL,
|
||||
favorite BOOLEAN NOT NULL
|
||||
);
|
||||
|
@ -72,22 +72,30 @@ fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
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>,
|
||||
favorite: Option<bool>,
|
||||
|
||||
/*
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4
|
||||
*/
|
||||
#[serde(rename = "type")]
|
||||
type_: i32,
|
||||
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>,
|
||||
|
||||
fields: Option<Vec<Value>>,
|
||||
favorite: bool,
|
||||
}
|
||||
|
||||
#[post("/ciphers", data = "<data>")]
|
||||
@ -95,8 +103,8 @@ fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonR
|
||||
let data: CipherData = data.into_inner();
|
||||
|
||||
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 = data.favorite;
|
||||
let mut cipher = Cipher::new(user_uuid, data.type_, data.name.clone(), favorite);
|
||||
|
||||
update_cipher_from_data(&mut cipher, data, &headers, &conn)?;
|
||||
cipher.save(&conn);
|
||||
@ -118,16 +126,42 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head
|
||||
cipher.folder_uuid = Some(folder_id);
|
||||
}
|
||||
|
||||
if let Some(org_id) = data.organizationId {
|
||||
if let org_id @ Some(_) = data.organizationId {
|
||||
// TODO: Check if user in org
|
||||
cipher.organization_uuid = Some(org_id);
|
||||
cipher.organization_uuid = org_id;
|
||||
}
|
||||
|
||||
// TODO: ******* Backwards compat start **********
|
||||
// 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
|
||||
});
|
||||
|
||||
if let Some(ref fields) = data.fields {
|
||||
values["Fields"] = Value::Array(fields.as_array().unwrap().iter().map(|f| {
|
||||
let mut value = json!({});
|
||||
|
||||
// Copy every field object and change the names to the correct case
|
||||
copy_values(&f, &mut value);
|
||||
|
||||
value
|
||||
}).collect());
|
||||
} else {
|
||||
values["Fields"] = Value::Null;
|
||||
}
|
||||
// TODO: ******* Backwards compat end **********
|
||||
|
||||
if let notes @ Some(_) = data.notes {
|
||||
cipher.notes = notes;
|
||||
}
|
||||
|
||||
if let Some(fields) = data.fields {
|
||||
use serde_json::to_string;
|
||||
cipher.fields = to_string(&fields).ok();
|
||||
}
|
||||
|
||||
let type_data_opt = match data.type_ {
|
||||
1 => data.login,
|
||||
2 => data.secureNote,
|
||||
@ -146,31 +180,11 @@ fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &Head
|
||||
err!("Data invalid")
|
||||
}
|
||||
|
||||
if let Some(ref fields) = data.fields {
|
||||
values["Fields"] = Value::Array(fields.iter().map(|f| {
|
||||
let mut value = empty_map_value();
|
||||
|
||||
// Copy every field object and change the names to the correct case
|
||||
copy_values(&f, &mut value);
|
||||
|
||||
value
|
||||
}).collect());
|
||||
} else {
|
||||
values["Fields"] = Value::Null;
|
||||
}
|
||||
|
||||
cipher.data = values.to_string();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn empty_map_value() -> Value {
|
||||
use std::collections::BTreeMap;
|
||||
use serde_json;
|
||||
|
||||
serde_json::to_value(BTreeMap::<String, Value>::new()).unwrap()
|
||||
}
|
||||
|
||||
fn copy_values(from: &Value, to: &mut Value) -> bool {
|
||||
let map = match from.as_object() {
|
||||
Some(map) => map,
|
||||
@ -230,8 +244,8 @@ fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbConn) -
|
||||
.map(|i| folders[*i as usize].uuid.clone());
|
||||
|
||||
let user_uuid = headers.user.uuid.clone();
|
||||
let favorite = cipher_data.favorite.unwrap_or(false);
|
||||
let mut cipher = Cipher::new(user_uuid, cipher_data.type_, favorite);
|
||||
let favorite = cipher_data.favorite;
|
||||
let mut cipher = Cipher::new(user_uuid, cipher_data.type_, cipher_data.name.clone(), favorite);
|
||||
|
||||
if update_cipher_from_data(&mut cipher, cipher_data, &headers, &conn).is_err() { err!("Error creating cipher") }
|
||||
|
||||
@ -262,7 +276,7 @@ fn put_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbCo
|
||||
err!("Cipher is not owned by user")
|
||||
}
|
||||
|
||||
cipher.favorite = data.favorite.unwrap_or(false);
|
||||
cipher.favorite = data.favorite;
|
||||
|
||||
update_cipher_from_data(&mut cipher, data, &headers, &conn)?;
|
||||
cipher.save(&conn);
|
||||
@ -366,6 +380,14 @@ fn delete_cipher(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[post("/ciphers/delete", data = "<data>")]
|
||||
fn delete_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
let data: Value = data.into_inner();
|
||||
|
||||
println!("{:#?}", data);
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[post("/ciphers/purge", data = "<data>")]
|
||||
fn delete_all(data: Json<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
let data: PasswordData = data.into_inner();
|
||||
|
@ -32,8 +32,9 @@ pub fn routes() -> Vec<Route> {
|
||||
delete_attachment,
|
||||
post_cipher,
|
||||
put_cipher,
|
||||
delete_cipher,
|
||||
delete_cipher_post,
|
||||
delete_cipher,
|
||||
delete_cipher_selected,
|
||||
delete_all,
|
||||
|
||||
get_folders,
|
||||
|
@ -18,15 +18,25 @@ pub struct Cipher {
|
||||
pub folder_uuid: Option<String>,
|
||||
pub organization_uuid: Option<String>,
|
||||
|
||||
/*
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4
|
||||
*/
|
||||
pub type_: i32,
|
||||
pub name: String,
|
||||
pub notes: Option<String>,
|
||||
pub fields: Option<String>,
|
||||
|
||||
pub data: String,
|
||||
|
||||
pub favorite: bool,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
impl Cipher {
|
||||
pub fn new(user_uuid: String, type_: i32, favorite: bool) -> Self {
|
||||
pub fn new(user_uuid: String, type_: i32, name: String, favorite: bool) -> Self {
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
Self {
|
||||
@ -40,6 +50,10 @@ impl Cipher {
|
||||
|
||||
type_,
|
||||
favorite,
|
||||
name,
|
||||
|
||||
notes: None,
|
||||
fields: None,
|
||||
|
||||
data: String::new(),
|
||||
}
|
||||
@ -58,12 +72,25 @@ impl Cipher {
|
||||
use util::format_date;
|
||||
use super::Attachment;
|
||||
|
||||
let data_json: JsonValue = serde_json::from_str(&self.data).unwrap();
|
||||
|
||||
let attachments = Attachment::find_by_cipher(&self.uuid, conn);
|
||||
let attachments_json: Vec<JsonValue> = attachments.iter().map(|c| c.to_json(host)).collect();
|
||||
|
||||
json!({
|
||||
let fields_json: JsonValue = if let Some(ref fields) = self.fields {
|
||||
serde_json::from_str(fields).unwrap()
|
||||
} else { JsonValue::Null };
|
||||
|
||||
let mut data_json: JsonValue = serde_json::from_str(&self.data).unwrap();
|
||||
|
||||
// TODO: ******* Backwards compat start **********
|
||||
// To remove backwards compatibility, just remove this entire section
|
||||
// and remove the compat code from ciphers::update_cipher_from_data
|
||||
if self.type_ == 1 && data_json["Uris"].is_array() {
|
||||
let uri = data_json["Uris"][0]["uri"].clone();
|
||||
data_json["Uri"] = uri;
|
||||
}
|
||||
// TODO: ******* Backwards compat end **********
|
||||
|
||||
let mut json_object = json!({
|
||||
"Id": self.uuid,
|
||||
"Type": self.type_,
|
||||
"RevisionDate": format_date(&self.updated_at),
|
||||
@ -72,10 +99,27 @@ impl Cipher {
|
||||
"OrganizationId": "",
|
||||
"Attachments": attachments_json,
|
||||
"OrganizationUseTotp": false,
|
||||
|
||||
"Name": self.name,
|
||||
"Notes": self.notes,
|
||||
"Fields": fields_json,
|
||||
|
||||
"Data": data_json,
|
||||
|
||||
"Object": "cipher",
|
||||
"Edit": true,
|
||||
})
|
||||
});
|
||||
|
||||
let key = match self.type_ {
|
||||
1 => "Login",
|
||||
2 => "SecureNote",
|
||||
3 => "Card",
|
||||
4 => "Identity",
|
||||
_ => panic!("Wrong type"),
|
||||
};
|
||||
|
||||
json_object[key] = data_json;
|
||||
json_object
|
||||
}
|
||||
|
||||
pub fn save(&mut self, conn: &DbConn) -> bool {
|
||||
|
@ -17,6 +17,9 @@ table! {
|
||||
organization_uuid -> Nullable<Text>,
|
||||
#[sql_name = "type"]
|
||||
type_ -> Integer,
|
||||
name -> Text,
|
||||
notes -> Nullable<Text>,
|
||||
fields -> Nullable<Text>,
|
||||
data -> Text,
|
||||
favorite -> Bool,
|
||||
}
|
||||
|
Reference in New Issue
Block a user