@ -374,6 +374,21 @@ async fn get_org_collections_details(
| | ( CONFIG . org_groups_enabled ( )
| | ( CONFIG . org_groups_enabled ( )
& & GroupUser ::has_full_access_by_member ( & org_id , & member . uuid , & mut conn ) . await ) ;
& & GroupUser ::has_full_access_by_member ( & org_id , & member . uuid , & mut conn ) . await ) ;
// Get all admins, owners and managers who can manage/access all
// Those are currently not listed in the col_users but need to be listed too.
let manage_all_members : Vec < Value > = Membership ::find_confirmed_and_manage_all_by_org ( & org_id , & mut conn )
. await
. into_iter ( )
. map ( | member | {
json ! ( {
"id" : member . uuid ,
"readOnly" : false ,
"hidePasswords" : false ,
"manage" : true ,
} )
} )
. collect ( ) ;
for col in Collection ::find_by_organization ( & org_id , & mut conn ) . await {
for col in Collection ::find_by_organization ( & org_id , & mut conn ) . await {
// check whether the current user has access to the given collection
// check whether the current user has access to the given collection
let assigned = has_full_access_to_org
let assigned = has_full_access_to_org
@ -382,7 +397,7 @@ async fn get_org_collections_details(
& & GroupUser ::has_access_to_collection_by_member ( & col . uuid , & member . uuid , & mut conn ) . await ) ;
& & GroupUser ::has_access_to_collection_by_member ( & col . uuid , & member . uuid , & mut conn ) . await ) ;
// get the users assigned directly to the given collection
// get the users assigned directly to the given collection
let users : Vec < Value > = col_users
let mut users : Vec < Value > = col_users
. iter ( )
. iter ( )
. filter ( | collection_member | collection_member . collection_uuid = = col . uuid )
. filter ( | collection_member | collection_member . collection_uuid = = col . uuid )
. map ( | collection_member | {
. map ( | collection_member | {
@ -391,6 +406,7 @@ async fn get_org_collections_details(
)
)
} )
} )
. collect ( ) ;
. collect ( ) ;
users . extend_from_slice ( & manage_all_members ) ;
// get the group details for the given collection
// get the group details for the given collection
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
@ -681,6 +697,9 @@ async fn _delete_organization_collection(
headers : & ManagerHeaders ,
headers : & ManagerHeaders ,
conn : & mut DbConn ,
conn : & mut DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
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 {
err ! ( "Collection not found" , "Collection does not exist or does not belong to this organization" )
err ! ( "Collection not found" , "Collection does not exist or does not belong to this organization" )
} ;
} ;
@ -893,7 +912,7 @@ struct OrgIdData {
#[ get( " /ciphers/organization-details?<data..> " ) ]
#[ get( " /ciphers/organization-details?<data..> " ) ]
async fn get_org_details ( data : OrgIdData , headers : OrgMemberHeaders , mut conn : DbConn ) -> JsonResult {
async fn get_org_details ( data : OrgIdData , headers : OrgMemberHeaders , mut conn : DbConn ) -> JsonResult {
if data . organization_id ! = headers . org_id {
if data . organization_id ! = headers . membership . org_uu id {
err_code ! ( "Resource not found." , "Organization id's do not match" , rocket ::http ::Status ::NotFound . code ) ;
err_code ! ( "Resource not found." , "Organization id's do not match" , rocket ::http ::Status ::NotFound . code ) ;
}
}
@ -1180,6 +1199,9 @@ async fn reinvite_member(
headers : AdminHeaders ,
headers : AdminHeaders ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
_reinvite_member ( & org_id , & member_id , & headers . user . email , & mut conn ) . await
_reinvite_member ( & org_id , & member_id , & headers . user . email , & mut conn ) . await
}
}
@ -1397,6 +1419,9 @@ async fn _confirm_invite(
conn : & mut DbConn ,
conn : & mut DbConn ,
nt : & Notify < '_ > ,
nt : & Notify < '_ > ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if key . is_empty ( ) | | member_id . is_empty ( ) {
if key . is_empty ( ) | | member_id . is_empty ( ) {
err ! ( "Key or UserId is not set, unable to process request" ) ;
err ! ( "Key or UserId is not set, unable to process request" ) ;
}
}
@ -1460,7 +1485,7 @@ async fn _confirm_invite(
let save_result = member_to_confirm . save ( conn ) . await ;
let save_result = member_to_confirm . save ( conn ) . await ;
if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user ) . await ;
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , & headers . device . push_uuid , conn ) . await ;
}
}
save_result
save_result
@ -1719,6 +1744,9 @@ async fn _delete_member(
conn : & mut DbConn ,
conn : & mut DbConn ,
nt : & Notify < '_ > ,
nt : & Notify < '_ > ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
let Some ( member_to_delete ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
let Some ( member_to_delete ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
err ! ( "User to delete isn't member of the organization" )
err ! ( "User to delete isn't member of the organization" )
} ;
} ;
@ -1747,7 +1775,7 @@ async fn _delete_member(
. await ;
. await ;
if let Some ( user ) = User ::find_by_uuid ( & member_to_delete . user_uuid , conn ) . await {
if let Some ( user ) = User ::find_by_uuid ( & member_to_delete . user_uuid , conn ) . await {
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user ) . await ;
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , & headers . device . push_uuid , conn ) . await ;
}
}
member_to_delete . delete ( conn ) . await
member_to_delete . delete ( conn ) . await
@ -1813,16 +1841,20 @@ struct RelationsData {
value : usize ,
value : usize ,
}
}
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/ImportCiphersController.cs#L62
#[ post( " /ciphers/import-organization?<query..> " , data = " <data> " ) ]
#[ post( " /ciphers/import-organization?<query..> " , data = " <data> " ) ]
async fn post_org_import (
async fn post_org_import (
query : OrgIdData ,
query : OrgIdData ,
data : Json < ImportData > ,
data : Json < ImportData > ,
headers : Admin Headers,
headers : OrgMember Headers,
mut conn : DbConn ,
mut conn : DbConn ,
nt : Notify < '_ > ,
nt : Notify < '_ > ,
) -> EmptyResult {
) -> EmptyResult {
let data : ImportData = data . into_inner ( ) ;
let org_id = query . organization_id ;
let org_id = query . organization_id ;
if org_id ! = headers . membership . org_uuid {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
let data : ImportData = data . into_inner ( ) ;
// Validate the import before continuing
// Validate the import before continuing
// Bitwarden does not process the import if there is one item invalid.
// Bitwarden does not process the import if there is one item invalid.
@ -1835,8 +1867,20 @@ async fn post_org_import(
let mut collections : Vec < CollectionId > = Vec ::with_capacity ( data . collections . len ( ) ) ;
let mut collections : Vec < CollectionId > = Vec ::with_capacity ( data . collections . len ( ) ) ;
for col in data . collections {
for col in data . collections {
let collection_uuid = if existing_collections . contains ( & col . id ) {
let collection_uuid = if existing_collections . contains ( & col . id ) {
col . id . unwrap ( )
let col_id = col . id . unwrap ( ) ;
// When not an Owner or Admin, check if the member is allowed to access the collection.
if headers . membership . atype < MembershipType ::Admin
& & ! Collection ::can_access_collection ( & headers . membership , & col_id , & mut conn ) . await
{
err ! ( Compact , "The current user isn't allowed to manage this collection" )
}
col_id
} else {
} else {
// We do not allow users or managers which can not manage all collections to create new collections
// If there is any collection other than an existing import collection, abort the import.
if headers . membership . atype < = MembershipType ::Manager & & ! headers . membership . has_full_access ( ) {
err ! ( Compact , "The current user isn't allowed to create new collections" )
}
let new_collection = Collection ::new ( org_id . clone ( ) , col . name , col . external_id ) ;
let new_collection = Collection ::new ( org_id . clone ( ) , col . name , col . external_id ) ;
new_collection . save ( & mut conn ) . await ? ;
new_collection . save ( & mut conn ) . await ? ;
new_collection . uuid
new_collection . uuid
@ -1859,7 +1903,17 @@ async fn post_org_import(
// Always clear folder_id's via an organization import
// Always clear folder_id's via an organization import
cipher_data . folder_id = None ;
cipher_data . folder_id = None ;
let mut cipher = Cipher ::new ( cipher_data . r#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 . ok ( ) ;
update_cipher_from_data (
& mut cipher ,
cipher_data ,
& headers ,
Some ( collections . clone ( ) ) ,
& mut conn ,
& nt ,
UpdateType ::None ,
)
. await
. ok ( ) ;
ciphers . push ( cipher . uuid ) ;
ciphers . push ( cipher . uuid ) ;
}
}
@ -1890,12 +1944,6 @@ struct BulkCollectionsData {
async fn post_bulk_collections ( data : Json < BulkCollectionsData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
async fn post_bulk_collections ( data : Json < BulkCollectionsData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
let data : BulkCollectionsData = data . into_inner ( ) ;
let data : BulkCollectionsData = data . into_inner ( ) ;
// This feature does not seem to be active on all the clients
// To prevent future issues, add a check to block a call when this is set to true
if data . remove_collections {
err ! ( "Bulk removing of collections is not yet implemented" )
}
// Get all the collection available to the user in one query
// Get all the collection available to the user in one query
// Also filter based upon the provided collections
// Also filter based upon the provided collections
let user_collections : HashMap < CollectionId , Collection > =
let user_collections : HashMap < CollectionId , Collection > =
@ -1924,10 +1972,18 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
// 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 , & mut conn ) . await {
if let Some ( cipher ) = Cipher ::find_by_uuid_and_org ( cipher_id , & data . organization_id , & mut conn ) . await {
if cipher . is_write_accessible_to_user ( & headers . user . uuid , & mut conn ) . await {
if cipher . is_write_accessible_to_user ( & headers . user . uuid , & mut 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.
if data . remove_collections {
for collection in & data . collection_ids {
CollectionCipher ::delete ( & cipher . uuid , collection , & mut conn ) . await ? ;
}
} else {
for collection in & data . collection_ids {
for collection in & data . collection_ids {
CollectionCipher ::save ( & cipher . uuid , collection , & mut conn ) . await ? ;
CollectionCipher ::save ( & cipher . uuid , collection , & mut conn ) . await ? ;
}
}
}
}
}
} ;
} ;
}
}
@ -2402,6 +2458,9 @@ async fn _revoke_member(
headers : & AdminHeaders ,
headers : & AdminHeaders ,
conn : & mut DbConn ,
conn : & mut DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
Some ( mut member ) if member . status > MembershipStatus ::Revoked as i32 = > {
Some ( mut member ) if member . status > MembershipStatus ::Revoked as i32 = > {
if member . user_uuid = = headers . user . uuid {
if member . user_uuid = = headers . user . uuid {
@ -2509,6 +2568,9 @@ async fn _restore_member(
headers : & AdminHeaders ,
headers : & AdminHeaders ,
conn : & mut DbConn ,
conn : & mut DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
Some ( mut member ) if member . status < MembershipStatus ::Accepted as i32 = > {
Some ( mut member ) if member . status < MembershipStatus ::Accepted as i32 = > {
if member . user_uuid = = headers . user . uuid {
if member . user_uuid = = headers . user . uuid {
@ -2556,19 +2618,28 @@ async fn _restore_member(
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ get( " /organizations/<org_id>/groups " ) ]
async fn get_groups_data (
async fn get_groups ( org_id : OrganizationId , headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
details : bool ,
org_id : OrganizationId ,
headers : ManagerHeadersLoose ,
mut conn : DbConn ,
) -> JsonResult {
if org_id ! = headers . membership . org_uuid {
if org_id ! = headers . membership . org_uuid {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
// Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
let groups = Group ::find_by_organization ( & org_id , & mut conn ) . await ;
let groups = Group ::find_by_organization ( & org_id , & mut conn ) . await ;
let mut groups_json = Vec ::with_capacity ( groups . len ( ) ) ;
let mut groups_json = Vec ::with_capacity ( groups . len ( ) ) ;
if details {
for g in groups {
for g in groups {
groups_json . push ( g . to_json_details ( & mut conn ) . await )
groups_json . push ( g . to_json_details ( & mut conn ) . await )
}
}
} else {
for g in groups {
groups_json . push ( g . to_json ( ) )
}
}
groups_json
groups_json
} 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,
@ -2583,9 +2654,14 @@ async fn get_groups(org_id: OrganizationId, headers: ManagerHeadersLoose, mut co
} ) ) )
} ) ) )
}
}
#[ get( " /organizations/<org_id>/groups " ) ]
async fn get_groups ( org_id : OrganizationId , headers : ManagerHeadersLoose , conn : DbConn ) -> JsonResult {
get_groups_data ( false , org_id , headers , conn ) . await
}
#[ get( " /organizations/<org_id>/groups/details " , rank = 1) ]
#[ get( " /organizations/<org_id>/groups/details " , rank = 1) ]
async fn get_groups_details ( org_id : OrganizationId , headers : ManagerHeadersLoose , conn : DbConn ) -> JsonResult {
async fn get_groups_details ( org_id : OrganizationId , headers : ManagerHeadersLoose , conn : DbConn ) -> JsonResult {
get_groups ( org_id , headers , conn ) . await
get_groups_data ( true , org_id , headers , conn ) . await
}
}
#[ derive(Deserialize) ]
#[ derive(Deserialize) ]
@ -2647,6 +2723,9 @@ async fn post_groups(
data : Json < GroupRequest > ,
data : Json < GroupRequest > ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if ! CONFIG . org_groups_enabled ( ) {
if ! CONFIG . org_groups_enabled ( ) {
err ! ( "Group support is disabled" ) ;
err ! ( "Group support is disabled" ) ;
}
}
@ -2676,6 +2755,9 @@ async fn put_group(
headers : AdminHeaders ,
headers : AdminHeaders ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if ! CONFIG . org_groups_enabled ( ) {
if ! CONFIG . org_groups_enabled ( ) {
err ! ( "Group support is disabled" ) ;
err ! ( "Group support is disabled" ) ;
}
}
@ -2740,7 +2822,8 @@ async fn add_update_group(
"organizationId" : group . organizations_uuid ,
"organizationId" : group . organizations_uuid ,
"name" : group . name ,
"name" : group . name ,
"accessAll" : group . access_all ,
"accessAll" : group . access_all ,
"externalId" : group . external_id
"externalId" : group . external_id ,
"object" : "group"
} ) ) )
} ) ) )
}
}
@ -2791,6 +2874,9 @@ async fn _delete_group(
headers : & AdminHeaders ,
headers : & AdminHeaders ,
conn : & mut DbConn ,
conn : & mut DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if ! CONFIG . org_groups_enabled ( ) {
if ! CONFIG . org_groups_enabled ( ) {
err ! ( "Group support is disabled" ) ;
err ! ( "Group support is disabled" ) ;
}
}
@ -2820,6 +2906,9 @@ async fn bulk_delete_groups(
headers : AdminHeaders ,
headers : AdminHeaders ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if ! CONFIG . org_groups_enabled ( ) {
if ! CONFIG . org_groups_enabled ( ) {
err ! ( "Group support is disabled" ) ;
err ! ( "Group support is disabled" ) ;
}
}
@ -2883,6 +2972,9 @@ async fn put_group_members(
data : Json < Vec < MembershipId > > ,
data : Json < Vec < MembershipId > > ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
if ! CONFIG . org_groups_enabled ( ) {
if ! CONFIG . org_groups_enabled ( ) {
err ! ( "Group support is disabled" ) ;
err ! ( "Group support is disabled" ) ;
}
}
@ -3067,7 +3159,7 @@ async fn get_organization_public_key(
headers : OrgMemberHeaders ,
headers : OrgMemberHeaders ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
if org_id ! = headers . org_id {
if org_id ! = headers . membership . org_uu id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
@ -3081,7 +3173,7 @@ async fn get_organization_public_key(
}
}
// Obsolete - Renamed to public-key (2023.8), left for backwards compatibility with older clients
// Obsolete - Renamed to public-key (2023.8), left for backwards compatibility with older clients
// https://github.com/bitwarden/server/blob/25dc0c9178e3e3584074bbef0d4be827b7c89415/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L463-L468
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L487-L492
#[ get( " /organizations/<org_id>/keys " ) ]
#[ get( " /organizations/<org_id>/keys " ) ]
async fn get_organization_keys ( org_id : OrganizationId , headers : OrgMemberHeaders , conn : DbConn ) -> JsonResult {
async fn get_organization_keys ( org_id : OrganizationId , headers : OrgMemberHeaders , conn : DbConn ) -> JsonResult {
get_organization_public_key ( org_id , headers , conn ) . await
get_organization_public_key ( org_id , headers , conn ) . await
@ -3132,7 +3224,7 @@ async fn put_reset_password(
user . set_password ( reset_request . new_master_password_hash . as_str ( ) , Some ( reset_request . key ) , true , None ) ;
user . set_password ( reset_request . new_master_password_hash . as_str ( ) , Some ( reset_request . key ) , true , None ) ;
user . save ( & mut conn ) . await ? ;
user . save ( & mut conn ) . await ? ;
nt . send_logout ( & user , None ) . await ;
nt . send_logout ( & user , None , & mut conn ) . await ;
log_event (
log_event (
EventType ::OrganizationUserAdminResetPassword as i32 ,
EventType ::OrganizationUserAdminResetPassword as i32 ,
@ -3172,16 +3264,16 @@ async fn get_reset_password_details(
check_reset_password_applicable_and_permissions ( & org_id , & member_id , & headers , & mut conn ) . await ? ;
check_reset_password_applicable_and_permissions ( & org_id , & member_id , & headers , & mut conn ) . await ? ;
// https://github.com/bitwarden/server/blob/3b50ccb9f804efaacdc46bed5b60e5b28eddefcf/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs#L111
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Models/Response/Organizations/OrganizationUserResponseModel.cs#L190
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"object" : "organizationUserResetPasswordDetails" ,
"object" : "organizationUserResetPasswordDetails" ,
"kdf" :user . client_kdf_type ,
"organizationUserId" : member_id ,
"kdfIterations " :user . client_kdf_i ter ,
"kdf" : user . client_kdf_typ e ,
"kdfMemory" :user . client_kdf_memory ,
"kdfIterations" : user . client_kdf_iter ,
"kdfParallelism" :user . client_kdf_parallelism ,
"kdfMemory" : user . client_kdf_memory ,
"resetPasswordKey" :member . reset_password_key ,
"kdfParallelism" : user . client_kdf_parallelism ,
"encryptedPrivateKey" :org . private _key ,
"resetPasswordKey" : member . reset_password _key ,
"encryptedPrivateKey" : org . private_key ,
} ) ) )
} ) ) )
}
}
@ -3269,6 +3361,9 @@ async fn put_reset_password_enrollment(
// NOTE: It seems clients can't handle uppercase-first keys!!
// NOTE: It seems clients can't handle uppercase-first keys!!
// We need to convert all keys so they have the first character to be a lowercase.
// We need to convert all keys so they have the first character to be a lowercase.
// Else the export will be just an empty JSON file.
// Else the export will be just an empty JSON file.
// We currently only support exports by members of the Admin or Owner status.
// Vaultwarden does not yet support exporting only managed collections!
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/OrganizationExportController.cs#L52
#[ get( " /organizations/<org_id>/export " ) ]
#[ get( " /organizations/<org_id>/export " ) ]
async fn get_org_export ( org_id : OrganizationId , headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
async fn get_org_export ( org_id : OrganizationId , headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
@ -3288,6 +3383,9 @@ async fn _api_key(
headers : AdminHeaders ,
headers : AdminHeaders ,
mut conn : DbConn ,
mut conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
if org_id ! = & headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
let data : PasswordOrOtpData = data . into_inner ( ) ;
let data : PasswordOrOtpData = data . into_inner ( ) ;
let user = headers . user ;
let user = headers . user ;