mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-07-27 06:14:29 +00:00
Change API and structs to camelCase (#4386)
* Change API inputs/outputs and structs to camelCase * Fix fields and password history * Use convert_json_key_lcase_first * Make sends lowercase * Update admin and templates * Update org revoke * Fix sends expecting size to be a string on mobile * Convert two-factor providers to string
This commit is contained in:
parent
8f05a90b96
commit
a2bf8def2a
37 changed files with 1950 additions and 2003 deletions
|
@ -12,7 +12,7 @@ use serde_json::Value;
|
|||
|
||||
use crate::util::NumberOrString;
|
||||
use crate::{
|
||||
api::{self, core::log_event, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordOrOtpData, UpdateType},
|
||||
api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType},
|
||||
auth::Headers,
|
||||
crypto,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
|
@ -141,15 +141,15 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
|
|||
};
|
||||
|
||||
Json(json!({
|
||||
"Profile": user_json,
|
||||
"Folders": folders_json,
|
||||
"Collections": collections_json,
|
||||
"Policies": policies_json,
|
||||
"Ciphers": ciphers_json,
|
||||
"Domains": domains_json,
|
||||
"Sends": sends_json,
|
||||
"profile": user_json,
|
||||
"folders": folders_json,
|
||||
"collections": collections_json,
|
||||
"policies": policies_json,
|
||||
"ciphers": ciphers_json,
|
||||
"domains": domains_json,
|
||||
"sends": sends_json,
|
||||
"unofficialServer": true,
|
||||
"Object": "sync"
|
||||
"object": "sync"
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -167,9 +167,9 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
|||
}
|
||||
|
||||
Json(json!({
|
||||
"Data": ciphers_json,
|
||||
"Object": "list",
|
||||
"ContinuationToken": null
|
||||
"data": ciphers_json,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -198,17 +198,17 @@ async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonR
|
|||
get_cipher(uuid, headers, conn).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CipherData {
|
||||
// Id is optional as it is included only in bulk share
|
||||
pub Id: Option<String>,
|
||||
pub id: Option<String>,
|
||||
// Folder id is not included in import
|
||||
FolderId: Option<String>,
|
||||
folder_id: Option<String>,
|
||||
// TODO: Some of these might appear all the time, no need for Option
|
||||
pub OrganizationId: Option<String>,
|
||||
pub organization_id: Option<String>,
|
||||
|
||||
Key: Option<String>,
|
||||
key: Option<String>,
|
||||
|
||||
/*
|
||||
Login = 1,
|
||||
|
@ -216,27 +216,27 @@ pub struct CipherData {
|
|||
Card = 3,
|
||||
Identity = 4
|
||||
*/
|
||||
pub Type: i32,
|
||||
pub Name: String,
|
||||
pub Notes: Option<String>,
|
||||
Fields: Option<Value>,
|
||||
pub r#type: i32,
|
||||
pub name: String,
|
||||
pub 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>,
|
||||
secure_note: Option<Value>,
|
||||
card: Option<Value>,
|
||||
identity: Option<Value>,
|
||||
|
||||
Favorite: Option<bool>,
|
||||
Reprompt: Option<i32>,
|
||||
favorite: Option<bool>,
|
||||
reprompt: Option<i32>,
|
||||
|
||||
PasswordHistory: Option<Value>,
|
||||
password_history: Option<Value>,
|
||||
|
||||
// These are used during key rotation
|
||||
// 'Attachments' is unused, contains map of {id: filename}
|
||||
#[serde(rename = "Attachments")]
|
||||
_Attachments: Option<Value>,
|
||||
Attachments2: Option<HashMap<String, Attachments2Data>>,
|
||||
#[allow(dead_code)]
|
||||
attachments: Option<Value>,
|
||||
attachments2: Option<HashMap<String, Attachments2Data>>,
|
||||
|
||||
// The revision datetime (in ISO 8601 format) of the client's local copy
|
||||
// of the cipher. This is used to prevent a client from updating a cipher
|
||||
|
@ -244,31 +244,26 @@ pub struct CipherData {
|
|||
// loss. It's not an error when no value is provided; this can happen
|
||||
// when using older client versions, or if the operation doesn't involve
|
||||
// updating an existing cipher.
|
||||
LastKnownRevisionDate: Option<String>,
|
||||
last_known_revision_date: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PartialCipherData {
|
||||
FolderId: Option<String>,
|
||||
Favorite: bool,
|
||||
folder_id: Option<String>,
|
||||
favorite: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Attachments2Data {
|
||||
FileName: String,
|
||||
Key: String,
|
||||
file_name: String,
|
||||
key: String,
|
||||
}
|
||||
|
||||
/// Called when an org admin clones an org cipher.
|
||||
#[post("/ciphers/admin", data = "<data>")]
|
||||
async fn post_ciphers_admin(
|
||||
data: JsonUpcase<ShareCipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
async fn post_ciphers_admin(data: Json<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
post_ciphers_create(data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -277,25 +272,25 @@ async fn post_ciphers_admin(
|
|||
/// `organizationId` is null.
|
||||
#[post("/ciphers/create", data = "<data>")]
|
||||
async fn post_ciphers_create(
|
||||
data: JsonUpcase<ShareCipherData>,
|
||||
data: Json<ShareCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
let mut data: ShareCipherData = data.into_inner().data;
|
||||
let mut data: ShareCipherData = data.into_inner();
|
||||
|
||||
// Check if there are one more more collections selected when this cipher is part of an organization.
|
||||
// err if this is not the case before creating an empty cipher.
|
||||
if data.Cipher.OrganizationId.is_some() && data.CollectionIds.is_empty() {
|
||||
if data.cipher.organization_id.is_some() && data.collection_ids.is_empty() {
|
||||
err!("You must select at least one collection.");
|
||||
}
|
||||
|
||||
// This check is usually only needed in update_cipher_from_data(), but we
|
||||
// need it here as well to avoid creating an empty cipher in the call to
|
||||
// cipher.save() below.
|
||||
enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &mut conn).await?;
|
||||
enforce_personal_ownership_policy(Some(&data.cipher), &headers, &mut conn).await?;
|
||||
|
||||
let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone());
|
||||
let mut cipher = Cipher::new(data.cipher.r#type, data.cipher.name.clone());
|
||||
cipher.user_uuid = Some(headers.user.uuid.clone());
|
||||
cipher.save(&mut conn).await?;
|
||||
|
||||
|
@ -305,23 +300,23 @@ async fn post_ciphers_create(
|
|||
// the current time, so the stale data check will end up failing down the
|
||||
// line. Since this function only creates new ciphers (whether by cloning
|
||||
// or otherwise), we can just ignore this field entirely.
|
||||
data.Cipher.LastKnownRevisionDate = None;
|
||||
data.cipher.last_known_revision_date = None;
|
||||
|
||||
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
/// Called when creating a new user-owned cipher.
|
||||
#[post("/ciphers", data = "<data>")]
|
||||
async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
let mut data: CipherData = data.into_inner().data;
|
||||
async fn post_ciphers(data: Json<CipherData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
let mut data: CipherData = data.into_inner();
|
||||
|
||||
// The web/browser clients set this field to null as expected, but the
|
||||
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
|
||||
// which results in a warning message being logged. This field isn't
|
||||
// needed when creating a new cipher, so just ignore it unconditionally.
|
||||
data.LastKnownRevisionDate = None;
|
||||
data.last_known_revision_date = None;
|
||||
|
||||
let mut cipher = Cipher::new(data.Type, data.Name.clone());
|
||||
let mut cipher = Cipher::new(data.r#type, data.name.clone());
|
||||
update_cipher_from_data(&mut cipher, data, &headers, None, &mut conn, &nt, UpdateType::SyncCipherCreate).await?;
|
||||
|
||||
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
|
||||
|
@ -339,7 +334,7 @@ async fn enforce_personal_ownership_policy(
|
|||
headers: &Headers,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
if data.is_none() || data.unwrap().OrganizationId.is_none() {
|
||||
if data.is_none() || data.unwrap().organization_id.is_none() {
|
||||
let user_uuid = &headers.user.uuid;
|
||||
let policy_type = OrgPolicyType::PersonalOwnership;
|
||||
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, None, conn).await {
|
||||
|
@ -363,7 +358,7 @@ pub async fn update_cipher_from_data(
|
|||
// Check that the client isn't updating an existing cipher with stale data.
|
||||
// And only perform this check when not importing ciphers, else the date/time check will fail.
|
||||
if ut != UpdateType::None {
|
||||
if let Some(dt) = data.LastKnownRevisionDate {
|
||||
if let Some(dt) = data.last_known_revision_date {
|
||||
match NaiveDateTime::parse_from_str(&dt, "%+") {
|
||||
// ISO 8601 format
|
||||
Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
|
||||
|
@ -375,20 +370,20 @@ pub async fn update_cipher_from_data(
|
|||
}
|
||||
}
|
||||
|
||||
if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.OrganizationId {
|
||||
if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.organization_id {
|
||||
err!("Organization mismatch. Please resync the client before updating the cipher")
|
||||
}
|
||||
|
||||
if let Some(note) = &data.Notes {
|
||||
if let Some(note) = &data.notes {
|
||||
if note.len() > 10_000 {
|
||||
err!("The field Notes exceeds the maximum encrypted value length of 10000 characters.")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this cipher is being transferred from a personal to an organization vault
|
||||
let transfer_cipher = cipher.organization_uuid.is_none() && data.OrganizationId.is_some();
|
||||
let transfer_cipher = cipher.organization_uuid.is_none() && data.organization_id.is_some();
|
||||
|
||||
if let Some(org_id) = data.OrganizationId {
|
||||
if let Some(org_id) = data.organization_id {
|
||||
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, conn).await {
|
||||
None => err!("You don't have permission to add item to organization"),
|
||||
Some(org_user) => {
|
||||
|
@ -412,7 +407,7 @@ pub async fn update_cipher_from_data(
|
|||
cipher.user_uuid = Some(headers.user.uuid.clone());
|
||||
}
|
||||
|
||||
if let Some(ref folder_id) = data.FolderId {
|
||||
if let Some(ref folder_id) = data.folder_id {
|
||||
match Folder::find_by_uuid(folder_id, conn).await {
|
||||
Some(folder) => {
|
||||
if folder.user_uuid != headers.user.uuid {
|
||||
|
@ -424,7 +419,7 @@ pub async fn update_cipher_from_data(
|
|||
}
|
||||
|
||||
// Modify attachments name and keys when rotating
|
||||
if let Some(attachments) = data.Attachments2 {
|
||||
if let Some(attachments) = data.attachments2 {
|
||||
for (id, attachment) in attachments {
|
||||
let mut saved_att = match Attachment::find_by_id(&id, conn).await {
|
||||
Some(att) => att,
|
||||
|
@ -445,8 +440,8 @@ pub async fn update_cipher_from_data(
|
|||
break;
|
||||
}
|
||||
|
||||
saved_att.akey = Some(attachment.Key);
|
||||
saved_att.file_name = attachment.FileName;
|
||||
saved_att.akey = Some(attachment.key);
|
||||
saved_att.file_name = attachment.file_name;
|
||||
|
||||
saved_att.save(conn).await?;
|
||||
}
|
||||
|
@ -460,44 +455,44 @@ pub async fn update_cipher_from_data(
|
|||
fn _clean_cipher_data(mut json_data: Value) -> Value {
|
||||
if json_data.is_array() {
|
||||
json_data.as_array_mut().unwrap().iter_mut().for_each(|ref mut f| {
|
||||
f.as_object_mut().unwrap().remove("Response");
|
||||
f.as_object_mut().unwrap().remove("response");
|
||||
});
|
||||
};
|
||||
json_data
|
||||
}
|
||||
|
||||
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.r#type {
|
||||
1 => data.login,
|
||||
2 => data.secure_note,
|
||||
3 => data.card,
|
||||
4 => data.identity,
|
||||
_ => err!("Invalid type"),
|
||||
};
|
||||
|
||||
let type_data = match type_data_opt {
|
||||
Some(mut data) => {
|
||||
// Remove the 'Response' key from the base object.
|
||||
data.as_object_mut().unwrap().remove("Response");
|
||||
data.as_object_mut().unwrap().remove("response");
|
||||
// Remove the 'Response' key from every Uri.
|
||||
if data["Uris"].is_array() {
|
||||
data["Uris"] = _clean_cipher_data(data["Uris"].clone());
|
||||
if data["uris"].is_array() {
|
||||
data["uris"] = _clean_cipher_data(data["uris"].clone());
|
||||
}
|
||||
data
|
||||
}
|
||||
None => err!("Data missing"),
|
||||
};
|
||||
|
||||
cipher.key = data.Key;
|
||||
cipher.name = data.Name;
|
||||
cipher.notes = data.Notes;
|
||||
cipher.fields = data.Fields.map(|f| _clean_cipher_data(f).to_string());
|
||||
cipher.key = data.key;
|
||||
cipher.name = data.name;
|
||||
cipher.notes = data.notes;
|
||||
cipher.fields = data.fields.map(|f| _clean_cipher_data(f).to_string());
|
||||
cipher.data = type_data.to_string();
|
||||
cipher.password_history = data.PasswordHistory.map(|f| f.to_string());
|
||||
cipher.reprompt = data.Reprompt;
|
||||
cipher.password_history = data.password_history.map(|f| f.to_string());
|
||||
cipher.reprompt = data.reprompt;
|
||||
|
||||
cipher.save(conn).await?;
|
||||
cipher.move_to_folder(data.FolderId, &headers.user.uuid, conn).await?;
|
||||
cipher.set_favorite(data.Favorite, &headers.user.uuid, conn).await?;
|
||||
cipher.move_to_folder(data.folder_id, &headers.user.uuid, conn).await?;
|
||||
cipher.set_favorite(data.favorite, &headers.user.uuid, conn).await?;
|
||||
|
||||
if ut != UpdateType::None {
|
||||
// Only log events for organizational ciphers
|
||||
|
@ -533,43 +528,43 @@ pub async fn update_cipher_from_data(
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ImportData {
|
||||
Ciphers: Vec<CipherData>,
|
||||
Folders: Vec<FolderData>,
|
||||
FolderRelationships: Vec<RelationsData>,
|
||||
ciphers: Vec<CipherData>,
|
||||
folders: Vec<FolderData>,
|
||||
folder_relationships: Vec<RelationsData>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RelationsData {
|
||||
// Cipher id
|
||||
Key: usize,
|
||||
key: usize,
|
||||
// Folder id
|
||||
Value: usize,
|
||||
value: usize,
|
||||
}
|
||||
|
||||
#[post("/ciphers/import", data = "<data>")]
|
||||
async fn post_ciphers_import(
|
||||
data: JsonUpcase<ImportData>,
|
||||
data: Json<ImportData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
enforce_personal_ownership_policy(None, &headers, &mut conn).await?;
|
||||
|
||||
let data: ImportData = data.into_inner().data;
|
||||
let data: ImportData = data.into_inner();
|
||||
|
||||
// Validate the import before continuing
|
||||
// Bitwarden does not process the import if there is one item invalid.
|
||||
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
|
||||
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
|
||||
Cipher::validate_notes(&data.Ciphers)?;
|
||||
Cipher::validate_notes(&data.ciphers)?;
|
||||
|
||||
// Read and create the folders
|
||||
let mut folders: Vec<_> = Vec::new();
|
||||
for folder in data.Folders.into_iter() {
|
||||
let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name);
|
||||
for folder in data.folders.into_iter() {
|
||||
let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.name);
|
||||
new_folder.save(&mut conn).await?;
|
||||
|
||||
folders.push(new_folder);
|
||||
|
@ -578,16 +573,16 @@ async fn post_ciphers_import(
|
|||
// Read the relations between folders and ciphers
|
||||
let mut relations_map = HashMap::new();
|
||||
|
||||
for relation in data.FolderRelationships {
|
||||
relations_map.insert(relation.Key, relation.Value);
|
||||
for relation in data.folder_relationships {
|
||||
relations_map.insert(relation.key, relation.value);
|
||||
}
|
||||
|
||||
// Read and create the ciphers
|
||||
for (index, mut cipher_data) in data.Ciphers.into_iter().enumerate() {
|
||||
for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() {
|
||||
let folder_uuid = relations_map.get(&index).map(|i| folders[*i].uuid.clone());
|
||||
cipher_data.FolderId = folder_uuid;
|
||||
cipher_data.folder_id = folder_uuid;
|
||||
|
||||
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
|
||||
let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
|
||||
update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &mut conn, &nt, UpdateType::None).await?;
|
||||
}
|
||||
|
||||
|
@ -602,7 +597,7 @@ async fn post_ciphers_import(
|
|||
#[put("/ciphers/<uuid>/admin", data = "<data>")]
|
||||
async fn put_cipher_admin(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CipherData>,
|
||||
data: Json<CipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -613,7 +608,7 @@ async fn put_cipher_admin(
|
|||
#[post("/ciphers/<uuid>/admin", data = "<data>")]
|
||||
async fn post_cipher_admin(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CipherData>,
|
||||
data: Json<CipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -622,25 +617,19 @@ async fn post_cipher_admin(
|
|||
}
|
||||
|
||||
#[post("/ciphers/<uuid>", data = "<data>")]
|
||||
async fn post_cipher(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
async fn post_cipher(uuid: &str, data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
put_cipher(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>", data = "<data>")]
|
||||
async fn put_cipher(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CipherData>,
|
||||
data: Json<CipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
let data: CipherData = data.into_inner().data;
|
||||
let data: CipherData = data.into_inner();
|
||||
|
||||
let mut cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(cipher) => cipher,
|
||||
|
@ -662,12 +651,7 @@ async fn put_cipher(
|
|||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/partial", data = "<data>")]
|
||||
async fn post_cipher_partial(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<PartialCipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
async fn post_cipher_partial(uuid: &str, data: Json<PartialCipherData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
put_cipher_partial(uuid, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -675,18 +659,18 @@ async fn post_cipher_partial(
|
|||
#[put("/ciphers/<uuid>/partial", data = "<data>")]
|
||||
async fn put_cipher_partial(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<PartialCipherData>,
|
||||
data: Json<PartialCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
let data: PartialCipherData = data.into_inner().data;
|
||||
let data: PartialCipherData = data.into_inner();
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(cipher) => cipher,
|
||||
None => err!("Cipher doesn't exist"),
|
||||
};
|
||||
|
||||
if let Some(ref folder_id) = data.FolderId {
|
||||
if let Some(ref folder_id) = data.folder_id {
|
||||
match Folder::find_by_uuid(folder_id, &mut conn).await {
|
||||
Some(folder) => {
|
||||
if folder.user_uuid != headers.user.uuid {
|
||||
|
@ -698,23 +682,23 @@ async fn put_cipher_partial(
|
|||
}
|
||||
|
||||
// Move cipher
|
||||
cipher.move_to_folder(data.FolderId.clone(), &headers.user.uuid, &mut conn).await?;
|
||||
cipher.move_to_folder(data.folder_id.clone(), &headers.user.uuid, &mut conn).await?;
|
||||
// Update favorite
|
||||
cipher.set_favorite(Some(data.Favorite), &headers.user.uuid, &mut conn).await?;
|
||||
cipher.set_favorite(Some(data.favorite), &headers.user.uuid, &mut conn).await?;
|
||||
|
||||
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CollectionsAdminData {
|
||||
CollectionIds: Vec<String>,
|
||||
collection_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/collections", data = "<data>")]
|
||||
async fn put_collections_update(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CollectionsAdminData>,
|
||||
data: Json<CollectionsAdminData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -725,7 +709,7 @@ async fn put_collections_update(
|
|||
#[post("/ciphers/<uuid>/collections", data = "<data>")]
|
||||
async fn post_collections_update(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CollectionsAdminData>,
|
||||
data: Json<CollectionsAdminData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -736,7 +720,7 @@ async fn post_collections_update(
|
|||
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
||||
async fn put_collections_admin(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CollectionsAdminData>,
|
||||
data: Json<CollectionsAdminData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -747,12 +731,12 @@ async fn put_collections_admin(
|
|||
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
||||
async fn post_collections_admin(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<CollectionsAdminData>,
|
||||
data: Json<CollectionsAdminData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let data: CollectionsAdminData = data.into_inner().data;
|
||||
let data: CollectionsAdminData = data.into_inner();
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(cipher) => cipher,
|
||||
|
@ -763,7 +747,7 @@ async fn post_collections_admin(
|
|||
err!("Cipher is not write accessible")
|
||||
}
|
||||
|
||||
let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
|
||||
let posted_collections: HashSet<String> = data.collection_ids.iter().cloned().collect();
|
||||
let current_collections: HashSet<String> =
|
||||
cipher.get_collections(headers.user.uuid.clone(), &mut conn).await.iter().cloned().collect();
|
||||
|
||||
|
@ -811,21 +795,21 @@ async fn post_collections_admin(
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ShareCipherData {
|
||||
Cipher: CipherData,
|
||||
CollectionIds: Vec<String>,
|
||||
cipher: CipherData,
|
||||
collection_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/share", data = "<data>")]
|
||||
async fn post_cipher_share(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<ShareCipherData>,
|
||||
data: Json<ShareCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
let data: ShareCipherData = data.into_inner().data;
|
||||
let data: ShareCipherData = data.into_inner();
|
||||
|
||||
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
@ -833,53 +817,53 @@ async fn post_cipher_share(
|
|||
#[put("/ciphers/<uuid>/share", data = "<data>")]
|
||||
async fn put_cipher_share(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<ShareCipherData>,
|
||||
data: Json<ShareCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
let data: ShareCipherData = data.into_inner().data;
|
||||
let data: ShareCipherData = data.into_inner();
|
||||
|
||||
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ShareSelectedCipherData {
|
||||
Ciphers: Vec<CipherData>,
|
||||
CollectionIds: Vec<String>,
|
||||
ciphers: Vec<CipherData>,
|
||||
collection_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[put("/ciphers/share", data = "<data>")]
|
||||
async fn put_cipher_share_selected(
|
||||
data: JsonUpcase<ShareSelectedCipherData>,
|
||||
data: Json<ShareSelectedCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let mut data: ShareSelectedCipherData = data.into_inner().data;
|
||||
let mut data: ShareSelectedCipherData = data.into_inner();
|
||||
|
||||
if data.Ciphers.is_empty() {
|
||||
if data.ciphers.is_empty() {
|
||||
err!("You must select at least one cipher.")
|
||||
}
|
||||
|
||||
if data.CollectionIds.is_empty() {
|
||||
if data.collection_ids.is_empty() {
|
||||
err!("You must select at least one collection.")
|
||||
}
|
||||
|
||||
for cipher in data.Ciphers.iter() {
|
||||
if cipher.Id.is_none() {
|
||||
for cipher in data.ciphers.iter() {
|
||||
if cipher.id.is_none() {
|
||||
err!("Request missing ids field")
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(cipher) = data.Ciphers.pop() {
|
||||
while let Some(cipher) = data.ciphers.pop() {
|
||||
let mut shared_cipher_data = ShareCipherData {
|
||||
Cipher: cipher,
|
||||
CollectionIds: data.CollectionIds.clone(),
|
||||
cipher,
|
||||
collection_ids: data.collection_ids.clone(),
|
||||
};
|
||||
|
||||
match shared_cipher_data.Cipher.Id.take() {
|
||||
match shared_cipher_data.cipher.id.take() {
|
||||
Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt).await?,
|
||||
None => err!("Request missing ids field"),
|
||||
};
|
||||
|
@ -908,8 +892,8 @@ async fn share_cipher_by_uuid(
|
|||
|
||||
let mut shared_to_collections = vec![];
|
||||
|
||||
if let Some(organization_uuid) = &data.Cipher.OrganizationId {
|
||||
for uuid in &data.CollectionIds {
|
||||
if let Some(organization_uuid) = &data.cipher.organization_id {
|
||||
for uuid in &data.collection_ids {
|
||||
match Collection::find_by_uuid_and_org(uuid, organization_uuid, conn).await {
|
||||
None => err!("Invalid collection ID provided"),
|
||||
Some(collection) => {
|
||||
|
@ -925,13 +909,13 @@ async fn share_cipher_by_uuid(
|
|||
};
|
||||
|
||||
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
|
||||
let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
|
||||
let ut = if data.cipher.last_known_revision_date.is_some() {
|
||||
UpdateType::SyncCipherUpdate
|
||||
} else {
|
||||
UpdateType::SyncCipherCreate
|
||||
};
|
||||
|
||||
update_cipher_from_data(&mut cipher, data.Cipher, headers, Some(shared_to_collections), conn, nt, ut).await?;
|
||||
update_cipher_from_data(&mut cipher, data.cipher, headers, Some(shared_to_collections), conn, nt, ut).await?;
|
||||
|
||||
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, conn).await))
|
||||
}
|
||||
|
@ -961,12 +945,12 @@ async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut c
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AttachmentRequestData {
|
||||
Key: String,
|
||||
FileName: String,
|
||||
FileSize: NumberOrString,
|
||||
AdminRequest: Option<bool>, // true when attaching from an org vault view
|
||||
key: String,
|
||||
file_name: String,
|
||||
file_size: NumberOrString,
|
||||
admin_request: Option<bool>, // true when attaching from an org vault view
|
||||
}
|
||||
|
||||
enum FileUploadType {
|
||||
|
@ -981,7 +965,7 @@ enum FileUploadType {
|
|||
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
|
||||
async fn post_attachment_v2(
|
||||
uuid: &str,
|
||||
data: JsonUpcase<AttachmentRequestData>,
|
||||
data: Json<AttachmentRequestData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
|
@ -994,28 +978,28 @@ async fn post_attachment_v2(
|
|||
err!("Cipher is not write accessible")
|
||||
}
|
||||
|
||||
let data: AttachmentRequestData = data.into_inner().data;
|
||||
let file_size = data.FileSize.into_i64()?;
|
||||
let data: AttachmentRequestData = data.into_inner();
|
||||
let file_size = data.file_size.into_i64()?;
|
||||
|
||||
if file_size < 0 {
|
||||
err!("Attachment size can't be negative")
|
||||
}
|
||||
let attachment_id = crypto::generate_attachment_id();
|
||||
let attachment =
|
||||
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.FileName, file_size, Some(data.Key));
|
||||
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key));
|
||||
attachment.save(&mut conn).await.expect("Error saving attachment");
|
||||
|
||||
let url = format!("/ciphers/{}/attachment/{}", cipher.uuid, attachment_id);
|
||||
let response_key = match data.AdminRequest {
|
||||
Some(b) if b => "CipherMiniResponse",
|
||||
_ => "CipherResponse",
|
||||
let response_key = match data.admin_request {
|
||||
Some(b) if b => "cipherMiniResponse",
|
||||
_ => "cipherResponse",
|
||||
};
|
||||
|
||||
Ok(Json(json!({ // AttachmentUploadDataResponseModel
|
||||
"Object": "attachment-fileUpload",
|
||||
"AttachmentId": attachment_id,
|
||||
"Url": url,
|
||||
"FileUploadType": FileUploadType::Direct as i32,
|
||||
"object": "attachment-fileUpload",
|
||||
"attachmentId": attachment_id,
|
||||
"url": url,
|
||||
"fileUploadType": FileUploadType::Direct as i32,
|
||||
response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await,
|
||||
})))
|
||||
}
|
||||
|
@ -1350,38 +1334,23 @@ async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt:
|
|||
}
|
||||
|
||||
#[delete("/ciphers", data = "<data>")]
|
||||
async fn delete_cipher_selected(
|
||||
data: JsonUpcase<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
async fn delete_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
#[post("/ciphers/delete", data = "<data>")]
|
||||
async fn delete_cipher_selected_post(
|
||||
data: JsonUpcase<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
async fn delete_cipher_selected_post(data: Json<Value>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
#[put("/ciphers/delete", data = "<data>")]
|
||||
async fn delete_cipher_selected_put(
|
||||
data: JsonUpcase<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
async fn delete_cipher_selected_put(data: Json<Value>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
_delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
|
||||
}
|
||||
|
||||
#[delete("/ciphers/admin", data = "<data>")]
|
||||
async fn delete_cipher_selected_admin(
|
||||
data: JsonUpcase<Value>,
|
||||
data: Json<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1391,7 +1360,7 @@ async fn delete_cipher_selected_admin(
|
|||
|
||||
#[post("/ciphers/delete-admin", data = "<data>")]
|
||||
async fn delete_cipher_selected_post_admin(
|
||||
data: JsonUpcase<Value>,
|
||||
data: Json<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1401,7 +1370,7 @@ async fn delete_cipher_selected_post_admin(
|
|||
|
||||
#[put("/ciphers/delete-admin", data = "<data>")]
|
||||
async fn delete_cipher_selected_put_admin(
|
||||
data: JsonUpcase<Value>,
|
||||
data: Json<Value>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1420,33 +1389,28 @@ async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn
|
|||
}
|
||||
|
||||
#[put("/ciphers/restore", data = "<data>")]
|
||||
async fn restore_cipher_selected(
|
||||
data: JsonUpcase<Value>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
async fn restore_cipher_selected(data: Json<Value>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MoveCipherData {
|
||||
FolderId: Option<String>,
|
||||
Ids: Vec<String>,
|
||||
folder_id: Option<String>,
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[post("/ciphers/move", data = "<data>")]
|
||||
async fn move_cipher_selected(
|
||||
data: JsonUpcase<MoveCipherData>,
|
||||
data: Json<MoveCipherData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let data = data.into_inner().data;
|
||||
let data = data.into_inner();
|
||||
let user_uuid = headers.user.uuid;
|
||||
|
||||
if let Some(ref folder_id) = data.FolderId {
|
||||
if let Some(ref folder_id) = data.folder_id {
|
||||
match Folder::find_by_uuid(folder_id, &mut conn).await {
|
||||
Some(folder) => {
|
||||
if folder.user_uuid != user_uuid {
|
||||
|
@ -1457,7 +1421,7 @@ async fn move_cipher_selected(
|
|||
}
|
||||
}
|
||||
|
||||
for uuid in data.Ids {
|
||||
for uuid in data.ids {
|
||||
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
||||
Some(cipher) => cipher,
|
||||
None => err!("Cipher doesn't exist"),
|
||||
|
@ -1468,7 +1432,7 @@ async fn move_cipher_selected(
|
|||
}
|
||||
|
||||
// Move cipher
|
||||
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
|
||||
cipher.move_to_folder(data.folder_id.clone(), &user_uuid, &mut conn).await?;
|
||||
|
||||
nt.send_cipher_update(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
|
@ -1486,7 +1450,7 @@ async fn move_cipher_selected(
|
|||
|
||||
#[put("/ciphers/move", data = "<data>")]
|
||||
async fn move_cipher_selected_put(
|
||||
data: JsonUpcase<MoveCipherData>,
|
||||
data: Json<MoveCipherData>,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1503,12 +1467,12 @@ struct OrganizationId {
|
|||
#[post("/ciphers/purge?<organization..>", data = "<data>")]
|
||||
async fn delete_all(
|
||||
organization: Option<OrganizationId>,
|
||||
data: JsonUpcase<PasswordOrOtpData>,
|
||||
data: Json<PasswordOrOtpData>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let data: PasswordOrOtpData = data.into_inner().data;
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
data.validate(&user, true, &mut conn).await?;
|
||||
|
@ -1616,13 +1580,13 @@ async fn _delete_cipher_by_uuid(
|
|||
}
|
||||
|
||||
async fn _delete_multiple_ciphers(
|
||||
data: JsonUpcase<Value>,
|
||||
data: Json<Value>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
soft_delete: bool,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let data: Value = data.into_inner().data;
|
||||
let data: Value = data.into_inner();
|
||||
|
||||
let uuids = match data.get("Ids") {
|
||||
Some(ids) => match ids.as_array() {
|
||||
|
@ -1681,12 +1645,12 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
|
|||
}
|
||||
|
||||
async fn _restore_multiple_ciphers(
|
||||
data: JsonUpcase<Value>,
|
||||
data: Json<Value>,
|
||||
headers: &Headers,
|
||||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
) -> JsonResult {
|
||||
let data: Value = data.into_inner().data;
|
||||
let data: Value = data.into_inner();
|
||||
|
||||
let uuids = match data.get("Ids") {
|
||||
Some(ids) => match ids.as_array() {
|
||||
|
@ -1705,9 +1669,9 @@ async fn _restore_multiple_ciphers(
|
|||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
"Data": ciphers,
|
||||
"Object": "list",
|
||||
"ContinuationToken": null
|
||||
"data": ciphers,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue