@ -1,28 +1,28 @@
use num_traits ::FromPrimitive ;
use num_traits ::FromPrimitive ;
use rocket ::serde ::json ::Json ;
use rocket ::Route ;
use rocket ::Route ;
use rocket ::serde ::json ::Json ;
use serde_json ::Value ;
use serde_json ::Value ;
use std ::collections ::{ HashMap , HashSet } ;
use std ::collections ::{ HashMap , HashSet } ;
use crate ::api ::admin ::FAKE_ADMIN_UUID ;
use crate ::api ::admin ::FAKE_ADMIN_UUID ;
use crate ::{
use crate ::{
CONFIG ,
api ::{
api ::{
core ::{ accept_org_invite , log_event , two_factor , CipherSyncData , CipherSyncType } ,
EmptyResult , JsonResult , Notify , PasswordOrOtpData , UpdateType ,
EmptyResult , JsonResult , Notify , PasswordOrOtpData , UpdateType ,
core ::{ CipherSyncData , CipherSyncType , accept_org_invite , log_event , two_factor } ,
} ,
} ,
auth ::{ decode_invite , AdminHeaders , Headers , ManagerHeaders , ManagerHeadersLoose , OrgMemberHeaders , OwnerHeaders } ,
auth ::{ AdminHeaders , Headers , ManagerHeaders , ManagerHeadersLoose , OrgMemberHeaders , OwnerHeaders , decode_invite } ,
db ::{
db ::{
DbConn ,
models ::{
models ::{
Cipher , CipherId , Collection , CollectionCipher , CollectionGroup , CollectionId , CollectionUser , EventType ,
Cipher , CipherId , Collection , CollectionCipher , CollectionGroup , CollectionId , CollectionUser , EventType ,
Group , GroupId , GroupUser , Invitation , Membership , MembershipId , MembershipStatus , MembershipType ,
Group , GroupId , GroupUser , Invitation , Membership , MembershipId , MembershipStatus , MembershipType ,
OrgPolicy , OrgPolicyType , Organization , OrganizationApiKey , OrganizationId , User , UserId ,
OrgPolicy , OrgPolicyType , Organization , OrganizationApiKey , OrganizationId , User , UserId ,
} ,
} ,
DbConn ,
} ,
} ,
mail ,
mail ,
sso ::FAKE_SSO_IDENTIFIER ,
sso ::FAKE_SSO_IDENTIFIER ,
util ::{ convert_json_key_lcase_first , NumberOrString } ,
util ::{ NumberOrString , convert_json_key_lcase_first } ,
CONFIG ,
} ;
} ;
pub fn routes ( ) -> Vec < Route > {
pub fn routes ( ) -> Vec < Route > {
@ -97,7 +97,7 @@ pub fn routes() -> Vec<Route> {
get_reset_password_details ,
get_reset_password_details ,
put_reset_password ,
put_reset_password ,
get_org_export ,
get_org_export ,
api_key ,
post_ api_key,
rotate_api_key ,
rotate_api_key ,
get_billing_metadata ,
get_billing_metadata ,
get_billing_warnings ,
get_billing_warnings ,
@ -286,9 +286,10 @@ async fn get_organization(org_id: OrganizationId, headers: OwnerHeaders, conn: D
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
match Organization ::find_by_uuid ( & org_id , & conn ) . await {
if let Some ( organization ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( organization ) = > Ok ( Json ( organization . to_json ( ) ) ) ,
Ok ( Json ( organization . to_json ( ) ) )
None = > err ! ( "Can't find organization details" ) ,
} else {
err ! ( "Can't find organization details" )
}
}
}
}
@ -367,7 +368,7 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn
} ;
} ;
let ( id , identifier , rp_auto_enroll ) = match org {
let ( id , identifier , rp_auto_enroll ) = match org {
None = > ( identifier . to_string ( ) , identifier . to_string ( ) , false ) ,
None = > ( identifier . to_owned ( ) , identifier . to_owned ( ) , false ) ,
Some ( org ) = > (
Some ( org ) = > (
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
@ -393,7 +394,7 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"data" : _ get_org_collections( & org_id , & conn ) . await ,
"data" : get_org_collections_impl ( & org_id , & conn ) . await ,
"object" : "list" ,
"object" : "list" ,
"continuationToken" : null ,
"continuationToken" : null ,
} ) ) )
} ) ) )
@ -465,7 +466,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
CollectionGroup ::find_by_collection ( & col . uuid , & conn )
CollectionGroup ::find_by_collection ( & col . uuid , & conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
. map ( CollectionGroup ::to_json_details_for_group )
. collect ( )
. collect ( )
} else {
} else {
Vec ::with_capacity ( 0 )
Vec ::with_capacity ( 0 )
@ -477,7 +478,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
json_object [ "groups" ] = json ! ( groups ) ;
json_object [ "groups" ] = json ! ( groups ) ;
json_object [ "object" ] = json ! ( "collectionAccessDetails" ) ;
json_object [ "object" ] = json ! ( "collectionAccessDetails" ) ;
json_object [ "unmanaged" ] = json ! ( false ) ;
json_object [ "unmanaged" ] = json ! ( false ) ;
data . push ( json_object )
data . push ( json_object ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -487,7 +488,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
} ) ) )
} ) ) )
}
}
async fn _ get_org_collections( org_id : & OrganizationId , conn : & DbConn ) -> Value {
async fn get_org_collections_impl ( org_id : & OrganizationId , conn : & DbConn ) -> Value {
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
}
}
@ -573,7 +574,7 @@ async fn post_bulk_access_collections(
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
err ! ( "Can't find organization details" )
err ! ( "Can't find organization details" )
} ;
}
for col_id in data . collection_ids {
for col_id in data . collection_ids {
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
@ -650,7 +651,7 @@ async fn post_organization_collection_update(
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
err ! ( "Can't find organization details" )
err ! ( "Can't find organization details" )
} ;
}
let Some ( mut collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
let Some ( mut collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
err ! ( "Collection not found" )
err ! ( "Collection not found" )
@ -701,7 +702,7 @@ async fn post_organization_collection_update(
Ok ( Json ( collection . to_json_details ( & headers . user . uuid , None , & conn ) . await ) )
Ok ( Json ( collection . to_json_details ( & headers . user . uuid , None , & conn ) . await ) )
}
}
async fn _ delete_organization_collection(
async fn delete_organization_collection_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
col_id : & CollectionId ,
col_id : & CollectionId ,
headers : & ManagerHeaders ,
headers : & ManagerHeaders ,
@ -733,7 +734,7 @@ async fn delete_organization_collection(
headers : ManagerHeaders ,
headers : ManagerHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await
}
}
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " ) ]
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " ) ]
@ -743,7 +744,7 @@ async fn post_organization_collection_delete(
headers : ManagerHeaders ,
headers : ManagerHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await
}
}
#[ derive(Deserialize, Debug) ]
#[ derive(Deserialize, Debug) ]
@ -769,7 +770,7 @@ async fn bulk_delete_organization_collections(
let headers = ManagerHeaders ::from_loose ( headers , & collections , & conn ) . await ? ;
let headers = ManagerHeaders ::from_loose ( headers , & collections , & conn ) . await ? ;
for col_id in collections {
for col_id in collections {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await ?
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await ? ;
}
}
Ok ( ( ) )
Ok ( ( ) )
}
}
@ -799,7 +800,7 @@ async fn get_org_collection_detail(
CollectionGroup ::find_by_collection ( & collection . uuid , & conn )
CollectionGroup ::find_by_collection ( & collection . uuid , & conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
. map ( CollectionGroup ::to_json_details_for_group )
. collect ( )
. collect ( )
} else {
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
@ -886,13 +887,13 @@ async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: Db
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"data" : _ get_org_details( & data . organization_id , & headers . host , & headers . user . uuid , & conn ) . await ? ,
"data" : get_org_details_impl ( & data . organization_id , & headers . host , & headers . user . uuid , & conn ) . await ? ,
"object" : "list" ,
"object" : "list" ,
"continuationToken" : null ,
"continuationToken" : null ,
} ) ) )
} ) ) )
}
}
async fn _ get_org_details(
async fn get_org_details_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
host : & str ,
host : & str ,
user_id : & UserId ,
user_id : & UserId ,
@ -975,14 +976,13 @@ async fn post_org_keys(
}
}
let data : OrgKeyData = data . into_inner ( ) ;
let data : OrgKeyData = data . into_inner ( ) ;
let mut org = match Organization ::find_by_uuid ( & org_id , & conn ) . await {
let mut org = if let Some ( organization ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( organization ) = > {
if organization . private_key . is_some ( ) & & organization . public_key . is_some ( ) {
if organization . private_key . is_some ( ) & & organization . public_key . is_some ( ) {
err ! ( "Organization Keys already exist" )
err ! ( "Organization Keys already exist" )
}
organization
}
}
None = > err ! ( "Can't find organization details" ) ,
organization
} else {
err ! ( "Can't find organization details" )
} ;
} ;
org . private_key = Some ( data . encrypted_private_key ) ;
org . private_key = Some ( data . encrypted_private_key ) ;
@ -1043,9 +1043,10 @@ async fn send_invite(
// The from_str() will convert the custom role type into a manager role type
// The from_str() will convert the custom role type into a manager role type
let raw_type = & data . r#type . into_string ( ) ;
let raw_type = & data . r#type . into_string ( ) ;
// Membership::from_str will convert custom (4) to manager (3)
// Membership::from_str will convert custom (4) to manager (3)
let new_type = match MembershipType ::from_str ( raw_type ) {
let new_type = if let Some ( new_type ) = MembershipType ::from_str ( raw_type ) {
Some ( new_type ) = > new_type as i32 ,
new_type as i32
None = > err ! ( "Invalid type" ) ,
} else {
err ! ( "Invalid type" )
} ;
} ;
if new_type ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
if new_type ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
@ -1062,7 +1063,7 @@ async fn send_invite(
& & data . permissions . get ( "createNewCollections" ) = = Some ( & json ! ( true ) ) ) ;
& & data . permissions . get ( "createNewCollections" ) = = Some ( & json ! ( true ) ) ) ;
let mut user_created : bool = false ;
let mut user_created : bool = false ;
for email in data . emails . iter ( ) {
for email in & data . emails {
let mut member_status = MembershipStatus ::Invited as i32 ;
let mut member_status = MembershipStatus ::Invited as i32 ;
let user = match User ::find_by_mail ( email , & conn ) . await {
let user = match User ::find_by_mail ( email , & conn ) . await {
None = > {
None = > {
@ -1086,13 +1087,13 @@ async fn send_invite(
Some ( user ) = > {
Some ( user ) = > {
if Membership ::find_by_user_and_org ( & user . uuid , & org_id , & conn ) . await . is_some ( ) {
if Membership ::find_by_user_and_org ( & user . uuid , & org_id , & conn ) . await . is_some ( ) {
err ! ( format ! ( "User already in organization: {email}" ) )
err ! ( format ! ( "User already in organization: {email}" ) )
} else {
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
member_status = MembershipStatus ::Accepted as i32 ;
}
user
}
}
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
member_status = MembershipStatus ::Accepted as i32 ;
}
user
}
}
} ;
} ;
@ -1103,9 +1104,10 @@ async fn send_invite(
new_member . save ( & conn ) . await ? ;
new_member . save ( & conn ) . await ? ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
let org_name = match Organization ::find_by_uuid ( & org_id , & conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization" ) ,
} else {
err ! ( "Error looking up organization" )
} ;
} ;
if let Err ( e ) = mail ::send_invite (
if let Err ( e ) = mail ::send_invite (
@ -1159,7 +1161,7 @@ async fn send_invite(
}
}
}
}
for group_id in data . groups . iter ( ) {
for group_id in & data . groups {
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , new_member . uuid . clone ( ) ) ;
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , new_member . uuid . clone ( ) ) ;
group_entry . save ( & conn ) . await ? ;
group_entry . save ( & conn ) . await ? ;
}
}
@ -1182,8 +1184,8 @@ async fn bulk_reinvite_members(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ reinvite_member( & org_id , & member_id , & headers . user . email , & conn ) . await {
let err_msg = match reinvite_member_impl ( & org_id , & member_id , & headers . user . email , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1193,7 +1195,7 @@ async fn bulk_reinvite_members(
"id" : member_id ,
"id" : member_id ,
"error" : err_msg
"error" : err_msg
}
}
) )
) ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -1213,10 +1215,10 @@ async fn reinvite_member(
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
_ reinvite_member( & org_id , & member_id , & headers . user . email , & conn ) . await
reinvite_member_impl ( & org_id , & member_id , & headers . user . email , & conn ) . await
}
}
async fn _ reinvite_member(
async fn reinvite_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
invited_by_email : & str ,
invited_by_email : & str ,
@ -1238,13 +1240,14 @@ async fn _reinvite_member(
err ! ( "Invitations are not allowed." )
err ! ( "Invitations are not allowed." )
}
}
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( org_id , conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization." ) ,
} else {
err ! ( "Error looking up organization." )
} ;
} ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
mail ::send_invite ( & user , org_id . clone ( ) , member . uuid , & org_name , Some ( invited_by_email . to_string ( ) ) ) . await ? ;
mail ::send_invite ( & user , org_id . clone ( ) , member . uuid , & org_name , Some ( invited_by_email . to_owned ( ) ) ) . await ? ;
} else if user . password_hash . is_empty ( ) {
} else if user . password_hash . is_empty ( ) {
let invitation = Invitation ::new ( & user . email ) ;
let invitation = Invitation ::new ( & user . email ) ;
invitation . save ( conn ) . await ? ;
invitation . save ( conn ) . await ? ;
@ -1352,8 +1355,8 @@ async fn bulk_confirm_invite(
for invite in keys {
for invite in keys {
let member_id = invite . id . unwrap ( ) ;
let member_id = invite . id . unwrap ( ) ;
let user_key = invite . key . unwrap_or_default ( ) ;
let user_key = invite . key . unwrap_or_default ( ) ;
let err_msg = match _ confirm_invite( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await {
let err_msg = match confirm_invite_impl ( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1387,10 +1390,10 @@ async fn confirm_invite(
) -> EmptyResult {
) -> EmptyResult {
let data = data . into_inner ( ) ;
let data = data . into_inner ( ) ;
let user_key = data . key . unwrap_or_default ( ) ;
let user_key = data . key . unwrap_or_default ( ) ;
_ confirm_invite( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await
confirm_invite_impl ( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await
}
}
async fn _ confirm_invite(
async fn confirm_invite_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
key : & str ,
key : & str ,
@ -1418,7 +1421,7 @@ async fn _confirm_invite(
}
}
member_to_confirm . status = MembershipStatus ::Confirmed as i32 ;
member_to_confirm . status = MembershipStatus ::Confirmed as i32 ;
member_to_confirm . akey = key . to_string ( ) ;
member_to_confirm . akey = key . to_owned ( ) ;
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
OrgPolicy ::check_user_allowed ( & member_to_confirm , "confirm" , conn ) . await ? ;
OrgPolicy ::check_user_allowed ( & member_to_confirm , "confirm" , conn ) . await ? ;
@ -1435,13 +1438,15 @@ async fn _confirm_invite(
. await ;
. await ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( org_id , conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization." ) ,
} else {
err ! ( "Error looking up organization." )
} ;
} ;
let address = match User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
let address = if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
Some ( user ) = > user . email ,
user . email
None = > err ! ( "Error looking up user." ) ,
} else {
err ! ( "Error looking up user." )
} ;
} ;
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
}
}
@ -1637,8 +1642,8 @@ async fn bulk_delete_member(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ delete_member( & org_id , & member_id , & headers , & conn , & nt ) . await {
let err_msg = match delete_member_impl ( & org_id , & member_id , & headers , & conn , & nt ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1648,7 +1653,7 @@ async fn bulk_delete_member(
"id" : member_id ,
"id" : member_id ,
"error" : err_msg
"error" : err_msg
}
}
) )
) ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -1666,10 +1671,10 @@ async fn delete_member(
conn : DbConn ,
conn : DbConn ,
nt : Notify < '_ > ,
nt : Notify < '_ > ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_member( & org_id , & member_id , & headers , & conn , & nt ) . await
delete_member_impl ( & org_id , & member_id , & headers , & conn , & nt ) . await
}
}
async fn _ delete_member(
async fn delete_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -1753,8 +1758,8 @@ async fn bulk_public_keys(
} ) ) )
} ) ) )
}
}
use super ::ciphers ::update_cipher_from_data ;
use super ::ciphers ::CipherData ;
use super ::ciphers ::CipherData ;
use super ::ciphers ::update_cipher_from_data ;
#[ derive(Deserialize) ]
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
#[ serde(rename_all = " camelCase " ) ]
@ -1902,24 +1907,24 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
}
}
}
}
for cipher_id in data . cipher_ids . iter ( ) {
for cipher_id in & data . cipher_ids {
// Only act on existing cipher uuid's
// Only act on existing cipher uuid's
// Do not abort the operation just ignore it, it could be a cipher was just deleted for example
// Do not abort the operation just ignore it, it could be a cipher was just deleted for example
if let Some ( cipher ) = Cipher ::find_by_uuid_and_org ( cipher_id , & data . organization_id , & conn ) . await {
if let Some ( cipher ) = Cipher ::find_by_uuid_and_org ( cipher_id , & data . organization_id , & conn ) . await
if cipher . is_write_accessible_to_user ( & headers . user . uuid , & conn ) . await {
& & cipher . is_write_accessible_to_user ( & headers . user . uuid , & conn ) . await
// When selecting a specific collection from the left filter list, and use the bulk option, you can remove an item from that collection
{
// In these cases the client will call this endpoint twice, once for adding the new collections and a second for deleting.
// When selecting a specific collection from the left filter list, and use the bulk option, you can remove an item from that collection
if data . remove_collections {
// In these cases the client will call this endpoint twice, once for adding the new collections and a second for deleting.
for collection in & data . collection_id s {
i f data . remove_ collections {
CollectionCipher ::delete ( & cipher . uuid , collection , & conn ) . await ? ;
for collection in & data . collection_ids {
}
CollectionCipher ::delete ( & cipher . uuid , collection , & conn ) . await ? ;
} else {
}
for collection in & data . collection_ids {
} else {
CollectionCipher ::save ( & cipher . uuid , collection , & conn ) . await ? ;
for collection in & data . collection_ids {
}
CollectionCipher ::save ( & cipher . uuid , collection , & conn ) . await ? ;
}
}
}
}
} ;
}
}
}
Ok ( ( ) )
Ok ( ( ) )
@ -1969,7 +1974,7 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, conn: DbConn)
fn get_dummy_master_password_policy ( ) -> JsonResult {
fn get_dummy_master_password_policy ( ) -> JsonResult {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
_ = > ( false , "null" . to_string ( ) ) ,
_ = > ( false , "null" . to_owned ( ) ) ,
} ;
} ;
let policy = OrgPolicy ::new ( FAKE_SSO_IDENTIFIER . into ( ) , OrgPolicyType ::MasterPassword , enabled , data ) ;
let policy = OrgPolicy ::new ( FAKE_SSO_IDENTIFIER . into ( ) , OrgPolicyType ::MasterPassword , enabled , data ) ;
Ok ( Json ( policy . to_json ( ) ) )
Ok ( Json ( policy . to_json ( ) ) )
@ -1982,7 +1987,7 @@ async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberH
OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::MasterPassword , & conn ) . await . unwrap_or_else ( | | {
OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::MasterPassword , & conn ) . await . unwrap_or_else ( | | {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
_ = > ( false , "null" . to_string ( ) ) ,
_ = > ( false , "null" . to_owned ( ) ) ,
} ;
} ;
OrgPolicy ::new ( org_id , OrgPolicyType ::MasterPassword , enabled , data )
OrgPolicy ::new ( org_id , OrgPolicyType ::MasterPassword , enabled , data )
@ -2003,7 +2008,7 @@ async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
Some ( p ) = > p ,
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "null" . to_string ( ) ) ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "null" . to_owned ( ) ) ,
} ;
} ;
Ok ( Json ( policy . to_json ( ) ) )
Ok ( Json ( policy . to_json ( ) ) )
@ -2078,7 +2083,7 @@ async fn put_policy(
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
for mut member in Membership ::find_by_org ( & org_id , & conn ) . await . into_iter ( ) {
for mut member in Membership ::find_by_org ( & org_id , & conn ) . await {
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
// Exclude invited and revoked users when checking for this policy.
// Exclude invited and revoked users when checking for this policy.
// Those users will not be allowed to accept or be activated because of the policy checks done there.
// Those users will not be allowed to accept or be activated because of the policy checks done there.
@ -2113,7 +2118,7 @@ async fn put_policy(
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
Some ( p ) = > p ,
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "{}" . to_string ( ) ) ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "{}" . to_owned ( ) ) ,
} ;
} ;
policy . enabled = data . enabled ;
policy . enabled = data . enabled ;
@ -2187,7 +2192,7 @@ fn get_plans() -> Json<Value> {
#[ get( " /organizations/<_org_id>/billing/metadata " ) ]
#[ get( " /organizations/<_org_id>/billing/metadata " ) ]
fn get_billing_metadata ( _org_id : OrganizationId , _headers : OrgMemberHeaders ) -> Json < Value > {
fn get_billing_metadata ( _org_id : OrganizationId , _headers : OrgMemberHeaders ) -> Json < Value > {
// Prevent a 404 error, which also causes Javascript errors.
// Prevent a 404 error, which also causes Javascript errors.
Json ( _ empty_data_json( ) )
Json ( empty_data_json ( ) )
}
}
#[ get( " /organizations/<_org_id>/billing/vnext/warnings " ) ]
#[ get( " /organizations/<_org_id>/billing/vnext/warnings " ) ]
@ -2209,7 +2214,7 @@ fn get_self_host_billing_metadata(_org_id: OrganizationId, _headers: OrgMemberHe
} ) )
} ) )
}
}
fn _ empty_data_json( ) -> Value {
fn empty_data_json ( ) -> Value {
json ! ( {
json ! ( {
"object" : "list" ,
"object" : "list" ,
"data" : [ ] ,
"data" : [ ] ,
@ -2230,7 +2235,7 @@ async fn revoke_member(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ revoke_member( & org_id , & member_id , & headers , & conn ) . await
revoke_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
}
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
@ -2249,8 +2254,8 @@ async fn bulk_revoke_members(
match data . ids {
match data . ids {
Some ( members ) = > {
Some ( members ) = > {
for member_id in members {
for member_id in members {
let err_msg = match _ revoke_member( & org_id , & member_id , & headers , & conn ) . await {
let err_msg = match revoke_member_impl ( & org_id , & member_id , & headers , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -2273,7 +2278,7 @@ async fn bulk_revoke_members(
} ) ) )
} ) ) )
}
}
async fn _ revoke_member(
async fn revoke_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2325,7 +2330,7 @@ async fn restore_member_vnext(
) -> EmptyResult {
) -> EmptyResult {
// Vaultwarden does not (yet) support the per User Collection linked to the `Enforce organization data ownership` policy.
// Vaultwarden does not (yet) support the per User Collection linked to the `Enforce organization data ownership` policy.
// Therefor we ignore the `defaultUserCollectionName` data sent and just call restore_member
// Therefor we ignore the `defaultUserCollectionName` data sent and just call restore_member
_ restore_member( & org_id , & member_id , & headers , & conn ) . await
restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
}
#[ put( " /organizations/<org_id>/users/<member_id>/restore " ) ]
#[ put( " /organizations/<org_id>/users/<member_id>/restore " ) ]
@ -2335,7 +2340,7 @@ async fn restore_member(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ restore_member( & org_id , & member_id , & headers , & conn ) . await
restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
}
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
@ -2352,8 +2357,8 @@ async fn bulk_restore_members(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ restore_member( & org_id , & member_id , & headers , & conn ) . await {
let err_msg = match restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -2373,7 +2378,7 @@ async fn bulk_restore_members(
} ) ) )
} ) ) )
}
}
async fn _ restore_member(
async fn restore_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2429,11 +2434,11 @@ async fn get_groups_data(
if details {
if details {
for g in groups {
for g in groups {
groups_json . push ( g . to_json_details ( & conn ) . await )
groups_json . push ( g . to_json_details ( & conn ) . await ) ;
}
}
} else {
} else {
for g in groups {
for g in groups {
groups_json . push ( g . to_json ( ) )
groups_json . push ( g . to_json ( ) ) ;
}
}
}
}
groups_json
groups_json
@ -2672,15 +2677,15 @@ async fn post_delete_group(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await
}
}
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
async fn delete_group ( org_id : OrganizationId , group_id : GroupId , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
async fn delete_group ( org_id : OrganizationId , group_id : GroupId , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await
}
}
async fn _ delete_group(
async fn delete_group_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
group_id : & GroupId ,
group_id : & GroupId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2728,7 +2733,7 @@ async fn bulk_delete_groups(
let data : BulkGroupIds = data . into_inner ( ) ;
let data : BulkGroupIds = data . into_inner ( ) ;
for group_id in data . ids {
for group_id in data . ids {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await ?
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await ? ;
}
}
Ok ( ( ) )
Ok ( ( ) )
}
}
@ -2765,7 +2770,7 @@ async fn get_group_members(
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
} ;
}
let group_members : Vec < MembershipId > = GroupUser ::find_by_group ( & group_id , & org_id , & conn )
let group_members : Vec < MembershipId > = GroupUser ::find_by_group ( & group_id , & org_id , & conn )
. await
. await
@ -2793,7 +2798,7 @@ async fn put_group_members(
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
} ;
}
let assigned_members = data . into_inner ( ) ;
let assigned_members = data . into_inner ( ) ;
@ -3100,12 +3105,12 @@ async fn get_org_export(org_id: OrganizationId, headers: AdminHeaders, conn: DbC
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"collections" : convert_json_key_lcase_first ( _ get_org_collections( & org_id , & conn ) . await ) ,
"collections" : convert_json_key_lcase_first ( get_org_collections_impl ( & org_id , & conn ) . await ) ,
"ciphers" : convert_json_key_lcase_first ( _ get_org_details( & org_id , & headers . host , & headers . user . uuid , & conn ) . await ? ) ,
"ciphers" : convert_json_key_lcase_first ( get_org_details_impl ( & org_id , & headers . host , & headers . user . uuid , & conn ) . await ? ) ,
} ) ) )
} ) ) )
}
}
async fn _ api_key(
async fn api_key (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
data : Json < PasswordOrOtpData > ,
data : Json < PasswordOrOtpData > ,
rotate : bool ,
rotate : bool ,
@ -3121,21 +3126,18 @@ async fn _api_key(
// Validate the admin users password/otp
// Validate the admin users password/otp
data . validate ( & user , true , & conn ) . await ? ;
data . validate ( & user , true , & conn ) . await ? ;
let org_api_key = match OrganizationApiKey ::find_by_org_uuid ( org_id , & conn ) . await {
let org_api_key = if let Some ( mut org_api_key ) = OrganizationApiKey ::find_by_org_uuid ( org_id , & conn ) . await {
Some ( mut org_api_key ) = > {
if rotate {
if rotate {
org_api_key . api_key = crate ::crypto ::generate_api_key ( ) ;
org_api_key . api_key = crate ::crypto ::generate_api_key ( ) ;
org_api_key . revision_date = chrono ::Utc ::now ( ) . naive_utc ( ) ;
org_api_key . revision_date = chrono ::Utc ::now ( ) . naive_utc ( ) ;
org_api_key . save ( & conn ) . await . expect ( "Error rotating organization API Key" ) ;
org_api_key . save ( & conn ) . await . expect ( "Error rotating organization API Key" ) ;
}
org_api_key
}
None = > {
let api_key = crate ::crypto ::generate_api_key ( ) ;
let new_org_api_key = OrganizationApiKey ::new ( org_id . clone ( ) , api_key ) ;
new_org_api_key . save ( & conn ) . await . expect ( "Error creating organization API Key" ) ;
new_org_api_key
}
}
org_api_key
} else {
let api_key = crate ::crypto ::generate_api_key ( ) ;
let new_org_api_key = OrganizationApiKey ::new ( org_id . clone ( ) , api_key ) ;
new_org_api_key . save ( & conn ) . await . expect ( "Error creating organization API Key" ) ;
new_org_api_key
} ;
} ;
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -3146,13 +3148,13 @@ async fn _api_key(
}
}
#[ post( " /organizations/<org_id>/api-key " , data = " <data> " ) ]
#[ post( " /organizations/<org_id>/api-key " , data = " <data> " ) ]
async fn api_key (
async fn post_ api_key(
org_id : OrganizationId ,
org_id : OrganizationId ,
data : Json < PasswordOrOtpData > ,
data : Json < PasswordOrOtpData > ,
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
_ api_key( & org_id , data , false , headers , conn ) . await
api_key ( & org_id , data , false , headers , conn ) . await
}
}
#[ post( " /organizations/<org_id>/rotate-api-key " , data = " <data> " ) ]
#[ post( " /organizations/<org_id>/rotate-api-key " , data = " <data> " ) ]
@ -3162,5 +3164,5 @@ async fn rotate_api_key(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
_ api_key( & org_id , data , true , headers , conn ) . await
api_key ( & org_id , data , true , headers , conn ) . await
}
}