@ -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 ! ( {
" P rofile": user_json ,
" F olders": folders_json ,
" C ollections": collections_json ,
" P olicies": policies_json ,
" C iphers": ciphers_json ,
" D omains": domains_json ,
" S ends": sends_json ,
" p rofile": user_json ,
" f olders": folders_json ,
" c ollections": collections_json ,
" p olicies": policies_json ,
" c iphers": ciphers_json ,
" d omains": domains_json ,
" s ends": sends_json ,
"unofficialServer" : true ,
" O bject": "sync"
" o bject": "sync"
} ) )
}
@ -167,9 +167,9 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
}
Json ( json ! ( {
" D ata": ciphers_json ,
" O bject": "list" ,
" C ontinuationToken": null
" d ata": ciphers_json ,
" o bject": "list" ,
" c ontinuationToken": null
} ) )
}
@ -198,17 +198,17 @@ async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonR
get_cipher ( uuid , headers , conn ) . await
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
pub struct CipherData {
// Id is optional as it is included only in bulk share
pub I d: Option < String > ,
pub i d: Option < String > ,
// Folder id is not included in import
FolderI d: Option < String > ,
folder_i d: Option < String > ,
// TODO: Some of these might appear all the time, no need for Option
pub OrganizationI d: Option < String > ,
pub organization_i d: Option < String > ,
K ey: Option < String > ,
k ey: Option < String > ,
/*
Login = 1 ,
@ -216,27 +216,27 @@ pub struct CipherData {
Card = 3 ,
Identity = 4
* /
pub T ype: i32 ,
pub N ame: String ,
pub N otes: Option < String > ,
F ields: Option < Value > ,
pub r#t ype: i32 ,
pub n ame: String ,
pub n otes: Option < String > ,
f ields: Option < Value > ,
// Only one of these should exist, depending on type
L ogin: Option < Value > ,
SecureN ote: Option < Value > ,
C ard: Option < Value > ,
I dentity: Option < Value > ,
l ogin: Option < Value > ,
secure_n ote: Option < Value > ,
c ard: Option < Value > ,
i dentity: Option < Value > ,
F avorite: Option < bool > ,
R eprompt: Option < i32 > ,
f avorite: Option < bool > ,
r eprompt: Option < i32 > ,
PasswordH istory: Option < Value > ,
password_h istory: Option < Value > ,
// These are used during key rotation
// 'Attachments' is unused, contains map of {id: filename}
#[ serde(rename = " Attachments " )]
_A ttachments: Option < Value > ,
A ttachments2: Option < HashMap < String , Attachments2Data > > ,
#[ allow(dead_code )]
a ttachments: Option < Value > ,
a ttachments2: 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.
LastKnownRevisionD ate: Option < String > ,
last_known_revision_d ate: Option < String > ,
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
pub struct PartialCipherData {
FolderI d: Option < String > ,
F avorite: bool ,
folder_i d: Option < String > ,
f avorite: bool ,
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
pub struct Attachments2Data {
FileN ame: String ,
K ey: String ,
file_n ame: String ,
k ey: 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 : Json Upcase < 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. OrganizationI d. is_some ( ) & & data . CollectionI ds. is_empty ( ) {
if data . cipher. organization_i d. is_some ( ) & & data . collection_i ds. 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 . C ipher) , & headers , & mut conn ) . await ? ;
enforce_personal_ownership_policy ( Some ( & data . c ipher) , & headers , & mut conn ) . await ? ;
let mut cipher = Cipher ::new ( data . Cipher. Type , data . Cipher . N ame. clone ( ) ) ;
let mut cipher = Cipher ::new ( data . cipher. r#type , data . cipher . n ame. 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. LastKnownRevisionD ate = None ;
data . cipher. last_known_revision_d ate = 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 : Json Upcase < 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 . LastKnownRevisionD ate = None ;
data . last_known_revision_d ate = None ;
let mut cipher = Cipher ::new ( data . Type, data . N ame. clone ( ) ) ;
let mut cipher = Cipher ::new ( data . r#type, data . n ame. 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 ( ) . OrganizationI d. is_none ( ) {
if data . is_none ( ) | | data . unwrap ( ) . organization_i d. 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 . LastKnownRevisionD ate {
if let Some ( dt ) = data . last_known_revision_d ate {
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 . OrganizationI d {
if cipher . organization_uuid . is_some ( ) & & cipher . organization_uuid ! = data . organization_i d {
err ! ( "Organization mismatch. Please resync the client before updating the cipher" )
}
if let Some ( note ) = & data . N otes {
if let Some ( note ) = & data . n otes {
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 . OrganizationI d. is_some ( ) ;
let transfer_cipher = cipher . organization_uuid . is_none ( ) & & data . organization_i d. is_some ( ) ;
if let Some ( org_id ) = data . OrganizationI d {
if let Some ( org_id ) = data . organization_i d {
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 . FolderI d {
if let Some ( ref folder_id ) = data . folder_i d {
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 . A ttachments2 {
if let Some ( attachments ) = data . a ttachments2 {
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 . K ey) ;
saved_att . file_name = attachment . FileN ame;
saved_att . akey = Some ( attachment . k ey) ;
saved_att . file_name = attachment . file_n ame;
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 ( " R esponse") ;
f . as_object_mut ( ) . unwrap ( ) . remove ( " r esponse") ;
} ) ;
} ;
json_data
}
let type_data_opt = match data . T ype {
1 = > data . L ogin,
2 = > data . SecureN ote,
3 = > data . C ard,
4 = > data . I dentity,
let type_data_opt = match data . r#t ype {
1 = > data . l ogin,
2 = > data . secure_n ote,
3 = > data . c ard,
4 = > data . i dentity,
_ = > 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 ( " R esponse") ;
data . as_object_mut ( ) . unwrap ( ) . remove ( " r esponse") ;
// Remove the 'Response' key from every Uri.
if data [ " U ris"] . is_array ( ) {
data [ " U ris"] = _clean_cipher_data ( data [ " U ris"] . clone ( ) ) ;
if data [ " u ris"] . is_array ( ) {
data [ " u ris"] = _clean_cipher_data ( data [ " u ris"] . clone ( ) ) ;
}
data
}
None = > err ! ( "Data missing" ) ,
} ;
cipher . key = data . K ey;
cipher . name = data . N ame;
cipher . notes = data . N otes;
cipher . fields = data . F ields. map ( | f | _clean_cipher_data ( f ) . to_string ( ) ) ;
cipher . key = data . k ey;
cipher . name = data . n ame;
cipher . notes = data . n otes;
cipher . fields = data . f ields. map ( | f | _clean_cipher_data ( f ) . to_string ( ) ) ;
cipher . data = type_data . to_string ( ) ;
cipher . password_history = data . PasswordH istory. map ( | f | f . to_string ( ) ) ;
cipher . reprompt = data . R eprompt;
cipher . password_history = data . password_h istory. map ( | f | f . to_string ( ) ) ;
cipher . reprompt = data . r eprompt;
cipher . save ( conn ) . await ? ;
cipher . move_to_folder ( data . FolderI d, & headers . user . uuid , conn ) . await ? ;
cipher . set_favorite ( data . F avorite, & headers . user . uuid , conn ) . await ? ;
cipher . move_to_folder ( data . folder_i d, & headers . user . uuid , conn ) . await ? ;
cipher . set_favorite ( data . f avorite, & 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 {
C iphers: Vec < CipherData > ,
F olders: Vec < FolderData > ,
FolderR elationships: Vec < RelationsData > ,
c iphers: Vec < CipherData > ,
f olders: Vec < FolderData > ,
folder_r elationships: Vec < RelationsData > ,
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct RelationsData {
// Cipher id
K ey: usize ,
k ey: usize ,
// Folder id
V alue: usize ,
v alue: usize ,
}
#[ post( " /ciphers/import " , data = " <data> " ) ]
async fn post_ciphers_import (
data : Json Upcase < 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 . C iphers) ? ;
Cipher ::validate_notes ( & data . c iphers) ? ;
// Read and create the folders
let mut folders : Vec < _ > = Vec ::new ( ) ;
for folder in data . F olders. into_iter ( ) {
let mut new_folder = Folder ::new ( headers . user . uuid . clone ( ) , folder . N ame) ;
for folder in data . f olders. into_iter ( ) {
let mut new_folder = Folder ::new ( headers . user . uuid . clone ( ) , folder . n ame) ;
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 . FolderR elationships {
relations_map . insert ( relation . Key, relation . V alue) ;
for relation in data . folder_r elationships {
relations_map . insert ( relation . key, relation . v alue) ;
}
// Read and create the ciphers
for ( index , mut cipher_data ) in data . C iphers. into_iter ( ) . enumerate ( ) {
for ( index , mut cipher_data ) in data . c iphers. into_iter ( ) . enumerate ( ) {
let folder_uuid = relations_map . get ( & index ) . map ( | i | folders [ * i ] . uuid . clone ( ) ) ;
cipher_data . FolderI d = folder_uuid ;
cipher_data . folder_i d = folder_uuid ;
let mut cipher = Cipher ::new ( cipher_data . Type, cipher_data . N ame. clone ( ) ) ;
let mut cipher = Cipher ::new ( cipher_data . r#type, cipher_data . n ame. 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 . FolderI d {
if let Some ( ref folder_id ) = data . folder_i d {
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 . FolderI d. clone ( ) , & headers . user . uuid , & mut conn ) . await ? ;
cipher . move_to_folder ( data . folder_i d. clone ( ) , & headers . user . uuid , & mut conn ) . await ? ;
// Update favorite
cipher . set_favorite ( Some ( data . F avorite) , & headers . user . uuid , & mut conn ) . await ? ;
cipher . set_favorite ( Some ( data . f avorite) , & 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 {
CollectionI ds: Vec < String > ,
collection_i ds: Vec < String > ,
}
#[ put( " /ciphers/<uuid>/collections " , data = " <data> " ) ]
async fn put_collections_update (
uuid : & str ,
data : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 . CollectionI ds. iter ( ) . cloned ( ) . collect ( ) ;
let posted_collections : HashSet < String > = data . collection_i ds. 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 {
C ipher: CipherData ,
CollectionI ds: Vec < String > ,
c ipher: CipherData ,
collection_i ds: Vec < String > ,
}
#[ post( " /ciphers/<uuid>/share " , data = " <data> " ) ]
async fn post_cipher_share (
uuid : & str ,
data : Json Upcase < 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 : Json Upcase < 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 {
C iphers: Vec < CipherData > ,
CollectionI ds: Vec < String > ,
c iphers: Vec < CipherData > ,
collection_i ds: Vec < String > ,
}
#[ put( " /ciphers/share " , data = " <data> " ) ]
async fn put_cipher_share_selected (
data : Json Upcase < 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 . C iphers. is_empty ( ) {
if data . c iphers. is_empty ( ) {
err ! ( "You must select at least one cipher." )
}
if data . CollectionI ds. is_empty ( ) {
if data . collection_i ds. is_empty ( ) {
err ! ( "You must select at least one collection." )
}
for cipher in data . C iphers. iter ( ) {
if cipher . I d. is_none ( ) {
for cipher in data . c iphers. iter ( ) {
if cipher . i d. is_none ( ) {
err ! ( "Request missing ids field" )
}
}
while let Some ( cipher ) = data . C iphers. pop ( ) {
while let Some ( cipher ) = data . c iphers. pop ( ) {
let mut shared_cipher_data = ShareCipherData {
Cipher: cipher,
CollectionIds: data . CollectionI ds. clone ( ) ,
cipher,
collection_ids: data . collection_i ds. clone ( ) ,
} ;
match shared_cipher_data . Cipher. I d. take ( ) {
match shared_cipher_data . cipher. i d. 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. OrganizationI d {
for uuid in & data . CollectionI ds {
if let Some ( organization_uuid ) = & data . cipher. organization_i d {
for uuid in & data . collection_i ds {
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. LastKnownRevisionD ate. is_some ( ) {
let ut = if data . cipher. last_known_revision_d ate. is_some ( ) {
UpdateType ::SyncCipherUpdate
} else {
UpdateType ::SyncCipherCreate
} ;
update_cipher_from_data ( & mut cipher , data . C ipher, headers , Some ( shared_to_collections ) , conn , nt , ut ) . await ? ;
update_cipher_from_data ( & mut cipher , data . c ipher, 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 {
K ey: String ,
FileN ame: String ,
FileS ize: NumberOrString ,
AdminR equest: Option < bool > , // true when attaching from an org vault view
k ey: String ,
file_n ame: String ,
file_s ize: NumberOrString ,
admin_r equest: 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 : Json Upcase < 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 . FileS ize. into_i64 ( ) ? ;
let data : AttachmentRequestData = data . into_inner ( ) ;
let file_size = data . file_s ize. 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 . FileN ame, file_size , Some ( data . K ey) ) ;
Attachment ::new ( attachment_id . clone ( ) , cipher . uuid . clone ( ) , data . file_n ame, file_size , Some ( data . k ey) ) ;
attachment . save ( & mut conn ) . await . expect ( "Error saving attachment" ) ;
let url = format! ( "/ciphers/{}/attachment/{}" , cipher . uuid , attachment_id ) ;
let response_key = match data . AdminR equest {
Some ( b ) if b = > " C ipherMiniResponse",
_ = > " C ipherResponse",
let response_key = match data . admin_r equest {
Some ( b ) if b = > " c ipherMiniResponse",
_ = > " c ipherResponse",
} ;
Ok ( Json ( json ! ( { // AttachmentUploadDataResponseModel
" O bject": "attachment-fileUpload" ,
" A ttachmentId": attachment_id ,
" U rl": url ,
" F ileUploadType": FileUploadType ::Direct as i32 ,
" o bject": "attachment-fileUpload" ,
" a ttachmentId": attachment_id ,
" u rl": url ,
" f ileUploadType": 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 {
FolderI d: Option < String > ,
I ds: Vec < String > ,
folder_i d: Option < String > ,
i ds: Vec < String > ,
}
#[ post( " /ciphers/move " , data = " <data> " ) ]
async fn move_cipher_selected (
data : Json Upcase < 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 . FolderI d {
if let Some ( ref folder_id ) = data . folder_i d {
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 . I ds {
for uuid in data . i ds {
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 . FolderI d. clone ( ) , & user_uuid , & mut conn ) . await ? ;
cipher . move_to_folder ( data . folder_i d. 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 : Json Upcase < 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 ! ( {
" D ata": ciphers ,
" O bject": "list" ,
" C ontinuationToken": null
" d ata": ciphers ,
" o bject": "list" ,
" c ontinuationToken": null
} ) ) )
}