|
@@ -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
|
|
|
})))
|
|
|
}
|
|
|
|