2020-07-14 18:00:09 +02:00
use num_traits ::FromPrimitive ;
2021-11-07 18:53:39 +01:00
use rocket ::serde ::json ::Json ;
use rocket ::Route ;
2018-10-10 20:40:39 +02:00
use serde_json ::Value ;
2024-08-07 22:46:03 +02:00
use std ::collections ::{ HashMap , HashSet } ;
2018-10-10 20:40:39 +02:00
2025-01-20 20:21:44 +01:00
use crate ::api ::admin ::FAKE_ADMIN_UUID ;
2020-07-14 18:00:09 +02:00
use crate ::{
2022-05-04 21:13:05 +02:00
api ::{
2024-01-01 19:41:40 +01:00
core ::{ log_event , two_factor , CipherSyncData , CipherSyncType } ,
2024-06-23 21:31:02 +02:00
EmptyResult , JsonResult , Notify , PasswordOrOtpData , UpdateType ,
2022-05-04 21:13:05 +02:00
} ,
2025-01-25 01:32:09 +01:00
auth ::{
decode_invite , AdminHeaders , ClientVersion , Headers , ManagerHeaders , ManagerHeadersLoose , OrgMemberHeaders ,
OwnerHeaders ,
} ,
2020-07-14 18:00:09 +02:00
db ::{ models ::* , DbConn } ,
2022-09-24 18:27:13 +02:00
mail ,
2024-01-27 02:43:26 +01:00
util ::{ convert_json_key_lcase_first , NumberOrString } ,
2022-09-24 18:27:13 +02:00
CONFIG ,
2019-01-25 17:43:51 +01:00
} ;
2018-10-10 20:40:39 +02:00
pub fn routes ( ) -> Vec < Route > {
routes! [
get_organization ,
create_organization ,
delete_organization ,
post_delete_organization ,
leave_organization ,
get_user_collections ,
get_org_collections ,
2023-02-21 21:58:37 +01:00
get_org_collections_details ,
2018-10-10 20:40:39 +02:00
get_org_collection_detail ,
get_collection_users ,
2019-01-25 17:43:51 +01:00
put_collection_users ,
2018-10-10 20:40:39 +02:00
put_organization ,
post_organization ,
post_organization_collections ,
2025-01-09 18:37:23 +01:00
delete_organization_collection_member ,
post_organization_collection_delete_member ,
2018-10-10 20:40:39 +02:00
post_organization_collection_update ,
put_organization_collection_update ,
delete_organization_collection ,
post_organization_collection_delete ,
2023-03-18 20:52:55 +01:00
bulk_delete_organization_collections ,
2024-08-07 22:46:03 +02:00
post_bulk_collections ,
2018-10-10 20:40:39 +02:00
get_org_details ,
2025-01-09 18:37:23 +01:00
get_members ,
2018-10-10 20:40:39 +02:00
send_invite ,
2025-01-09 18:37:23 +01:00
reinvite_member ,
bulk_reinvite_members ,
2018-10-10 20:40:39 +02:00
confirm_invite ,
2021-09-18 14:22:14 +02:00
bulk_confirm_invite ,
2018-12-14 21:56:00 -05:00
accept_invite ,
2025-01-04 19:31:59 +01:00
get_org_user_mini_details ,
2018-10-10 20:40:39 +02:00
get_user ,
2025-01-09 18:37:23 +01:00
edit_member ,
put_member ,
delete_member ,
bulk_delete_member ,
post_delete_member ,
2018-10-10 20:40:39 +02:00
post_org_import ,
2020-03-14 13:22:30 +01:00
list_policies ,
2020-03-20 10:51:17 +01:00
list_policies_token ,
2020-03-14 13:22:30 +01:00
get_policy ,
put_policy ,
2021-01-31 21:46:37 +01:00
get_organization_tax ,
2020-09-14 08:34:17 +02:00
get_plans ,
2023-08-24 22:33:35 +02:00
get_plans_all ,
2021-01-31 21:46:37 +01:00
get_plans_tax_rates ,
2021-02-06 18:22:39 +01:00
import ,
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
post_org_keys ,
2023-01-25 08:06:21 +01:00
get_organization_keys ,
2024-08-11 19:39:56 +02:00
get_organization_public_key ,
2021-09-18 14:22:14 +02:00
bulk_public_keys ,
2025-01-09 18:37:23 +01:00
deactivate_member ,
bulk_deactivate_members ,
revoke_member ,
bulk_revoke_members ,
activate_member ,
bulk_activate_members ,
restore_member ,
bulk_restore_members ,
2022-10-20 15:31:53 +02:00
get_groups ,
2025-01-04 19:31:59 +01:00
get_groups_details ,
2022-10-20 15:31:53 +02:00
post_groups ,
get_group ,
put_group ,
post_group ,
get_group_details ,
delete_group ,
post_delete_group ,
2023-03-18 21:13:06 +01:00
bulk_delete_groups ,
2025-01-09 18:37:23 +01:00
get_group_members ,
put_group_members ,
2022-10-20 15:31:53 +02:00
get_user_groups ,
post_user_groups ,
put_user_groups ,
2025-01-09 18:37:23 +01:00
delete_group_member ,
post_delete_group_member ,
2023-01-25 08:06:21 +01:00
put_reset_password_enrollment ,
get_reset_password_details ,
put_reset_password ,
2023-06-02 21:36:15 +02:00
get_org_export ,
api_key ,
rotate_api_key ,
2025-01-04 19:31:59 +01:00
get_billing_metadata ,
2018-10-10 20:40:39 +02:00
]
}
2018-04-24 22:01:55 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-24 22:01:55 +02:00
struct OrgData {
2024-06-23 21:31:02 +02:00
billing_email : String ,
collection_name : String ,
key : String ,
name : String ,
keys : Option < OrgKeyData > ,
#[ allow(dead_code) ]
plan_type : NumberOrString , // Ignored, always use the same plan
2018-04-24 22:01:55 +02:00
}
2018-04-20 17:35:11 +01:00
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-20 17:35:11 +01:00
struct OrganizationUpdateData {
2024-06-23 21:31:02 +02:00
billing_email : String ,
name : String ,
2018-04-20 17:35:11 +01:00
}
2022-10-20 15:31:53 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-20 17:35:11 +01:00
struct NewCollectionData {
2024-06-23 21:31:02 +02:00
name : String ,
2025-01-09 18:37:23 +01:00
groups : Vec < NewCollectionGroupData > ,
users : Vec < NewCollectionMemberData > ,
id : Option < CollectionId > ,
2024-06-23 21:31:02 +02:00
external_id : Option < String > ,
2022-10-20 15:31:53 +02:00
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2025-01-09 18:37:23 +01:00
struct NewCollectionGroupData {
2024-06-23 21:31:02 +02:00
hide_passwords : bool ,
2025-01-09 18:37:23 +01:00
id : GroupId ,
read_only : bool ,
2025-01-21 23:33:41 +01:00
manage : bool ,
2025-01-09 18:37:23 +01:00
}
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct NewCollectionMemberData {
hide_passwords : bool ,
id : MembershipId ,
2024-06-23 21:31:02 +02:00
read_only : bool ,
2025-01-21 23:33:41 +01:00
manage : bool ,
2018-04-20 17:35:11 +01:00
}
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
struct OrgKeyData {
2024-06-23 21:31:02 +02:00
encrypted_private_key : String ,
public_key : String ,
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
}
2021-09-18 14:22:14 +02:00
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2025-01-09 18:37:23 +01:00
struct BulkGroupIds {
ids : Vec < GroupId > ,
}
#[ derive(Deserialize, Debug) ]
#[ serde(rename_all = " camelCase " ) ]
struct BulkMembershipIds {
ids : Vec < MembershipId > ,
2021-09-18 14:22:14 +02:00
}
2018-02-17 22:30:19 +01:00
#[ post( " /organizations " , data = " <data> " ) ]
2024-06-23 21:31:02 +02:00
async fn create_organization ( headers : Headers , data : Json < OrgData > , mut conn : DbConn ) -> JsonResult {
2020-08-05 22:35:29 -07:00
if ! CONFIG . is_org_creation_allowed ( & headers . user . email ) {
err! ( " User not allowed to create organizations " )
}
2022-05-20 23:39:47 +02:00
if OrgPolicy ::is_applicable_to_user ( & headers . user . uuid , OrgPolicyType ::SingleOrg , None , & mut conn ) . await {
2021-09-24 17:55:49 +02:00
err! (
" You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization. "
)
}
2020-08-05 22:35:29 -07:00
2024-06-23 21:31:02 +02:00
let data : OrgData = data . into_inner ( ) ;
let ( private_key , public_key ) = if data . keys . is_some ( ) {
let keys : OrgKeyData = data . keys . unwrap ( ) ;
( Some ( keys . encrypted_private_key ) , Some ( keys . public_key ) )
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
} else {
( None , None )
} ;
2018-02-17 22:30:19 +01:00
2024-06-23 21:31:02 +02:00
let org = Organization ::new ( data . name , data . billing_email , private_key , public_key ) ;
2025-01-09 18:37:23 +01:00
let mut member = Membership ::new ( headers . user . uuid , org . uuid . clone ( ) ) ;
2024-06-23 21:31:02 +02:00
let collection = Collection ::new ( org . uuid . clone ( ) , data . collection_name , None ) ;
2018-02-17 22:30:19 +01:00
2025-01-09 18:37:23 +01:00
member . akey = data . key ;
member . access_all = true ;
member . atype = MembershipType ::Owner as i32 ;
member . status = MembershipStatus ::Confirmed as i32 ;
2018-04-24 22:01:55 +02:00
2022-05-20 23:39:47 +02:00
org . save ( & mut conn ) . await ? ;
2025-01-09 18:37:23 +01:00
member . save ( & mut conn ) . await ? ;
2022-05-20 23:39:47 +02:00
collection . save ( & mut conn ) . await ? ;
2018-04-24 22:01:55 +02:00
Ok ( Json ( org . to_json ( ) ) )
2018-02-17 22:30:19 +01:00
}
2018-08-13 16:45:30 +01:00
#[ delete( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn delete_organization (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2024-06-23 21:31:02 +02:00
data : Json < PasswordOrOtpData > ,
2018-12-30 23:34:31 +01:00
headers : OwnerHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : PasswordOrOtpData = data . into_inner ( ) ;
2018-04-25 00:34:40 +02:00
2023-11-12 22:15:44 +01:00
data . validate ( & headers . user , true , & mut conn ) . await ? ;
2018-05-18 16:52:51 +01:00
2025-01-09 18:37:23 +01:00
match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-05-18 16:52:51 +01:00
None = > err! ( " Organization not found " ) ,
2022-05-20 23:39:47 +02:00
Some ( org ) = > org . delete ( & mut conn ) . await ,
2018-05-18 16:52:51 +01:00
}
2018-04-25 00:34:40 +02:00
}
2018-08-13 16:45:30 +01:00
#[ post( " /organizations/<org_id>/delete " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn post_delete_organization (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2024-06-23 21:31:02 +02:00
data : Json < PasswordOrOtpData > ,
2018-12-30 23:34:31 +01:00
headers : OwnerHeaders ,
conn : DbConn ,
) -> EmptyResult {
2021-11-16 17:07:55 +01:00
delete_organization ( org_id , data , headers , conn ) . await
2018-08-13 16:45:30 +01:00
}
2018-07-11 16:30:03 +02:00
#[ post( " /organizations/<org_id>/leave " ) ]
2025-01-09 18:37:23 +01:00
async fn leave_organization ( org_id : OrganizationId , headers : Headers , mut conn : DbConn ) -> EmptyResult {
match Membership ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
2018-07-11 16:30:03 +02:00
None = > err! ( " User not part of organization " ) ,
2025-01-09 18:37:23 +01:00
Some ( member ) = > {
if member . atype = = MembershipType ::Owner
& & Membership ::count_confirmed_by_org_and_type ( & org_id , MembershipType ::Owner , & mut conn ) . await < = 1
2022-08-20 16:42:36 +02:00
{
err! ( " The last owner can't leave " )
2018-07-11 16:30:03 +02:00
}
2018-12-30 23:34:31 +01:00
2022-11-20 19:15:45 +01:00
log_event (
2025-01-28 11:25:53 +01:00
EventType ::OrganizationUserLeft as i32 ,
2025-01-09 18:37:23 +01:00
& member . uuid ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
member . delete ( & mut conn ) . await
2018-07-11 16:30:03 +02:00
}
}
}
2018-04-20 17:35:11 +01:00
#[ get( " /organizations/<org_id> " ) ]
2025-01-25 01:32:09 +01:00
async fn get_organization ( org_id : OrganizationId , headers : OwnerHeaders , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-04-20 17:35:11 +01:00
Some ( organization ) = > Ok ( Json ( organization . to_json ( ) ) ) ,
2018-12-30 23:34:31 +01:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 17:35:11 +01:00
}
}
2018-08-21 13:25:52 +01:00
#[ put( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn put_organization (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2018-12-30 23:34:31 +01:00
headers : OwnerHeaders ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUpdateData > ,
2018-12-30 23:34:31 +01:00
conn : DbConn ,
) -> JsonResult {
2023-03-09 16:31:28 +01:00
post_organization ( org_id , headers , data , conn ) . await
2018-08-21 13:25:52 +01:00
}
2018-04-20 17:35:11 +01:00
#[ post( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn post_organization (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2022-11-20 19:15:45 +01:00
headers : OwnerHeaders ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUpdateData > ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : OrganizationUpdateData = data . into_inner ( ) ;
2018-04-20 17:35:11 +01:00
2025-01-09 18:37:23 +01:00
let Some ( mut org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
2025-01-25 01:32:09 +01:00
err! ( " Organization not found " )
2018-04-20 17:35:11 +01:00
} ;
2024-06-23 21:31:02 +02:00
org . name = data . name ;
2025-01-25 01:32:09 +01:00
org . billing_email = data . billing_email . to_lowercase ( ) ;
2018-04-20 17:35:11 +01:00
2022-05-20 23:39:47 +02:00
org . save ( & mut conn ) . await ? ;
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUpdated as i32 ,
2025-01-09 18:37:23 +01:00
org_id . as_ref ( ) ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2018-12-19 21:52:53 +01:00
Ok ( Json ( org . to_json ( ) ) )
2018-04-20 17:35:11 +01:00
}
2018-02-17 22:30:19 +01:00
// GET /api/collections?writeOnly=false
#[ get( " /collections " ) ]
2022-05-20 23:39:47 +02:00
async fn get_user_collections ( headers : Headers , mut conn : DbConn ) -> Json < Value > {
2021-03-27 15:07:26 +00:00
Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " :
2024-01-25 22:02:07 +01:00
Collection ::find_by_user_uuid ( headers . user . uuid , & mut conn ) . await
2018-04-20 17:35:11 +01:00
. iter ( )
2018-09-13 15:16:24 +02:00
. map ( Collection ::to_json )
. collect ::< Value > ( ) ,
2024-06-23 21:31:02 +02:00
" object " : " list " ,
" continuationToken " : null ,
2021-03-27 15:07:26 +00:00
} ) )
2018-02-17 22:30:19 +01:00
}
#[ get( " /organizations/<org_id>/collections " ) ]
2025-01-25 01:32:09 +01:00
async fn get_org_collections ( org_id : OrganizationId , headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
Ok ( Json ( json! ( {
2025-01-09 18:37:23 +01:00
" data " : _get_org_collections ( & org_id , & mut conn ) . await ,
2024-06-23 21:31:02 +02:00
" object " : " list " ,
" continuationToken " : null ,
2025-01-25 01:32:09 +01:00
} ) ) )
2022-09-24 18:27:13 +02:00
}
2023-02-21 21:58:37 +01:00
#[ get( " /organizations/<org_id>/collections/details " ) ]
2025-01-09 18:37:23 +01:00
async fn get_org_collections_details (
org_id : OrganizationId ,
headers : ManagerHeadersLoose ,
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2023-02-21 21:58:37 +01:00
let mut data = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User is not part of organization " )
2023-02-27 16:37:58 +01:00
} ;
2024-03-17 22:11:34 +01:00
// get all collection memberships for the current organization
2025-01-09 18:37:23 +01:00
let col_users = CollectionUser ::find_by_organization_swap_user_uuid_with_member_uuid ( & org_id , & mut conn ) . await ;
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
2025-01-04 19:31:59 +01:00
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
2025-01-09 18:37:23 +01:00
let membership_type : HashMap < MembershipId , i32 > =
Membership ::find_confirmed_by_org ( & org_id , & mut conn ) . await . into_iter ( ) . map ( | m | ( m . uuid , m . atype ) ) . collect ( ) ;
2023-02-27 16:37:58 +01:00
2024-03-17 22:11:34 +01:00
// check if current user has full access to the organization (either directly or via any group)
2025-01-09 18:37:23 +01:00
let has_full_access_to_org = member . access_all
2024-03-17 22:11:34 +01:00
| | ( CONFIG . org_groups_enabled ( )
2025-01-09 18:37:23 +01:00
& & GroupUser ::has_full_access_by_member ( & org_id , & member . uuid , & mut conn ) . await ) ;
2024-03-17 22:11:34 +01:00
2025-01-09 18:37:23 +01:00
for col in Collection ::find_by_organization ( & org_id , & mut conn ) . await {
2024-04-27 22:09:00 +02:00
// check whether the current user has access to the given collection
let assigned = has_full_access_to_org
2025-01-09 18:37:23 +01:00
| | CollectionUser ::has_access_to_collection_by_user ( & col . uuid , & member . user_uuid , & mut conn ) . await
2024-04-27 22:09:00 +02:00
| | ( CONFIG . org_groups_enabled ( )
2025-01-09 18:37:23 +01:00
& & GroupUser ::has_access_to_collection_by_member ( & col . uuid , & member . uuid , & mut conn ) . await ) ;
2023-02-27 16:37:58 +01:00
2024-03-17 22:11:34 +01:00
// get the users assigned directly to the given collection
2025-01-09 18:37:23 +01:00
let users : Vec < Value > = col_users
2023-02-21 21:58:37 +01:00
. iter ( )
2025-01-21 23:33:41 +01:00
. filter ( | collection_member | collection_member . collection_uuid = = col . uuid )
. map ( | collection_member | {
collection_member . to_json_details_for_member (
* membership_type . get ( & collection_member . membership_uuid ) . unwrap_or ( & ( MembershipType ::User as i32 ) ) ,
2025-01-04 19:31:59 +01:00
)
} )
2023-02-21 21:58:37 +01:00
. collect ( ) ;
2024-03-17 22:11:34 +01:00
// get the group details for the given collection
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
CollectionGroup ::find_by_collection ( & col . uuid , & mut conn )
. await
. iter ( )
2025-01-09 18:37:23 +01:00
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
2024-03-17 22:11:34 +01:00
. collect ( )
} else {
Vec ::with_capacity ( 0 )
} ;
2024-10-11 18:42:40 +02:00
let mut json_object = col . to_json_details ( & headers . user . uuid , None , & mut conn ) . await ;
2024-06-23 21:31:02 +02:00
json_object [ " assigned " ] = json! ( assigned ) ;
json_object [ " users " ] = json! ( users ) ;
json_object [ " groups " ] = json! ( groups ) ;
json_object [ " object " ] = json! ( " collectionAccessDetails " ) ;
2024-10-18 20:37:32 +02:00
json_object [ " unmanaged " ] = json! ( false ) ;
2023-02-21 21:58:37 +01:00
data . push ( json_object )
}
2023-02-27 16:37:58 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : data ,
" object " : " list " ,
" continuationToken " : null ,
2023-02-27 16:37:58 +01:00
} ) ) )
2023-02-21 21:58:37 +01:00
}
2025-01-09 18:37:23 +01:00
async fn _get_org_collections ( org_id : & OrganizationId , conn : & mut DbConn ) -> Value {
2022-11-07 17:13:34 +01:00
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
2018-02-17 22:30:19 +01:00
}
2018-04-20 17:35:11 +01:00
#[ post( " /organizations/<org_id>/collections " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn post_organization_collections (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2020-12-02 22:50:51 +01:00
headers : ManagerHeadersLoose ,
2024-06-23 21:31:02 +02:00
data : Json < NewCollectionData > ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : NewCollectionData = data . into_inner ( ) ;
2018-04-20 17:35:11 +01:00
2025-01-09 18:37:23 +01:00
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Can't find organization details " )
2018-04-20 17:35:11 +01:00
} ;
2024-06-23 21:31:02 +02:00
let collection = Collection ::new ( org . uuid , data . name , data . external_id ) ;
2022-05-20 23:39:47 +02:00
collection . save ( & mut conn ) . await ? ;
2018-05-04 19:25:50 +02:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::CollectionCreated as i32 ,
& collection . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2024-06-23 21:31:02 +02:00
for group in data . groups {
2025-01-21 23:33:41 +01:00
CollectionGroup ::new ( collection . uuid . clone ( ) , group . id , group . read_only , group . hide_passwords , group . manage )
2022-05-20 23:39:47 +02:00
. save ( & mut conn )
2022-10-20 15:31:53 +02:00
. await ? ;
}
2024-06-23 21:31:02 +02:00
for user in data . users {
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_uuid_and_org ( & user . id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User is not part of organization " )
2023-02-27 16:37:58 +01:00
} ;
2025-01-09 18:37:23 +01:00
if member . access_all {
2023-02-27 16:37:58 +01:00
continue ;
}
2025-01-21 23:33:41 +01:00
CollectionUser ::save (
& member . user_uuid ,
& collection . uuid ,
user . read_only ,
user . hide_passwords ,
user . manage ,
& mut conn ,
)
. await ? ;
2020-12-02 22:50:51 +01:00
}
2025-01-09 18:37:23 +01:00
if headers . membership . atype = = MembershipType ::Manager & & ! headers . membership . access_all {
2025-01-21 23:33:41 +01:00
CollectionUser ::save ( & headers . membership . user_uuid , & collection . uuid , false , false , false , & mut conn ) . await ? ;
2023-06-23 08:26:45 +02:00
}
2025-01-29 20:41:31 +01:00
Ok ( Json ( collection . to_json_details ( & headers . membership . user_uuid , None , & mut conn ) . await ) )
2018-04-20 17:35:11 +01:00
}
2018-08-13 16:45:30 +01:00
#[ put( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn put_organization_collection_update (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
2020-12-02 22:50:51 +01:00
headers : ManagerHeaders ,
2024-06-23 21:31:02 +02:00
data : Json < NewCollectionData > ,
2018-12-30 23:34:31 +01:00
conn : DbConn ,
) -> JsonResult {
2023-03-09 16:31:28 +01:00
post_organization_collection_update ( org_id , col_id , headers , data , conn ) . await
2018-08-13 16:45:30 +01:00
}
2018-04-20 17:35:11 +01:00
#[ post( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn post_organization_collection_update (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
2022-11-20 19:15:45 +01:00
headers : ManagerHeaders ,
2024-06-23 21:31:02 +02:00
data : Json < NewCollectionData > ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : NewCollectionData = data . into_inner ( ) ;
2018-04-20 17:35:11 +01:00
2025-01-09 18:37:23 +01:00
if Organization ::find_by_uuid ( & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " Can't find organization details " )
2018-04-20 17:35:11 +01:00
} ;
2025-01-09 18:37:23 +01:00
let Some ( mut collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Collection not found " )
2018-04-20 17:35:11 +01:00
} ;
2024-06-23 21:31:02 +02:00
collection . name = data . name ;
collection . external_id = match data . external_id {
2023-07-12 21:58:45 +02:00
Some ( external_id ) if ! external_id . trim ( ) . is_empty ( ) = > Some ( external_id ) ,
_ = > None ,
} ;
2022-05-20 23:39:47 +02:00
collection . save ( & mut conn ) . await ? ;
2018-04-20 17:35:11 +01:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::CollectionUpdated as i32 ,
& collection . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
CollectionGroup ::delete_all_by_collection ( & col_id , & mut conn ) . await ? ;
2022-10-20 15:31:53 +02:00
2024-06-23 21:31:02 +02:00
for group in data . groups {
2025-01-21 23:33:41 +01:00
CollectionGroup ::new ( col_id . clone ( ) , group . id , group . read_only , group . hide_passwords , group . manage )
. save ( & mut conn )
. await ? ;
2022-10-20 15:31:53 +02:00
}
2025-01-09 18:37:23 +01:00
CollectionUser ::delete_all_by_collection ( & col_id , & mut conn ) . await ? ;
2023-02-27 16:37:58 +01:00
2024-06-23 21:31:02 +02:00
for user in data . users {
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_uuid_and_org ( & user . id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User is not part of organization " )
2023-02-27 16:37:58 +01:00
} ;
2025-01-09 18:37:23 +01:00
if member . access_all {
2023-02-27 16:37:58 +01:00
continue ;
}
2025-01-21 23:33:41 +01:00
CollectionUser ::save ( & member . user_uuid , & col_id , user . read_only , user . hide_passwords , user . manage , & mut conn )
. await ? ;
2023-02-27 16:37:58 +01:00
}
2024-08-15 12:36:00 +02:00
Ok ( Json ( collection . to_json_details ( & headers . user . uuid , None , & mut conn ) . await ) )
2018-04-20 17:35:11 +01:00
}
2025-01-09 18:37:23 +01:00
#[ delete( " /organizations/<org_id>/collections/<col_id>/user/<member_id> " ) ]
async fn delete_organization_collection_member (
org_id : OrganizationId ,
col_id : CollectionId ,
member_id : MembershipId ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Collection not found " , " Collection does not exist or does not belong to this organization " )
2018-05-30 16:01:56 +01:00
} ;
2025-01-09 18:37:23 +01:00
match Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await {
2018-05-30 16:01:56 +01:00
None = > err! ( " User not found in organization " ) ,
2025-01-09 18:37:23 +01:00
Some ( member ) = > {
match CollectionUser ::find_by_collection_and_user ( & collection . uuid , & member . user_uuid , & mut conn ) . await {
2018-05-30 16:01:56 +01:00
None = > err! ( " User not assigned to collection " ) ,
2022-05-20 23:39:47 +02:00
Some ( col_user ) = > col_user . delete ( & mut conn ) . await ,
2018-05-29 16:01:38 +01:00
}
}
}
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/collections/<col_id>/delete-user/<member_id> " ) ]
async fn post_organization_collection_delete_member (
org_id : OrganizationId ,
col_id : CollectionId ,
member_id : MembershipId ,
2018-12-30 23:34:31 +01:00
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
delete_organization_collection_member ( org_id , col_id , member_id , headers , conn ) . await
2018-05-16 23:05:50 +01:00
}
2023-03-18 20:52:55 +01:00
async fn _delete_organization_collection (
2025-01-09 18:37:23 +01:00
org_id : & OrganizationId ,
col_id : & CollectionId ,
2023-03-18 20:52:55 +01:00
headers : & ManagerHeaders ,
conn : & mut DbConn ,
2021-03-31 21:18:35 +01:00
) -> EmptyResult {
2024-12-14 00:55:34 +01:00
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 " )
} ;
log_event (
EventType ::CollectionDeleted as i32 ,
& collection . uuid ,
org_id ,
& headers . user . uuid ,
headers . device . atype ,
& headers . ip . ip ,
conn ,
)
. await ;
collection . delete ( conn ) . await
2018-05-16 23:05:50 +01:00
}
2023-03-18 20:52:55 +01:00
#[ delete( " /organizations/<org_id>/collections/<col_id> " ) ]
async fn delete_organization_collection (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
2023-03-18 20:52:55 +01:00
headers : ManagerHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_delete_organization_collection ( & org_id , & col_id , & headers , & mut conn ) . await
2023-03-18 20:52:55 +01:00
}
2018-08-13 16:45:30 +01:00
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-08-13 16:45:30 +01:00
struct DeleteCollectionData {
2024-06-23 21:31:02 +02:00
#[ allow(dead_code) ]
id : String ,
#[ allow(dead_code) ]
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2018-08-13 16:45:30 +01:00
}
2024-12-14 00:55:34 +01:00
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " ) ]
2021-11-16 17:07:55 +01:00
async fn post_organization_collection_delete (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
2020-12-02 22:50:51 +01:00
headers : ManagerHeaders ,
2023-03-18 20:52:55 +01:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_delete_organization_collection ( & org_id , & col_id , & headers , & mut conn ) . await
2023-03-18 20:52:55 +01:00
}
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-03-18 20:52:55 +01:00
struct BulkCollectionIds {
2025-01-09 18:37:23 +01:00
ids : Vec < CollectionId > ,
2023-03-18 20:52:55 +01:00
}
#[ delete( " /organizations/<org_id>/collections " , data = " <data> " ) ]
async fn bulk_delete_organization_collections (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2023-03-18 20:52:55 +01:00
headers : ManagerHeadersLoose ,
2024-06-23 21:31:02 +02:00
data : Json < BulkCollectionIds > ,
2023-03-18 20:52:55 +01:00
mut conn : DbConn ,
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : BulkCollectionIds = data . into_inner ( ) ;
2023-03-18 20:52:55 +01:00
2024-06-23 21:31:02 +02:00
let collections = data . ids ;
2023-03-18 20:52:55 +01:00
let headers = ManagerHeaders ::from_loose ( headers , & collections , & mut conn ) . await ? ;
for col_id in collections {
2025-01-09 18:37:23 +01:00
_delete_organization_collection ( & org_id , & col_id , & headers , & mut conn ) . await ?
2023-03-18 20:52:55 +01:00
}
Ok ( ( ) )
2018-08-13 16:45:30 +01:00
}
2025-01-09 18:37:23 +01:00
#[ get( " /organizations/<org_id>/collections/<col_id>/details " ) ]
2021-11-16 17:07:55 +01:00
async fn get_org_collection_detail (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
2021-11-16 17:07:55 +01:00
headers : ManagerHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2021-11-16 17:07:55 +01:00
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
match Collection ::find_by_uuid_and_user ( & col_id , headers . user . uuid . clone ( ) , & mut conn ) . await {
2018-04-20 17:35:11 +01:00
None = > err! ( " Collection not found " ) ,
2018-05-30 22:30:45 +02:00
Some ( collection ) = > {
if collection . org_uuid ! = org_id {
err! ( " Collection is not owned by organization " )
}
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User is not part of organization " )
2023-02-27 16:37:58 +01:00
} ;
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
CollectionGroup ::find_by_collection ( & collection . uuid , & mut conn )
. await
. iter ( )
2025-01-09 18:37:23 +01:00
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
2023-02-27 16:37:58 +01:00
. collect ( )
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups.
Vec ::with_capacity ( 0 )
} ;
2025-01-09 18:37:23 +01:00
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
2025-01-04 19:31:59 +01:00
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
2025-01-09 18:37:23 +01:00
let membership_type : HashMap < MembershipId , i32 > = Membership ::find_confirmed_by_org ( & org_id , & mut conn )
2025-01-04 19:31:59 +01:00
. await
. into_iter ( )
2025-01-09 18:37:23 +01:00
. map ( | m | ( m . uuid , m . atype ) )
2025-01-04 19:31:59 +01:00
. collect ( ) ;
2025-01-29 20:41:31 +01:00
let users : Vec < Value > = CollectionUser ::find_by_org_and_coll_swap_user_uuid_with_member_uuid (
& org_id ,
& collection . uuid ,
& mut conn ,
)
. await
. iter ( )
. map ( | collection_member | {
collection_member . to_json_details_for_member (
* membership_type . get ( & collection_member . membership_uuid ) . unwrap_or ( & ( MembershipType ::User as i32 ) ) ,
)
} )
. collect ( ) ;
2023-02-27 16:37:58 +01:00
2025-01-09 18:37:23 +01:00
let assigned = Collection ::can_access_collection ( & member , & collection . uuid , & mut conn ) . await ;
2022-10-20 15:31:53 +02:00
2024-10-11 18:42:40 +02:00
let mut json_object = collection . to_json_details ( & headers . user . uuid , None , & mut conn ) . await ;
2024-06-23 21:31:02 +02:00
json_object [ " assigned " ] = json! ( assigned ) ;
json_object [ " users " ] = json! ( users ) ;
json_object [ " groups " ] = json! ( groups ) ;
json_object [ " object " ] = json! ( " collectionAccessDetails " ) ;
2022-10-20 15:31:53 +02:00
Ok ( Json ( json_object ) )
2018-05-30 22:30:45 +02:00
}
2018-04-20 17:35:11 +01:00
}
}
2025-01-09 18:37:23 +01:00
#[ get( " /organizations/<org_id>/collections/<col_id>/users " ) ]
async fn get_collection_users (
org_id : OrganizationId ,
col_id : CollectionId ,
2025-01-25 01:32:09 +01:00
headers : ManagerHeaders ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2018-04-25 00:34:40 +02:00
// Get org and collection, check that collection is from org
2025-01-09 18:37:23 +01:00
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Collection not found in Organization " )
2018-05-29 16:01:38 +01:00
} ;
2018-04-25 00:34:40 +02:00
2025-01-09 18:37:23 +01:00
let mut member_list = Vec ::new ( ) ;
2022-05-20 23:39:47 +02:00
for col_user in CollectionUser ::find_by_collection ( & collection . uuid , & mut conn ) . await {
2025-01-09 18:37:23 +01:00
member_list . push (
Membership ::find_by_user_and_org ( & col_user . user_uuid , & org_id , & mut conn )
2021-11-16 17:07:55 +01:00
. await
2018-12-30 23:34:31 +01:00
. unwrap ( )
2022-05-20 23:39:47 +02:00
. to_json_user_access_restrictions ( & col_user ) ,
) ;
}
2018-04-25 00:34:40 +02:00
2025-01-09 18:37:23 +01:00
Ok ( Json ( json! ( member_list ) ) )
2018-04-25 00:34:40 +02:00
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/collections/<col_id>/users " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn put_collection_users (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
col_id : CollectionId ,
data : Json < Vec < MembershipData > > ,
2025-01-25 01:32:09 +01:00
headers : ManagerHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2019-01-25 17:43:51 +01:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2019-01-25 17:43:51 +01:00
// Get org and collection, check that collection is from org
2025-01-09 18:37:23 +01:00
if Collection ::find_by_uuid_and_org ( & col_id , & org_id , & mut conn ) . await . is_none ( ) {
2019-01-25 17:43:51 +01:00
err! ( " Collection not found in Organization " )
}
// Delete all the user-collections
2025-01-09 18:37:23 +01:00
CollectionUser ::delete_all_by_collection ( & col_id , & mut conn ) . await ? ;
2019-01-25 17:43:51 +01:00
// And then add all the received ones (except if the user has access_all)
2024-06-23 21:31:02 +02:00
for d in data . iter ( ) {
2025-01-09 18:37:23 +01:00
let Some ( user ) = Membership ::find_by_uuid_and_org ( & d . id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User is not part of organization " )
2019-01-25 17:43:51 +01:00
} ;
if user . access_all {
continue ;
}
2025-01-21 23:33:41 +01:00
CollectionUser ::save ( & user . user_uuid , & col_id , d . read_only , d . hide_passwords , d . manage , & mut conn ) . await ? ;
2019-01-25 17:43:51 +01:00
}
Ok ( ( ) )
}
2018-04-24 22:01:55 +02:00
#[ derive(FromForm) ]
struct OrgIdData {
2021-11-07 18:53:39 +01:00
#[ field(name = " organizationId " ) ]
2025-01-09 18:37:23 +01:00
organization_id : OrganizationId ,
2018-04-24 22:01:55 +02:00
}
2018-10-10 20:40:39 +02:00
#[ get( " /ciphers/organization-details?<data..> " ) ]
2025-01-25 01:32:09 +01:00
async fn get_org_details ( data : OrgIdData , headers : OrgMemberHeaders , mut conn : DbConn ) -> JsonResult {
if data . organization_id ! = headers . org_id {
err_code! ( " Resource not found. " , " Organization id's do not match " , rocket ::http ::Status ::NotFound . code ) ;
2024-08-11 19:39:56 +02:00
}
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : _get_org_details ( & data . organization_id , & headers . host , & headers . user . uuid , & mut conn ) . await ,
" object " : " list " ,
" continuationToken " : null ,
2024-08-11 19:39:56 +02:00
} ) ) )
2022-09-24 18:27:13 +02:00
}
2025-01-09 18:37:23 +01:00
async fn _get_org_details ( org_id : & OrganizationId , host : & str , user_id : & UserId , conn : & mut DbConn ) -> Value {
2022-09-24 18:27:13 +02:00
let ciphers = Cipher ::find_by_org ( org_id , conn ) . await ;
2025-01-09 18:37:23 +01:00
let cipher_sync_data = CipherSyncData ::new ( user_id , CipherSyncType ::Organization , conn ) . await ;
2022-05-04 21:13:05 +02:00
2023-01-11 20:23:53 +01:00
let mut ciphers_json = Vec ::with_capacity ( ciphers . len ( ) ) ;
2022-05-20 23:39:47 +02:00
for c in ciphers {
2025-01-09 18:37:23 +01:00
ciphers_json . push ( c . to_json ( host , user_id , Some ( & cipher_sync_data ) , CipherSyncType ::Organization , conn ) . await ) ;
2022-05-20 23:39:47 +02:00
}
2022-11-07 17:13:34 +01:00
json! ( ciphers_json )
2018-04-24 22:01:55 +02:00
}
2023-02-27 16:37:58 +01:00
#[ derive(FromForm) ]
struct GetOrgUserData {
#[ field(name = " includeCollections " ) ]
include_collections : Option < bool > ,
#[ field(name = " includeGroups " ) ]
include_groups : Option < bool > ,
}
#[ get( " /organizations/<org_id>/users?<data..> " ) ]
2025-01-09 18:37:23 +01:00
async fn get_members (
2023-02-27 16:37:58 +01:00
data : GetOrgUserData ,
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2025-01-25 01:32:09 +01:00
headers : ManagerHeadersLoose ,
2023-02-27 16:37:58 +01:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-05-20 23:39:47 +02:00
let mut users_json = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
for u in Membership ::find_by_org ( & org_id , & mut conn ) . await {
2023-02-27 16:37:58 +01:00
users_json . push (
u . to_json_user_details (
data . include_collections . unwrap_or ( false ) ,
data . include_groups . unwrap_or ( false ) ,
& mut conn ,
)
. await ,
) ;
2022-05-20 23:39:47 +02:00
}
2018-04-24 22:01:55 +02:00
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : users_json ,
" object " : " list " ,
" continuationToken " : null ,
2025-01-25 01:32:09 +01:00
} ) ) )
2018-04-24 22:01:55 +02:00
}
2018-02-17 22:30:19 +01:00
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
#[ post( " /organizations/<org_id>/keys " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn post_org_keys (
org_id : OrganizationId ,
data : Json < OrgKeyData > ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data : OrgKeyData = data . into_inner ( ) ;
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
2025-01-09 18:37:23 +01:00
let mut org = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
Some ( organization ) = > {
if organization . private_key . is_some ( ) & & organization . public_key . is_some ( ) {
err! ( " Organization Keys already exist " )
}
organization
}
None = > err! ( " Can't find organization details " ) ,
} ;
2024-06-23 21:31:02 +02:00
org . private_key = Some ( data . encrypted_private_key ) ;
org . public_key = Some ( data . public_key ) ;
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
2022-05-20 23:39:47 +02:00
org . save ( & mut conn ) . await ? ;
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" object " : " organizationKeys " ,
" publicKey " : org . public_key ,
" privateKey " : org . private_key ,
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
} ) ) )
}
2018-04-24 22:01:55 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-25 00:34:40 +02:00
struct CollectionData {
2025-01-09 18:37:23 +01:00
id : CollectionId ,
read_only : bool ,
hide_passwords : bool ,
2025-01-21 23:33:41 +01:00
manage : bool ,
2025-01-09 18:37:23 +01:00
}
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct MembershipData {
id : MembershipId ,
2024-06-23 21:31:02 +02:00
read_only : bool ,
hide_passwords : bool ,
2025-01-21 23:33:41 +01:00
manage : bool ,
2018-04-24 22:01:55 +02:00
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-24 22:01:55 +02:00
struct InviteData {
2024-06-23 21:31:02 +02:00
emails : Vec < String > ,
2025-01-09 18:37:23 +01:00
groups : Vec < GroupId > ,
2024-06-23 21:31:02 +02:00
r#type : NumberOrString ,
collections : Option < Vec < CollectionData > > ,
2024-08-01 19:45:42 +02:00
#[ serde(default) ]
access_all : bool ,
2025-01-04 19:31:59 +01:00
#[ serde(default) ]
permissions : HashMap < String , Value > ,
2018-04-24 22:01:55 +02:00
}
#[ post( " /organizations/<org_id>/users/invite " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn send_invite (
org_id : OrganizationId ,
data : Json < InviteData > ,
headers : AdminHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-04 19:31:59 +01:00
let mut data : InviteData = data . into_inner ( ) ;
2018-04-24 22:01:55 +02:00
2025-01-04 19:31:59 +01:00
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type
let raw_type = & data . r#type . into_string ( ) ;
2025-01-09 18:37:23 +01:00
// Membership::from_str will convert custom (4) to manager (3)
let new_type = match MembershipType ::from_str ( raw_type ) {
2018-04-25 00:34:40 +02:00
Some ( new_type ) = > new_type as i32 ,
2018-12-30 23:34:31 +01:00
None = > err! ( " Invalid type " ) ,
2018-04-25 00:34:40 +02:00
} ;
2025-01-09 18:37:23 +01:00
if new_type ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
2018-11-12 17:13:25 +00:00
err! ( " Only Owners can invite Managers, Admins or Owners " )
2018-04-24 23:04:10 +02:00
}
2018-04-24 22:01:55 +02:00
2025-01-04 19:31:59 +01:00
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type . eq ( " 4 " )
& & data . permissions . get ( " editAnyCollection " ) = = Some ( & json! ( true ) )
& & data . permissions . get ( " deleteAnyCollection " ) = = Some ( & json! ( true ) )
& & data . permissions . get ( " createNewCollections " ) = = Some ( & json! ( true ) )
{
data . access_all = true ;
}
2025-01-08 18:13:45 +01:00
let mut user_created : bool = false ;
2024-06-23 21:31:02 +02:00
for email in data . emails . iter ( ) {
2025-01-09 18:37:23 +01:00
let mut member_status = MembershipStatus ::Invited as i32 ;
2024-10-18 20:34:11 +02:00
let user = match User ::find_by_mail ( email , & mut conn ) . await {
2018-12-30 23:34:31 +01:00
None = > {
2019-01-25 18:23:51 +01:00
if ! CONFIG . invitations_allowed ( ) {
2022-12-29 14:11:52 +01:00
err! ( format! ( " User does not exist: {email} " ) )
2020-04-09 01:51:05 -07:00
}
2024-10-18 20:34:11 +02:00
if ! CONFIG . is_email_domain_allowed ( email ) {
2020-04-09 01:51:05 -07:00
err! ( " Email domain not eligible for invitations " )
2019-01-08 15:11:16 +01:00
}
2019-02-02 01:09:21 +01:00
if ! CONFIG . mail_enabled ( ) {
2025-01-08 18:13:45 +01:00
Invitation ::new ( email ) . save ( & mut conn ) . await ? ;
2018-12-30 23:34:31 +01:00
}
2019-01-08 15:11:16 +01:00
2025-01-08 18:13:45 +01:00
let mut new_user = User ::new ( email . clone ( ) ) ;
new_user . save ( & mut conn ) . await ? ;
user_created = true ;
new_user
2018-12-30 23:34:31 +01:00
}
Some ( user ) = > {
2025-01-09 18:37:23 +01:00
if Membership ::find_by_user_and_org ( & user . uuid , & org_id , & mut conn ) . await . is_some ( ) {
2022-12-29 14:11:52 +01:00
err! ( format! ( " User already in organization: {email} " ) )
2018-12-30 23:34:31 +01:00
} else {
2022-09-27 23:19:35 +02:00
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
2025-01-09 18:37:23 +01:00
member_status = MembershipStatus ::Accepted as i32 ;
2022-09-27 23:19:35 +02:00
}
2018-12-30 23:34:31 +01:00
user
}
2018-09-10 14:51:40 +01:00
}
} ;
2025-01-09 18:37:23 +01:00
let mut new_member = Membership ::new ( user . uuid . clone ( ) , org_id . clone ( ) ) ;
2024-08-01 19:45:42 +02:00
let access_all = data . access_all ;
2025-01-08 18:13:45 +01:00
new_member . access_all = access_all ;
new_member . atype = new_type ;
2025-01-09 18:37:23 +01:00
new_member . status = member_status ;
2025-01-08 18:13:45 +01:00
new_member . save ( & mut conn ) . await ? ;
if CONFIG . mail_enabled ( ) {
2025-01-09 18:37:23 +01:00
let org_name = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2025-01-08 18:13:45 +01:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization " ) ,
} ;
if let Err ( e ) = mail ::send_invite (
& user ,
2025-01-20 20:21:44 +01:00
org_id . clone ( ) ,
new_member . uuid . clone ( ) ,
2025-01-08 18:13:45 +01:00
& org_name ,
Some ( headers . user . email . clone ( ) ) ,
)
. await
{
// Upon error delete the user, invite and org member records when needed
if user_created {
user . delete ( & mut conn ) . await ? ;
} else {
new_member . delete ( & mut conn ) . await ? ;
}
err! ( format! ( " Error sending invite: {e:?} " ) ) ;
2025-01-09 18:37:23 +01:00
}
2025-01-08 18:13:45 +01:00
}
log_event (
EventType ::OrganizationUserInvited as i32 ,
2025-01-09 18:37:23 +01:00
& new_member . uuid ,
& org_id ,
2025-01-08 18:13:45 +01:00
& headers . user . uuid ,
headers . device . atype ,
& headers . ip . ip ,
& mut conn ,
)
. await ;
2018-12-19 22:51:08 +01:00
// If no accessAll, add the collections received
if ! access_all {
2024-06-23 21:31:02 +02:00
for col in data . collections . iter ( ) . flatten ( ) {
2025-01-09 18:37:23 +01:00
match Collection ::find_by_uuid_and_org ( & col . id , & org_id , & mut conn ) . await {
2018-12-19 22:51:08 +01:00
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2024-06-23 21:31:02 +02:00
CollectionUser ::save (
& user . uuid ,
& collection . uuid ,
col . read_only ,
col . hide_passwords ,
2025-01-21 23:33:41 +01:00
col . manage ,
2024-06-23 21:31:02 +02:00
& mut conn ,
)
. await ? ;
2018-05-04 19:25:50 +02:00
}
2018-04-24 22:01:55 +02:00
}
}
2018-10-12 15:20:10 +01:00
}
2018-12-14 21:56:00 -05:00
2025-01-09 18:37:23 +01:00
for group_id in data . groups . iter ( ) {
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , new_member . uuid . clone ( ) ) ;
2023-06-22 11:10:40 +02:00
group_entry . save ( & mut conn ) . await ? ;
}
2018-12-14 21:56:00 -05:00
}
Ok ( ( ) )
}
2021-09-18 14:22:14 +02:00
#[ post( " /organizations/<org_id>/users/reinvite " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_reinvite_members (
org_id : OrganizationId ,
data : Json < BulkMembershipIds > ,
2021-09-18 14:22:14 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let data : BulkMembershipIds = data . into_inner ( ) ;
2021-09-18 14:22:14 +02:00
let mut bulk_response = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
for member_id in data . ids {
let err_msg = match _reinvite_member ( & org_id , & member_id , & headers . user . email , & mut conn ) . await {
2022-11-04 12:56:02 +01:00
Ok ( _ ) = > String ::new ( ) ,
2022-12-29 14:11:52 +01:00
Err ( e ) = > format! ( " {e:?} " ) ,
2021-09-18 14:22:14 +02:00
} ;
bulk_response . push ( json! (
{
2024-06-23 21:31:02 +02:00
" object " : " OrganizationBulkConfirmResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-23 21:31:02 +02:00
" error " : err_msg
2021-09-18 14:22:14 +02:00
}
) )
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2021-09-18 14:22:14 +02:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id>/reinvite " ) ]
async fn reinvite_member (
org_id : OrganizationId ,
member_id : MembershipId ,
headers : AdminHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
_reinvite_member ( & org_id , & member_id , & headers . user . email , & mut conn ) . await
2021-09-18 14:22:14 +02:00
}
2025-01-09 18:37:23 +01:00
async fn _reinvite_member (
org_id : & OrganizationId ,
member_id : & MembershipId ,
invited_by_email : & str ,
conn : & mut DbConn ,
) -> EmptyResult {
let Some ( member ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " The user hasn't been invited to the organization. " )
2018-12-30 00:19:01 -05:00
} ;
2025-01-09 18:37:23 +01:00
if member . status ! = MembershipStatus ::Invited as i32 {
2019-01-07 15:29:57 +01:00
err! ( " The user is already accepted or confirmed to the organization " )
}
2025-01-09 18:37:23 +01:00
let Some ( user ) = User ::find_by_uuid ( & member . user_uuid , conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User not found. " )
2018-12-29 23:24:38 -05:00
} ;
2019-01-08 14:05:05 +01:00
2024-07-24 00:32:46 +02:00
if ! CONFIG . invitations_allowed ( ) & & user . password_hash . is_empty ( ) {
err! ( " Invitations are not allowed. " )
}
2021-11-16 17:07:55 +01:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
2018-12-29 23:24:38 -05:00
Some ( org ) = > org . name ,
2018-12-30 23:34:31 +01:00
None = > err! ( " Error looking up organization. " ) ,
2018-12-29 23:24:38 -05:00
} ;
2019-02-02 01:09:21 +01:00
if CONFIG . mail_enabled ( ) {
2025-01-20 20:21:44 +01:00
mail ::send_invite ( & user , org_id . clone ( ) , member . uuid , & org_name , Some ( invited_by_email . to_string ( ) ) ) . await ? ;
2024-07-24 00:32:46 +02:00
} else if user . password_hash . is_empty ( ) {
2022-11-26 19:07:28 +01:00
let invitation = Invitation ::new ( & user . email ) ;
2021-11-16 17:07:55 +01:00
invitation . save ( conn ) . await ? ;
2024-07-24 00:32:46 +02:00
} else {
2025-01-08 18:13:45 +01:00
Invitation ::take ( & user . email , conn ) . await ;
2025-01-09 18:37:23 +01:00
let mut member = member ;
member . status = MembershipStatus ::Accepted as i32 ;
member . save ( conn ) . await ? ;
2018-12-29 23:24:38 -05:00
}
2018-12-30 23:34:31 +01:00
2018-12-29 23:24:38 -05:00
Ok ( ( ) )
}
2018-12-18 23:16:03 -05:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-12-18 23:16:03 -05:00
struct AcceptData {
2024-06-23 21:31:02 +02:00
token : String ,
reset_password_key : Option < String > ,
2018-12-18 23:16:03 -05:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id>/accept " , data = " <data> " ) ]
async fn accept_invite (
org_id : OrganizationId ,
member_id : MembershipId ,
data : Json < AcceptData > ,
2025-01-20 20:21:44 +01:00
headers : Headers ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> EmptyResult {
// The web-vault passes org_id and member_id in the URL, but we are just reading them from the JWT instead
2024-06-23 21:31:02 +02:00
let data : AcceptData = data . into_inner ( ) ;
let claims = decode_invite ( & data . token ) ? ;
2018-12-14 21:56:00 -05:00
2025-01-20 20:21:44 +01:00
// Don't allow other users from accepting an invitation.
if ! claims . email . eq ( & headers . user . email ) {
err! ( " Invitation was issued to a different account " , " Claim does not match user_id " )
}
2025-01-09 18:37:23 +01:00
// If a claim does not have a member_id or it does not match the one in from the URI, something is wrong.
2025-01-20 20:21:44 +01:00
if ! claims . member_id . eq ( & member_id ) {
err! ( " Error accepting the invitation " , " Claim does not match the member_id " )
2024-12-14 00:55:34 +01:00
}
2025-01-20 20:21:44 +01:00
let member = & claims . member_id ;
let org = & claims . org_id ;
2019-01-08 15:11:16 +01:00
2025-01-20 20:21:44 +01:00
Invitation ::take ( & claims . email , & mut conn ) . await ;
2019-01-08 15:11:16 +01:00
2025-01-20 20:21:44 +01:00
// skip invitation logic when we were invited via the /admin panel
if * * member ! = FAKE_ADMIN_UUID {
let Some ( mut member ) = Membership ::find_by_uuid_and_org ( member , org , & mut conn ) . await else {
err! ( " Error accepting the invitation " )
} ;
2019-01-08 15:11:16 +01:00
2025-01-20 20:21:44 +01:00
if member . status ! = MembershipStatus ::Invited as i32 {
err! ( " User already accepted the invitation " )
}
let master_password_required = OrgPolicy ::org_is_reset_password_auto_enroll ( org , & mut conn ) . await ;
if data . reset_password_key . is_none ( ) & & master_password_required {
err! ( " Reset password key is required, but not provided. " ) ;
}
2023-01-25 08:06:21 +01:00
2025-01-20 20:21:44 +01:00
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
// It returns different error messages per function.
if member . atype < MembershipType ::Admin {
match OrgPolicy ::is_user_allowed ( & member . user_uuid , & org_id , false , & mut conn ) . await {
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
if CONFIG . email_2fa_auto_fallback ( ) {
two_factor ::email ::activate_email_2fa ( & headers . user , & mut conn ) . await ? ;
} else {
err! ( " You cannot join this organization until you enable two-step login on your user account " ) ;
2021-09-24 17:55:49 +02:00
}
}
2025-01-20 20:21:44 +01:00
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
err! ( " You cannot join this organization because you are a member of an organization which forbids it " ) ;
2023-01-25 08:06:21 +01:00
}
2018-12-14 21:56:00 -05:00
}
2018-12-30 23:34:31 +01:00
}
2025-01-20 20:21:44 +01:00
member . status = MembershipStatus ::Accepted as i32 ;
if master_password_required {
member . reset_password_key = data . reset_password_key ;
}
member . save ( & mut conn ) . await ? ;
2018-04-24 22:01:55 +02:00
}
2019-02-02 01:09:21 +01:00
if CONFIG . mail_enabled ( ) {
2025-01-20 20:21:44 +01:00
if let Some ( invited_by_email ) = & claims . invited_by_email {
let org_name = match Organization ::find_by_uuid ( & claims . org_id , & mut conn ) . await {
2019-01-04 10:32:51 -05:00
Some ( org ) = > org . name ,
2019-01-08 15:11:16 +01:00
None = > err! ( " Organization not found. " ) ,
2019-01-05 23:03:49 -05:00
} ;
2019-01-04 10:32:51 -05:00
// User was invited to an organization, so they must be confirmed manually after acceptance
2022-07-06 23:57:37 +02:00
mail ::send_invite_accepted ( & claims . email , invited_by_email , & org_name ) . await ? ;
2019-01-04 10:32:51 -05:00
} else {
// User was invited from /admin, so they are automatically confirmed
2025-01-20 20:21:44 +01:00
let org_name = CONFIG . invitation_org_name ( ) ;
2022-07-06 23:57:37 +02:00
mail ::send_invite_confirmed ( & claims . email , & org_name ) . await ? ;
2019-01-04 10:32:51 -05:00
}
2019-01-02 22:20:39 -05:00
}
2018-04-24 22:01:55 +02:00
Ok ( ( ) )
}
2024-06-23 21:31:02 +02:00
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct ConfirmData {
2025-01-09 18:37:23 +01:00
id : Option < MembershipId > ,
2024-06-23 21:31:02 +02:00
key : Option < String > ,
}
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct BulkConfirmData {
keys : Option < Vec < ConfirmData > > ,
}
2021-09-18 14:22:14 +02:00
#[ post( " /organizations/<org_id>/users/confirm " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn bulk_confirm_invite (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2024-06-23 21:31:02 +02:00
data : Json < BulkConfirmData > ,
2021-11-16 17:07:55 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-12-30 21:23:55 +01:00
nt : Notify < '_ > ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
2021-09-18 14:22:14 +02:00
let mut bulk_response = Vec ::new ( ) ;
2024-06-23 21:31:02 +02:00
match data . keys {
2021-09-18 14:22:14 +02:00
Some ( keys ) = > {
for invite in keys {
2025-01-09 18:37:23 +01:00
let member_id = invite . id . unwrap ( ) ;
2024-06-23 21:31:02 +02:00
let user_key = invite . key . unwrap_or_default ( ) ;
2025-01-09 18:37:23 +01:00
let err_msg = match _confirm_invite ( & org_id , & member_id , & user_key , & headers , & mut conn , & nt ) . await {
2022-11-04 12:56:02 +01:00
Ok ( _ ) = > String ::new ( ) ,
2022-12-29 14:11:52 +01:00
Err ( e ) = > format! ( " {e:?} " ) ,
2021-09-18 14:22:14 +02:00
} ;
bulk_response . push ( json! (
{
2024-06-23 21:31:02 +02:00
" object " : " OrganizationBulkConfirmResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-23 21:31:02 +02:00
" error " : err_msg
2021-09-18 14:22:14 +02:00
}
) ) ;
}
}
None = > error! ( " No keys to confirm " ) ,
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2021-09-18 14:22:14 +02:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id>/confirm " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn confirm_invite (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2024-06-23 21:31:02 +02:00
data : Json < ConfirmData > ,
2018-12-30 23:34:31 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-12-30 21:23:55 +01:00
nt : Notify < '_ > ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
let user_key = data . key . unwrap_or_default ( ) ;
2025-01-09 18:37:23 +01:00
_confirm_invite ( & org_id , & member_id , & user_key , & headers , & mut conn , & nt ) . await
2021-09-18 14:22:14 +02:00
}
2018-06-01 00:18:50 +02:00
2021-11-16 17:07:55 +01:00
async fn _confirm_invite (
2025-01-09 18:37:23 +01:00
org_id : & OrganizationId ,
member_id : & MembershipId ,
2021-11-16 17:07:55 +01:00
key : & str ,
headers : & AdminHeaders ,
2022-05-20 23:39:47 +02:00
conn : & mut DbConn ,
2022-12-30 21:23:55 +01:00
nt : & Notify < '_ > ,
2021-11-16 17:07:55 +01:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
if key . is_empty ( ) | | member_id . is_empty ( ) {
2021-09-18 14:22:14 +02:00
err! ( " Key or UserId is not set, unable to process request " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( mut member_to_confirm ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " The specified user isn't a member of the organization " )
2018-04-25 00:34:40 +02:00
} ;
2025-01-09 18:37:23 +01:00
if member_to_confirm . atype ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
2018-11-12 17:13:25 +00:00
err! ( " Only Owners can confirm Managers, Admins or Owners " )
2018-04-24 23:04:10 +02:00
}
2018-04-24 22:01:55 +02:00
2025-01-09 18:37:23 +01:00
if member_to_confirm . status ! = MembershipStatus ::Accepted as i32 {
2018-04-24 22:01:55 +02:00
err! ( " User in invalid state " )
}
2025-01-09 18:37:23 +01:00
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
2022-08-20 16:42:36 +02:00
// It returns different error messages per function.
2025-01-09 18:37:23 +01:00
if member_to_confirm . atype < MembershipType ::Admin {
match OrgPolicy ::is_user_allowed ( & member_to_confirm . user_uuid , org_id , true , conn ) . await {
2022-08-20 16:42:36 +02:00
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
2024-03-17 22:35:02 +01:00
if CONFIG . email_2fa_auto_fallback ( ) {
2025-01-09 18:37:23 +01:00
two_factor ::email ::find_and_activate_email_2fa ( & member_to_confirm . user_uuid , conn ) . await ? ;
2024-03-17 22:35:02 +01:00
} else {
err! ( " You cannot confirm this user because they have not setup 2FA " ) ;
}
2022-08-20 16:42:36 +02:00
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
2024-03-17 22:35:02 +01:00
err! ( " You cannot confirm this user because they are a member of an organization which forbids it " ) ;
2022-08-20 16:42:36 +02:00
}
}
}
2025-01-09 18:37:23 +01:00
member_to_confirm . status = MembershipStatus ::Confirmed as i32 ;
member_to_confirm . akey = key . to_string ( ) ;
2018-04-24 22:01:55 +02:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserConfirmed as i32 ,
2025-01-09 18:37:23 +01:00
& member_to_confirm . uuid ,
2023-04-30 17:18:12 +02:00
org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
conn ,
)
. await ;
2019-02-02 01:09:21 +01:00
if CONFIG . mail_enabled ( ) {
2021-11-16 17:07:55 +01:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
2019-01-02 22:20:39 -05:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization. " ) ,
} ;
2025-01-09 18:37:23 +01:00
let address = match User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
2019-01-02 22:20:39 -05:00
Some ( user ) = > user . email ,
None = > err! ( " Error looking up user. " ) ,
} ;
2022-07-06 23:57:37 +02:00
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
2019-01-02 22:20:39 -05:00
}
2025-01-09 18:37:23 +01:00
let save_result = member_to_confirm . save ( conn ) . await ;
2022-12-30 21:23:55 +01:00
2025-01-09 18:37:23 +01:00
if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
2022-12-30 21:23:55 +01:00
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user ) . await ;
}
save_result
2018-04-25 00:34:40 +02:00
}
2025-01-04 19:31:59 +01:00
#[ get( " /organizations/<org_id>/users/mini-details " , rank = 1) ]
2025-01-09 18:37:23 +01:00
async fn get_org_user_mini_details (
org_id : OrganizationId ,
2025-01-25 01:32:09 +01:00
headers : ManagerHeadersLoose ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let mut members_json = Vec ::new ( ) ;
for m in Membership ::find_by_org ( & org_id , & mut conn ) . await {
members_json . push ( m . to_json_mini_details ( & mut conn ) . await ) ;
2025-01-04 19:31:59 +01:00
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2025-01-09 18:37:23 +01:00
" data " : members_json ,
2025-01-04 19:31:59 +01:00
" object " : " list " ,
" continuationToken " : null ,
2025-01-25 01:32:09 +01:00
} ) ) )
2025-01-04 19:31:59 +01:00
}
2025-01-09 18:37:23 +01:00
#[ get( " /organizations/<org_id>/users/<member_id>?<data..> " , rank = 2) ]
2023-03-06 16:53:21 +01:00
async fn get_user (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2023-03-06 16:53:21 +01:00
data : GetOrgUserData ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2023-03-06 16:53:21 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( user ) = Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " The specified user isn't a member of the organization " )
2018-04-25 00:34:40 +02:00
} ;
2023-03-06 16:53:21 +01:00
// In this case, when groups are requested we also need to include collections.
// Else these will not be shown in the interface, and could lead to missing collections when saved.
let include_groups = data . include_groups . unwrap_or ( false ) ;
Ok ( Json (
user . to_json_user_details ( data . include_collections . unwrap_or ( include_groups ) , include_groups , & mut conn ) . await ,
) )
2018-04-25 00:34:40 +02:00
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-04-25 00:34:40 +02:00
struct EditUserData {
2024-06-23 21:31:02 +02:00
r#type : NumberOrString ,
collections : Option < Vec < CollectionData > > ,
2025-01-09 18:37:23 +01:00
groups : Option < Vec < GroupId > > ,
2024-08-01 19:45:42 +02:00
#[ serde(default) ]
2024-06-23 21:31:02 +02:00
access_all : bool ,
2025-01-04 19:31:59 +01:00
#[ serde(default) ]
permissions : HashMap < String , Value > ,
2018-04-25 00:34:40 +02:00
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id> " , data = " <data> " , rank = 1) ]
async fn put_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2024-06-23 21:31:02 +02:00
data : Json < EditUserData > ,
2018-12-30 23:34:31 +01:00
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
edit_member ( org_id , member_id , data , headers , conn ) . await
2018-08-13 16:45:30 +01:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id> " , data = " <data> " , rank = 1) ]
async fn edit_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2024-06-23 21:31:02 +02:00
data : Json < EditUserData > ,
2018-12-30 23:34:31 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-04 19:31:59 +01:00
let mut data : EditUserData = data . into_inner ( ) ;
2018-04-25 00:34:40 +02:00
2025-01-04 19:31:59 +01:00
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type
let raw_type = & data . r#type . into_string ( ) ;
2025-01-09 18:37:23 +01:00
// MembershipTyp::from_str will convert custom (4) to manager (3)
let Some ( new_type ) = MembershipType ::from_str ( raw_type ) else {
2024-12-14 00:55:34 +01:00
err! ( " Invalid type " )
2018-04-25 00:34:40 +02:00
} ;
2025-01-04 19:31:59 +01:00
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type . eq ( " 4 " )
& & data . permissions . get ( " editAnyCollection " ) = = Some ( & json! ( true ) )
& & data . permissions . get ( " deleteAnyCollection " ) = = Some ( & json! ( true ) )
& & data . permissions . get ( " createNewCollections " ) = = Some ( & json! ( true ) )
{
data . access_all = true ;
}
2025-01-09 18:37:23 +01:00
let mut member_to_edit = match Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await {
Some ( member ) = > member ,
2025-01-04 19:31:59 +01:00
None = > err! ( " The specified user isn't member of the organization " ) ,
2018-04-25 00:34:40 +02:00
} ;
2025-01-09 18:37:23 +01:00
if new_type ! = member_to_edit . atype
& & ( member_to_edit . atype > = MembershipType ::Admin | | new_type > = MembershipType ::Admin )
& & headers . membership_type ! = MembershipType ::Owner
2018-12-30 23:34:31 +01:00
{
2018-09-04 11:24:53 +01:00
err! ( " Only Owners can grant and remove Admin or Owner privileges " )
2018-04-25 00:34:40 +02:00
}
2025-01-09 18:37:23 +01:00
if member_to_edit . atype = = MembershipType ::Owner & & headers . membership_type ! = MembershipType ::Owner {
2018-09-04 11:24:53 +01:00
err! ( " Only Owners can edit Owner users " )
2018-04-25 00:34:40 +02:00
}
2025-01-09 18:37:23 +01:00
if member_to_edit . atype = = MembershipType ::Owner
& & new_type ! = MembershipType ::Owner
& & member_to_edit . status = = MembershipStatus ::Confirmed as i32
2022-09-27 10:10:09 +02:00
{
// Removing owner permission, check that there is at least one other confirmed owner
2025-01-09 18:37:23 +01:00
if Membership ::count_confirmed_by_org_and_type ( & org_id , MembershipType ::Owner , & mut conn ) . await < = 1 {
2018-04-25 00:34:40 +02:00
err! ( " Can't delete the last owner " )
}
}
2025-01-09 18:37:23 +01:00
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
2022-08-20 16:42:36 +02:00
// It returns different error messages per function.
2025-01-09 18:37:23 +01:00
if new_type < MembershipType ::Admin {
match OrgPolicy ::is_user_allowed ( & member_to_edit . user_uuid , & org_id , true , & mut conn ) . await {
2022-08-20 16:42:36 +02:00
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
2024-03-17 22:35:02 +01:00
if CONFIG . email_2fa_auto_fallback ( ) {
2025-01-09 18:37:23 +01:00
two_factor ::email ::find_and_activate_email_2fa ( & member_to_edit . user_uuid , & mut conn ) . await ? ;
2024-03-17 22:35:02 +01:00
} else {
err! ( " You cannot modify this user to this type because they have not setup 2FA " ) ;
}
2022-08-20 16:42:36 +02:00
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
2024-03-17 22:35:02 +01:00
err! ( " You cannot modify this user to this type because they are a member of an organization which forbids it " ) ;
2022-08-20 16:42:36 +02:00
}
}
}
2025-01-09 18:37:23 +01:00
member_to_edit . access_all = data . access_all ;
member_to_edit . atype = new_type as i32 ;
2018-04-25 00:34:40 +02:00
2018-05-04 19:25:50 +02:00
// Delete all the odd collections
2025-01-09 18:37:23 +01:00
for c in CollectionUser ::find_by_organization_and_user_uuid ( & org_id , & member_to_edit . user_uuid , & mut conn ) . await {
2022-05-20 23:39:47 +02:00
c . delete ( & mut conn ) . await ? ;
2018-05-04 19:25:50 +02:00
}
// If no accessAll, add the collections received
2024-06-23 21:31:02 +02:00
if ! data . access_all {
for col in data . collections . iter ( ) . flatten ( ) {
2025-01-09 18:37:23 +01:00
match Collection ::find_by_uuid_and_org ( & col . id , & org_id , & mut conn ) . await {
2018-05-28 17:26:02 +01:00
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2021-03-31 21:18:35 +01:00
CollectionUser ::save (
2025-01-09 18:37:23 +01:00
& member_to_edit . user_uuid ,
2021-03-31 21:18:35 +01:00
& collection . uuid ,
2024-06-23 21:31:02 +02:00
col . read_only ,
col . hide_passwords ,
2025-01-21 23:33:41 +01:00
col . manage ,
2022-05-20 23:39:47 +02:00
& mut conn ,
2021-11-16 17:07:55 +01:00
)
. await ? ;
2018-05-28 17:26:02 +01:00
}
}
2018-05-04 19:25:50 +02:00
}
2018-04-25 00:34:40 +02:00
}
2025-01-09 18:37:23 +01:00
GroupUser ::delete_all_by_member ( & member_to_edit . uuid , & mut conn ) . await ? ;
2023-03-06 16:53:21 +01:00
2025-01-09 18:37:23 +01:00
for group_id in data . groups . iter ( ) . flatten ( ) {
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , member_to_edit . uuid . clone ( ) ) ;
2023-03-06 16:53:21 +01:00
group_entry . save ( & mut conn ) . await ? ;
}
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserUpdated as i32 ,
2025-01-09 18:37:23 +01:00
& member_to_edit . uuid ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
member_to_edit . save ( & mut conn ) . await
2018-04-24 22:01:55 +02:00
}
2021-09-18 14:22:14 +02:00
#[ delete( " /organizations/<org_id>/users " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_delete_member (
org_id : OrganizationId ,
data : Json < BulkMembershipIds > ,
2021-11-16 17:07:55 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-12-30 21:23:55 +01:00
nt : Notify < '_ > ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let data : BulkMembershipIds = data . into_inner ( ) ;
2021-09-18 14:22:14 +02:00
let mut bulk_response = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
for member_id in data . ids {
let err_msg = match _delete_member ( & org_id , & member_id , & headers , & mut conn , & nt ) . await {
2022-11-04 12:56:02 +01:00
Ok ( _ ) = > String ::new ( ) ,
2022-12-29 14:11:52 +01:00
Err ( e ) = > format! ( " {e:?} " ) ,
2021-09-18 14:22:14 +02:00
} ;
bulk_response . push ( json! (
{
2024-06-23 21:31:02 +02:00
" object " : " OrganizationBulkConfirmResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-23 21:31:02 +02:00
" error " : err_msg
2021-09-18 14:22:14 +02:00
}
) )
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2021-09-18 14:22:14 +02:00
}
2025-01-09 18:37:23 +01:00
#[ delete( " /organizations/<org_id>/users/<member_id> " ) ]
async fn delete_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
mut conn : DbConn ,
2022-12-30 21:23:55 +01:00
nt : Notify < '_ > ,
2022-11-20 19:15:45 +01:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_delete_member ( & org_id , & member_id , & headers , & mut conn , & nt ) . await
2022-11-20 19:15:45 +01:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id>/delete " ) ]
async fn post_delete_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
mut conn : DbConn ,
2022-12-30 21:23:55 +01:00
nt : Notify < '_ > ,
2022-11-20 19:15:45 +01:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_delete_member ( & org_id , & member_id , & headers , & mut conn , & nt ) . await
2021-09-18 14:22:14 +02:00
}
2025-01-09 18:37:23 +01:00
async fn _delete_member (
org_id : & OrganizationId ,
member_id : & MembershipId ,
2022-11-20 19:15:45 +01:00
headers : & AdminHeaders ,
conn : & mut DbConn ,
2022-12-30 21:23:55 +01:00
nt : & Notify < '_ > ,
2022-11-20 19:15:45 +01:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
let Some ( member_to_delete ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User to delete isn't member of the organization " )
2018-04-25 00:34:40 +02:00
} ;
2025-01-09 18:37:23 +01:00
if member_to_delete . atype ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
2018-04-24 23:04:10 +02:00
err! ( " Only Owners can delete Admins or Owners " )
}
2018-02-17 22:30:19 +01:00
2025-01-09 18:37:23 +01:00
if member_to_delete . atype = = MembershipType ::Owner & & member_to_delete . status = = MembershipStatus ::Confirmed as i32
{
2022-08-20 16:42:36 +02:00
// Removing owner, check that there is at least one other confirmed owner
2025-01-09 18:37:23 +01:00
if Membership ::count_confirmed_by_org_and_type ( org_id , MembershipType ::Owner , conn ) . await < = 1 {
2018-04-25 00:34:40 +02:00
err! ( " Can't delete the last owner " )
}
}
2018-02-17 22:30:19 +01:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
2025-01-09 18:37:23 +01:00
& member_to_delete . uuid ,
2023-04-30 17:18:12 +02:00
org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
conn ,
)
. await ;
2018-08-13 16:45:30 +01:00
2025-01-09 18:37:23 +01:00
if let Some ( user ) = User ::find_by_uuid ( & member_to_delete . user_uuid , conn ) . await {
2022-12-30 21:23:55 +01:00
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user ) . await ;
}
2025-01-09 18:37:23 +01:00
member_to_delete . delete ( conn ) . await
2018-09-13 15:16:24 +02:00
}
2021-09-18 14:22:14 +02:00
#[ post( " /organizations/<org_id>/users/public-keys " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn bulk_public_keys (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
data : Json < BulkMembershipIds > ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let data : BulkMembershipIds = data . into_inner ( ) ;
2021-09-18 14:22:14 +02:00
let mut bulk_response = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
// Check all received Membership UUID's and find the matching User to retrieve the public-key.
// If the user does not exists, just ignore it, and do not return any information regarding that Membership UUID.
2023-10-05 20:08:26 +03:00
// The web-vault will then ignore that user for the following steps.
2025-01-09 18:37:23 +01:00
for member_id in data . ids {
match Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await {
Some ( member ) = > match User ::find_by_uuid ( & member . user_uuid , & mut conn ) . await {
2021-09-18 14:22:14 +02:00
Some ( user ) = > bulk_response . push ( json! (
{
2024-06-23 21:31:02 +02:00
" object " : " organizationUserPublicKeyResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-23 21:31:02 +02:00
" userId " : user . uuid ,
" key " : user . public_key
2021-09-18 14:22:14 +02:00
}
) ) ,
None = > debug! ( " User doesn't exist " ) ,
} ,
2025-01-09 18:37:23 +01:00
None = > debug! ( " Membership doesn't exist " ) ,
2021-09-18 14:22:14 +02:00
}
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2021-09-18 14:22:14 +02:00
}
2018-09-13 15:16:24 +02:00
use super ::ciphers ::update_cipher_from_data ;
2018-12-30 23:34:31 +01:00
use super ::ciphers ::CipherData ;
2018-09-13 15:16:24 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-09-13 15:16:24 +02:00
struct ImportData {
2024-06-23 21:31:02 +02:00
ciphers : Vec < CipherData > ,
collections : Vec < NewCollectionData > ,
collection_relationships : Vec < RelationsData > ,
2018-09-13 15:16:24 +02:00
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2018-09-13 15:16:24 +02:00
struct RelationsData {
// Cipher index
2024-06-23 21:31:02 +02:00
key : usize ,
2018-09-13 15:16:24 +02:00
// Collection index
2024-06-23 21:31:02 +02:00
value : usize ,
2018-09-13 15:16:24 +02:00
}
2018-10-10 20:40:39 +02:00
#[ post( " /ciphers/import-organization?<query..> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn post_org_import (
2021-11-07 18:53:39 +01:00
query : OrgIdData ,
2024-06-23 21:31:02 +02:00
data : Json < ImportData > ,
2020-03-14 13:22:30 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2021-11-16 17:07:55 +01:00
nt : Notify < '_ > ,
2018-12-30 23:34:31 +01:00
) -> EmptyResult {
2024-06-23 21:31:02 +02:00
let data : ImportData = data . into_inner ( ) ;
2021-11-07 18:53:39 +01:00
let org_id = query . organization_id ;
2018-09-13 15:16:24 +02:00
2023-01-01 15:09:10 +01:00
// 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.
2024-09-09 11:36:37 +02:00
Cipher ::validate_cipher_data ( & data . ciphers ) ? ;
2023-01-01 15:09:10 +01:00
2025-01-09 18:37:23 +01:00
let existing_collections : HashSet < Option < CollectionId > > =
Collection ::find_by_organization ( & org_id , & mut conn ) . await . into_iter ( ) . map ( | c | Some ( c . uuid ) ) . collect ( ) ;
let mut collections : Vec < CollectionId > = Vec ::with_capacity ( data . collections . len ( ) ) ;
for col in data . collections {
let collection_uuid = if existing_collections . contains ( & col . id ) {
col . id . unwrap ( )
2022-05-20 23:39:47 +02:00
} else {
2025-01-09 18:37:23 +01:00
let new_collection = Collection ::new ( org_id . clone ( ) , col . name , col . external_id ) ;
2024-11-17 21:33:23 +01:00
new_collection . save ( & mut conn ) . await ? ;
new_collection . uuid
} ;
collections . push ( collection_uuid ) ;
2022-05-20 23:39:47 +02:00
}
2018-09-13 15:16:24 +02:00
// Read the relations between collections and ciphers
2024-11-17 21:33:23 +01:00
// Ciphers can be in multiple collections at the same time
let mut relations = Vec ::with_capacity ( data . collection_relationships . len ( ) ) ;
2024-06-23 21:31:02 +02:00
for relation in data . collection_relationships {
relations . push ( ( relation . key , relation . value ) ) ;
2018-09-13 15:16:24 +02:00
}
2020-03-14 13:22:30 +01:00
let headers : Headers = headers . into ( ) ;
2025-01-09 18:37:23 +01:00
let mut ciphers : Vec < CipherId > = Vec ::with_capacity ( data . ciphers . len ( ) ) ;
2024-11-17 21:33:23 +01:00
for mut cipher_data in data . ciphers {
// Always clear folder_id's via an organization import
cipher_data . folder_id = None ;
2024-06-23 21:31:02 +02:00
let mut cipher = Cipher ::new ( cipher_data . r#type , cipher_data . name . clone ( ) ) ;
2024-04-27 12:53:10 -07:00
update_cipher_from_data ( & mut cipher , cipher_data , & headers , None , & mut conn , & nt , UpdateType ::None ) . await . ok ( ) ;
2024-11-17 21:33:23 +01:00
ciphers . push ( cipher . uuid ) ;
2022-05-20 23:39:47 +02:00
}
2018-09-13 15:16:24 +02:00
// Assign the collections
2025-01-09 18:37:23 +01:00
for ( cipher_index , col_index ) in relations {
2024-11-17 21:33:23 +01:00
let cipher_id = & ciphers [ cipher_index ] ;
2025-01-09 18:37:23 +01:00
let col_id = & collections [ col_index ] ;
CollectionCipher ::save ( cipher_id , col_id , & mut conn ) . await ? ;
2018-09-13 15:16:24 +02:00
}
let mut user = headers . user ;
2022-05-20 23:39:47 +02:00
user . update_revision ( & mut conn ) . await
2018-10-10 20:40:39 +02:00
}
2020-03-14 13:22:30 +01:00
2024-08-07 22:46:03 +02:00
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
#[ allow(dead_code) ]
struct BulkCollectionsData {
2025-01-09 18:37:23 +01:00
organization_id : OrganizationId ,
cipher_ids : Vec < CipherId > ,
collection_ids : HashSet < CollectionId > ,
2024-08-07 22:46:03 +02:00
remove_collections : bool ,
}
2024-11-26 01:35:00 +08:00
// This endpoint is only reachable via the organization view, therefore this endpoint is located here
2024-08-07 22:46:03 +02:00
// Also Bitwarden does not send out Notifications for these changes, it only does this for individual cipher collection updates
#[ post( " /ciphers/bulk-collections " , data = " <data> " ) ]
async fn post_bulk_collections ( data : Json < BulkCollectionsData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
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
// Also filter based upon the provided collections
2025-01-09 18:37:23 +01:00
let user_collections : HashMap < CollectionId , Collection > =
2024-08-07 22:46:03 +02:00
Collection ::find_by_organization_and_user_uuid ( & data . organization_id , & headers . user . uuid , & mut conn )
. await
. into_iter ( )
. filter_map ( | c | {
if data . collection_ids . contains ( & c . uuid ) {
Some ( ( c . uuid . clone ( ) , c ) )
} else {
None
}
} )
. collect ( ) ;
// Verify if all the collections requested exists and are writeable for the user, else abort
for collection_uuid in & data . collection_ids {
match user_collections . get ( collection_uuid ) {
Some ( collection ) if collection . is_writable_by_user ( & headers . user . uuid , & mut conn ) . await = > ( ) ,
_ = > err_code! ( " Resource not found " , " User does not have access to a collection " , 404 ) ,
}
}
for cipher_id in data . cipher_ids . iter ( ) {
// 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
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 {
for collection in & data . collection_ids {
CollectionCipher ::save ( & cipher . uuid , collection , & mut conn ) . await ? ;
}
}
} ;
}
Ok ( ( ) )
}
2020-03-14 13:22:30 +01:00
#[ get( " /organizations/<org_id>/policies " ) ]
2025-01-25 01:32:09 +01:00
async fn list_policies ( org_id : OrganizationId , headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let policies = OrgPolicy ::find_by_org ( & org_id , & mut conn ) . await ;
2020-03-14 13:22:30 +01:00
let policies_json : Vec < Value > = policies . iter ( ) . map ( OrgPolicy ::to_json ) . collect ( ) ;
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : policies_json ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2020-03-14 13:22:30 +01:00
}
2020-03-20 10:51:17 +01:00
#[ get( " /organizations/<org_id>/policies/token?<token> " ) ]
2025-01-09 18:37:23 +01:00
async fn list_policies_token ( org_id : OrganizationId , token : & str , mut conn : DbConn ) -> JsonResult {
2024-09-23 20:25:32 +02:00
let invite = decode_invite ( token ) ? ;
2020-03-20 10:51:17 +01:00
2025-01-20 20:21:44 +01:00
if invite . org_id ! = org_id {
2020-03-20 10:51:17 +01:00
err! ( " Token doesn't match request organization " ) ;
}
2020-07-14 18:00:09 +02:00
2025-01-20 20:21:44 +01:00
// exit early when we have been invited via /admin panel
if org_id . as_ref ( ) = = FAKE_ADMIN_UUID {
return Ok ( Json ( json! ( { } ) ) ) ;
}
2020-03-20 10:51:17 +01:00
// TODO: We receive the invite token as ?token=<>, validate it contains the org id
2025-01-09 18:37:23 +01:00
let policies = OrgPolicy ::find_by_org ( & org_id , & mut conn ) . await ;
2020-03-20 10:51:17 +01:00
let policies_json : Vec < Value > = policies . iter ( ) . map ( OrgPolicy ::to_json ) . collect ( ) ;
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : policies_json ,
" object " : " list " ,
" continuationToken " : null
2020-03-20 10:51:17 +01:00
} ) ) )
}
2020-03-14 13:22:30 +01:00
#[ get( " /organizations/<org_id>/policies/<pol_type> " ) ]
2025-01-25 01:32:09 +01:00
async fn get_policy ( org_id : OrganizationId , pol_type : i32 , headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-12-14 00:55:34 +01:00
let Some ( pol_type_enum ) = OrgPolicyType ::from_i32 ( pol_type ) else {
err! ( " Invalid or unsupported policy type " )
2020-03-14 13:22:30 +01:00
} ;
2025-01-09 18:37:23 +01:00
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & mut conn ) . await {
2020-03-14 13:22:30 +01:00
Some ( p ) = > p ,
2025-01-09 18:37:23 +01:00
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , " null " . to_string ( ) ) ,
2020-03-14 13:22:30 +01:00
} ;
Ok ( Json ( policy . to_json ( ) ) )
}
#[ derive(Deserialize) ]
struct PolicyData {
enabled : bool ,
#[ serde(rename = " type " ) ]
_type : i32 ,
2021-09-24 17:20:44 +02:00
data : Option < Value > ,
2020-03-14 13:22:30 +01:00
}
#[ put( " /organizations/<org_id>/policies/<pol_type> " , data = " <data> " ) ]
2021-11-16 17:07:55 +01:00
async fn put_policy (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2021-03-31 21:18:35 +01:00
pol_type : i32 ,
data : Json < PolicyData > ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2021-03-31 21:18:35 +01:00
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2020-03-14 13:22:30 +01:00
let data : PolicyData = data . into_inner ( ) ;
2024-12-14 00:55:34 +01:00
let Some ( pol_type_enum ) = OrgPolicyType ::from_i32 ( pol_type ) else {
err! ( " Invalid or unsupported policy type " )
2020-03-14 13:22:30 +01:00
} ;
2024-08-30 21:37:59 +02:00
// Bitwarden only allows the Reset Password policy when Single Org policy is enabled
// Vaultwarden encouraged to use multiple orgs instead of groups because groups were not available in the past
// Now that groups are available we can enforce this option when wanted.
// We put this behind a config option to prevent breaking current installation.
// Maybe we want to enable this by default in the future, but currently it is disabled by default.
if CONFIG . enforce_single_org_with_reset_pw_policy ( ) {
if pol_type_enum = = OrgPolicyType ::ResetPassword & & data . enabled {
let single_org_policy_enabled =
2025-01-09 18:37:23 +01:00
match OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::SingleOrg , & mut conn ) . await {
2024-08-30 21:37:59 +02:00
Some ( p ) = > p . enabled ,
None = > false ,
} ;
if ! single_org_policy_enabled {
err! ( " Single Organization policy is not enabled. It is mandatory for this policy to be enabled. " )
}
}
// Also prevent the Single Org Policy to be disabled if the Reset Password policy is enabled
if pol_type_enum = = OrgPolicyType ::SingleOrg & & ! data . enabled {
let reset_pw_policy_enabled =
2025-01-09 18:37:23 +01:00
match OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::ResetPassword , & mut conn ) . await {
2024-08-30 21:37:59 +02:00
Some ( p ) = > p . enabled ,
None = > false ,
} ;
if reset_pw_policy_enabled {
err! ( " Account recovery policy is enabled. It is not allowed to disable this policy. " )
}
}
}
2024-01-01 19:41:40 +01:00
// When enabling the TwoFactorAuthentication policy, revoke all members that do not have 2FA
2021-04-16 14:49:59 -04:00
if pol_type_enum = = OrgPolicyType ::TwoFactorAuthentication & & data . enabled {
2024-01-01 19:41:40 +01:00
two_factor ::enforce_2fa_policy_for_org (
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
headers . device . atype ,
& headers . ip . ip ,
& mut conn ,
)
. await ? ;
2021-04-11 22:57:17 -04:00
}
2020-03-14 13:22:30 +01:00
2022-08-20 16:42:36 +02:00
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
2021-09-24 17:55:49 +02:00
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
2025-01-09 18:37:23 +01:00
for member in Membership ::find_by_org ( & org_id , & mut conn ) . await . into_iter ( ) {
2021-09-24 17:55:49 +02:00
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
2022-08-20 16:42:36 +02:00
// 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.
// We check if the count is larger then 1, because it includes this organization also.
2025-01-09 18:37:23 +01:00
if member . atype < MembershipType ::Admin
& & member . status ! = MembershipStatus ::Invited as i32
& & Membership ::count_accepted_and_confirmed_by_user ( & member . user_uuid , & mut conn ) . await > 1
2022-08-20 16:42:36 +02:00
{
if CONFIG . mail_enabled ( ) {
2022-05-20 23:39:47 +02:00
let org = Organization ::find_by_uuid ( & member . org_uuid , & mut conn ) . await . unwrap ( ) ;
let user = User ::find_by_uuid ( & member . user_uuid , & mut conn ) . await . unwrap ( ) ;
2022-08-20 16:42:36 +02:00
mail ::send_single_org_removed_from_org ( & user . email , & org . name ) . await ? ;
2021-09-24 17:55:49 +02:00
}
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& member . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2022-05-20 23:39:47 +02:00
member . delete ( & mut conn ) . await ? ;
2021-09-24 17:55:49 +02:00
}
}
}
2025-01-09 18:37:23 +01:00
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & mut conn ) . await {
2020-03-14 13:22:30 +01:00
Some ( p ) = > p ,
2025-01-09 18:37:23 +01:00
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , " {} " . to_string ( ) ) ,
2020-03-14 13:22:30 +01:00
} ;
policy . enabled = data . enabled ;
policy . data = serde_json ::to_string ( & data . data ) ? ;
2022-05-20 23:39:47 +02:00
policy . save ( & mut conn ) . await ? ;
2020-03-14 13:22:30 +01:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::PolicyUpdated as i32 ,
2025-01-09 18:37:23 +01:00
policy . uuid . as_ref ( ) ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2020-03-14 13:22:30 +01:00
Ok ( Json ( policy . to_json ( ) ) )
2020-04-09 01:51:05 -07:00
}
2020-09-14 08:34:17 +02:00
2021-01-31 21:46:37 +01:00
#[ allow(unused_variables) ]
#[ get( " /organizations/<org_id>/tax " ) ]
2025-01-09 18:37:23 +01:00
fn get_organization_tax ( org_id : OrganizationId , _headers : Headers ) -> Json < Value > {
2021-01-31 21:46:37 +01:00
// Prevent a 404 error, which also causes Javascript errors.
2021-11-05 19:18:54 +01:00
// Upstream sends "Only allowed when not self hosted." As an error message.
// If we do the same it will also output this to the log, which is overkill.
// An empty list/data also works fine.
Json ( _empty_data_json ( ) )
2021-01-31 21:46:37 +01:00
}
2020-09-14 08:34:17 +02:00
#[ get( " /plans " ) ]
2022-09-12 14:10:54 +02:00
fn get_plans ( ) -> Json < Value > {
2021-11-05 19:18:54 +01:00
// Respond with a minimal json just enough to allow the creation of an new organization.
2021-03-27 15:07:26 +00:00
Json ( json! ( {
2024-06-23 21:31:02 +02:00
" object " : " list " ,
" data " : [ {
" object " : " plan " ,
" type " : 0 ,
" product " : 0 ,
" name " : " Free " ,
" nameLocalizationKey " : " planNameFree " ,
" bitwardenProduct " : 0 ,
" maxUsers " : 0 ,
" descriptionLocalizationKey " : " planDescFree "
2023-08-24 22:33:35 +02:00
} , {
2024-06-23 21:31:02 +02:00
" object " : " plan " ,
" type " : 0 ,
" product " : 1 ,
" name " : " Free " ,
" nameLocalizationKey " : " planNameFree " ,
" bitwardenProduct " : 1 ,
" maxUsers " : 0 ,
" descriptionLocalizationKey " : " planDescFree "
2021-11-05 19:18:54 +01:00
} ] ,
2024-06-23 21:31:02 +02:00
" continuationToken " : null
2021-03-27 15:07:26 +00:00
} ) )
2021-01-23 20:50:06 -08:00
}
2021-01-31 21:46:37 +01:00
2023-08-24 22:33:35 +02:00
#[ get( " /plans/all " ) ]
fn get_plans_all ( ) -> Json < Value > {
get_plans ( )
}
2021-01-31 21:46:37 +01:00
#[ get( " /plans/sales-tax-rates " ) ]
2021-11-05 19:18:54 +01:00
fn get_plans_tax_rates ( _headers : Headers ) -> Json < Value > {
2021-01-31 21:46:37 +01:00
// Prevent a 404 error, which also causes Javascript errors.
2021-11-05 19:18:54 +01:00
Json ( _empty_data_json ( ) )
}
2025-01-04 19:31:59 +01:00
#[ get( " /organizations/<_org_id>/billing/metadata " ) ]
2025-01-09 18:37:23 +01:00
fn get_billing_metadata ( _org_id : OrganizationId , _headers : Headers ) -> Json < Value > {
2025-01-04 19:31:59 +01:00
// Prevent a 404 error, which also causes Javascript errors.
Json ( _empty_data_json ( ) )
}
2021-11-05 19:18:54 +01:00
fn _empty_data_json ( ) -> Value {
json! ( {
2024-06-23 21:31:02 +02:00
" object " : " list " ,
" data " : [ ] ,
" continuationToken " : null
2021-11-05 19:18:54 +01:00
} )
2021-01-31 21:46:37 +01:00
}
2021-02-06 18:22:39 +01:00
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2021-02-06 18:22:39 +01:00
struct OrgImportGroupData {
2024-06-23 21:31:02 +02:00
#[ allow(dead_code) ]
name : String , // "GroupName"
#[ allow(dead_code) ]
external_id : String , // "cn=GroupName,ou=Groups,dc=example,dc=com"
#[ allow(dead_code) ]
users : Vec < String > , // ["uid=user,ou=People,dc=example,dc=com"]
2021-02-06 18:22:39 +01:00
}
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2021-02-06 18:22:39 +01:00
struct OrgImportUserData {
2024-06-23 21:31:02 +02:00
email : String , // "user@maildomain.net"
2021-09-22 21:39:31 +02:00
#[ allow(dead_code) ]
2024-06-23 21:31:02 +02:00
external_id : String , // "uid=user,ou=People,dc=example,dc=com"
deleted : bool ,
2021-02-06 18:22:39 +01:00
}
#[ derive(Deserialize, Debug) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2021-02-06 18:22:39 +01:00
struct OrgImportData {
2021-09-22 21:39:31 +02:00
#[ allow(dead_code) ]
2024-06-23 21:31:02 +02:00
groups : Vec < OrgImportGroupData > ,
overwrite_existing : bool ,
users : Vec < OrgImportUserData > ,
2021-02-06 18:22:39 +01:00
}
2025-01-08 18:13:45 +01:00
/// This function seems to be deprected
/// It is only used with older directory connectors
/// TODO: Cleanup Tech debt
2021-02-06 18:22:39 +01:00
#[ post( " /organizations/<org_id>/import " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn import ( org_id : OrganizationId , data : Json < OrgImportData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
2021-02-06 18:22:39 +01:00
// TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way
// to differentiate between auto-imported users and manually added ones.
// This means that this endpoint can end up removing users that were added manually by an admin,
// as opposed to upstream which only removes auto-imported users.
2023-10-05 20:08:26 +03:00
// User needs to be admin or owner to use the Directory Connector
2025-01-09 18:37:23 +01:00
match Membership ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
Some ( member ) if member . atype > = MembershipType ::Admin = > { /* Okay, nothing to do */ }
2021-02-06 18:22:39 +01:00
Some ( _ ) = > err! ( " User has insufficient permissions to use Directory Connector " ) ,
None = > err! ( " User not part of organization " ) ,
} ;
2024-06-23 21:31:02 +02:00
for user_data in & data . users {
if user_data . deleted {
2021-02-06 18:22:39 +01:00
// If user is marked for deletion and it exists, delete it
2025-01-09 18:37:23 +01:00
if let Some ( member ) = Membership ::find_by_email_and_org ( & user_data . email , & org_id , & mut conn ) . await {
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
2025-01-09 18:37:23 +01:00
& member . uuid ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
member . delete ( & mut conn ) . await ? ;
2021-02-06 18:22:39 +01:00
}
// If user is not part of the organization, but it exists
2025-01-09 18:37:23 +01:00
} else if Membership ::find_by_email_and_org ( & user_data . email , & org_id , & mut conn ) . await . is_none ( ) {
2024-06-23 21:31:02 +02:00
if let Some ( user ) = User ::find_by_mail ( & user_data . email , & mut conn ) . await {
2025-01-09 18:37:23 +01:00
let member_status = if CONFIG . mail_enabled ( ) {
MembershipStatus ::Invited as i32
2021-02-06 18:22:39 +01:00
} else {
2025-01-09 18:37:23 +01:00
MembershipStatus ::Accepted as i32 // Automatically mark user as accepted if no email invites
2021-02-06 18:22:39 +01:00
} ;
2025-01-09 18:37:23 +01:00
let mut new_member = Membership ::new ( user . uuid . clone ( ) , org_id . clone ( ) ) ;
2025-01-08 18:13:45 +01:00
new_member . access_all = false ;
2025-01-09 18:37:23 +01:00
new_member . atype = MembershipType ::User as i32 ;
new_member . status = member_status ;
2022-11-20 19:15:45 +01:00
2021-02-06 18:22:39 +01:00
if CONFIG . mail_enabled ( ) {
2025-01-09 18:37:23 +01:00
let org_name = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2021-02-06 18:22:39 +01:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization " ) ,
} ;
mail ::send_invite (
2024-09-01 15:55:41 +02:00
& user ,
2025-01-20 20:21:44 +01:00
org_id . clone ( ) ,
new_member . uuid . clone ( ) ,
2021-02-06 18:22:39 +01:00
& org_name ,
Some ( headers . user . email . clone ( ) ) ,
2022-07-06 23:57:37 +02:00
)
. await ? ;
2021-02-06 18:22:39 +01:00
}
2025-01-08 18:13:45 +01:00
// Save the member after sending an email
// If sending fails the member will not be saved to the database, and will not result in the admin needing to reinvite the users manually
new_member . save ( & mut conn ) . await ? ;
log_event (
EventType ::OrganizationUserInvited as i32 ,
& new_member . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2025-01-08 18:13:45 +01:00
& headers . user . uuid ,
headers . device . atype ,
& headers . ip . ip ,
& mut conn ,
)
. await ;
2021-03-27 15:07:26 +00:00
}
2021-02-06 18:22:39 +01:00
}
}
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
2024-06-23 21:31:02 +02:00
if data . overwrite_existing {
2025-01-09 18:37:23 +01:00
for member in Membership ::find_by_org_and_type ( & org_id , MembershipType ::User , & mut conn ) . await {
if let Some ( user_email ) = User ::find_by_uuid ( & member . user_uuid , & mut conn ) . await . map ( | u | u . email ) {
2024-06-23 21:31:02 +02:00
if ! data . users . iter ( ) . any ( | u | u . email = = user_email ) {
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
2025-01-09 18:37:23 +01:00
& member . uuid ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
member . delete ( & mut conn ) . await ? ;
2021-02-06 18:22:39 +01:00
}
2021-03-27 15:07:26 +00:00
}
2021-02-06 18:22:39 +01:00
}
}
Ok ( ( ) )
}
2022-08-20 16:42:36 +02:00
2022-09-12 16:08:36 +02:00
// Pre web-vault v2022.9.x endpoint
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/deactivate " ) ]
async fn deactivate_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-08-20 16:42:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-08-20 16:42:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_revoke_member ( & org_id , & member_id , & headers , & mut conn ) . await
}
#[ derive(Deserialize, Debug) ]
#[ serde(rename_all = " camelCase " ) ]
struct BulkRevokeMembershipIds {
ids : Option < Vec < MembershipId > > ,
2022-08-20 16:42:36 +02:00
}
2022-09-12 16:08:36 +02:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 16:42:36 +02:00
#[ put( " /organizations/<org_id>/users/deactivate " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_deactivate_members (
org_id : OrganizationId ,
data : Json < BulkRevokeMembershipIds > ,
2022-08-20 16:42:36 +02:00
headers : AdminHeaders ,
conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
2025-01-09 18:37:23 +01:00
bulk_revoke_members ( org_id , data , headers , conn ) . await
2022-09-12 16:08:36 +02:00
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/revoke " ) ]
async fn revoke_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-09-12 16:08:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-09-12 16:08:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_revoke_member ( & org_id , & member_id , & headers , & mut conn ) . await
2024-06-23 21:31:02 +02:00
}
2022-09-12 16:08:36 +02:00
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_revoke_members (
org_id : OrganizationId ,
data : Json < BulkRevokeMembershipIds > ,
2022-09-12 16:08:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
2022-08-20 16:42:36 +02:00
let mut bulk_response = Vec ::new ( ) ;
2024-06-23 21:31:02 +02:00
match data . ids {
2025-01-09 18:37:23 +01:00
Some ( members ) = > {
for member_id in members {
let err_msg = match _revoke_member ( & org_id , & member_id , & headers , & mut conn ) . await {
2022-11-04 12:56:02 +01:00
Ok ( _ ) = > String ::new ( ) ,
2022-12-29 14:11:52 +01:00
Err ( e ) = > format! ( " {e:?} " ) ,
2022-08-20 16:42:36 +02:00
} ;
bulk_response . push ( json! (
{
2024-06-23 21:31:02 +02:00
" object " : " OrganizationUserBulkResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-23 21:31:02 +02:00
" error " : err_msg
2022-08-20 16:42:36 +02:00
}
) ) ;
}
}
None = > error! ( " No users to revoke " ) ,
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2022-08-20 16:42:36 +02:00
}
2025-01-09 18:37:23 +01:00
async fn _revoke_member (
org_id : & OrganizationId ,
member_id : & MembershipId ,
2022-08-20 16:42:36 +02:00
headers : & AdminHeaders ,
2022-05-20 23:39:47 +02:00
conn : & mut DbConn ,
2022-08-20 16:42:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
Some ( mut member ) if member . status > MembershipStatus ::Revoked as i32 = > {
if member . user_uuid = = headers . user . uuid {
2022-08-20 16:42:36 +02:00
err! ( " You cannot revoke yourself " )
}
2025-01-09 18:37:23 +01:00
if member . atype = = MembershipType ::Owner & & headers . membership_type ! = MembershipType ::Owner {
2022-08-20 16:42:36 +02:00
err! ( " Only owners can revoke other owners " )
}
2025-01-09 18:37:23 +01:00
if member . atype = = MembershipType ::Owner
& & Membership ::count_confirmed_by_org_and_type ( org_id , MembershipType ::Owner , conn ) . await < = 1
2022-08-20 16:42:36 +02:00
{
err! ( " Organization must have at least one confirmed owner " )
}
2025-01-09 18:37:23 +01:00
member . revoke ( ) ;
member . save ( conn ) . await ? ;
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRevoked as i32 ,
2025-01-09 18:37:23 +01:00
& member . uuid ,
2023-04-30 17:18:12 +02:00
org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
conn ,
)
. await ;
2022-08-20 16:42:36 +02:00
}
Some ( _ ) = > err! ( " User is already revoked " ) ,
None = > err! ( " User not found in organization " ) ,
}
Ok ( ( ) )
}
2022-09-12 16:08:36 +02:00
// Pre web-vault v2022.9.x endpoint
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/activate " ) ]
async fn activate_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-08-20 16:42:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-08-20 16:42:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_restore_member ( & org_id , & member_id , & headers , & mut conn ) . await
2022-08-20 16:42:36 +02:00
}
2022-09-12 16:08:36 +02:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 16:42:36 +02:00
#[ put( " /organizations/<org_id>/users/activate " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_activate_members (
org_id : OrganizationId ,
data : Json < BulkMembershipIds > ,
2022-08-20 16:42:36 +02:00
headers : AdminHeaders ,
conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
2025-01-09 18:37:23 +01:00
bulk_restore_members ( org_id , data , headers , conn ) . await
2022-09-12 16:08:36 +02:00
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/restore " ) ]
async fn restore_member (
org_id : OrganizationId ,
member_id : MembershipId ,
2022-09-12 16:08:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-09-12 16:08:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
_restore_member ( & org_id , & member_id , & headers , & mut conn ) . await
2022-09-12 16:08:36 +02:00
}
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn bulk_restore_members (
org_id : OrganizationId ,
data : Json < BulkMembershipIds > ,
2022-09-12 16:08:36 +02:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
2022-08-20 16:42:36 +02:00
let mut bulk_response = Vec ::new ( ) ;
2025-01-09 18:37:23 +01:00
for member_id in data . ids {
let err_msg = match _restore_member ( & org_id , & member_id , & headers , & mut conn ) . await {
2024-06-24 21:17:59 +02:00
Ok ( _ ) = > String ::new ( ) ,
Err ( e ) = > format! ( " {e:?} " ) ,
} ;
2022-08-20 16:42:36 +02:00
2024-06-24 21:17:59 +02:00
bulk_response . push ( json! (
{
" object " : " OrganizationUserBulkResponseModel " ,
2025-01-09 18:37:23 +01:00
" id " : member_id ,
2024-06-24 21:17:59 +02:00
" error " : err_msg
2022-08-20 16:42:36 +02:00
}
2024-06-24 21:17:59 +02:00
) ) ;
2022-08-20 16:42:36 +02:00
}
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : bulk_response ,
" object " : " list " ,
" continuationToken " : null
2025-01-25 01:32:09 +01:00
} ) ) )
2022-08-20 16:42:36 +02:00
}
2025-01-09 18:37:23 +01:00
async fn _restore_member (
org_id : & OrganizationId ,
member_id : & MembershipId ,
2022-08-20 16:42:36 +02:00
headers : & AdminHeaders ,
2022-05-20 23:39:47 +02:00
conn : & mut DbConn ,
2022-08-20 16:42:36 +02:00
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
match Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await {
Some ( mut member ) if member . status < MembershipStatus ::Accepted as i32 = > {
if member . user_uuid = = headers . user . uuid {
2022-08-20 16:42:36 +02:00
err! ( " You cannot restore yourself " )
}
2025-01-09 18:37:23 +01:00
if member . atype = = MembershipType ::Owner & & headers . membership_type ! = MembershipType ::Owner {
2022-08-20 16:42:36 +02:00
err! ( " Only owners can restore other owners " )
}
2025-01-09 18:37:23 +01:00
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
2022-08-20 16:42:36 +02:00
// It returns different error messages per function.
2025-01-09 18:37:23 +01:00
if member . atype < MembershipType ::Admin {
match OrgPolicy ::is_user_allowed ( & member . user_uuid , org_id , false , conn ) . await {
2022-08-20 16:42:36 +02:00
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
2024-03-17 22:35:02 +01:00
if CONFIG . email_2fa_auto_fallback ( ) {
2025-01-09 18:37:23 +01:00
two_factor ::email ::find_and_activate_email_2fa ( & member . user_uuid , conn ) . await ? ;
2024-03-17 22:35:02 +01:00
} else {
err! ( " You cannot restore this user because they have not setup 2FA " ) ;
}
2022-08-20 16:42:36 +02:00
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
2024-03-17 22:35:02 +01:00
err! ( " You cannot restore this user because they are a member of an organization which forbids it " ) ;
2022-08-20 16:42:36 +02:00
}
}
}
2025-01-09 18:37:23 +01:00
member . restore ( ) ;
member . save ( conn ) . await ? ;
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserRestored as i32 ,
2025-01-09 18:37:23 +01:00
& member . uuid ,
2023-04-30 17:18:12 +02:00
org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
conn ,
)
. await ;
2022-08-20 16:42:36 +02:00
}
Some ( _ ) = > err! ( " User is already active " ) ,
None = > err! ( " User not found in organization " ) ,
}
Ok ( ( ) )
}
2022-09-24 18:27:13 +02:00
2022-10-20 15:31:53 +02:00
#[ get( " /organizations/<org_id>/groups " ) ]
2025-01-25 01:32:09 +01:00
async fn get_groups ( org_id : OrganizationId , headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . membership . org_uuid {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2023-02-27 16:37:58 +01:00
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>()
2025-01-09 18:37:23 +01:00
let groups = Group ::find_by_organization ( & org_id , & mut conn ) . await ;
2023-02-27 16:37:58 +01:00
let mut groups_json = Vec ::with_capacity ( groups . len ( ) ) ;
2024-08-15 12:36:00 +02:00
2023-02-27 16:37:58 +01:00
for g in groups {
2024-11-13 19:19:19 +01:00
groups_json . push ( g . to_json_details ( & mut conn ) . await )
2023-02-27 16:37:58 +01:00
}
groups_json
2023-02-22 12:17:13 -08:00
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups.
2023-02-27 16:37:58 +01:00
Vec ::with_capacity ( 0 )
2023-02-22 12:17:13 -08:00
} ;
2022-10-20 15:31:53 +02:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" data " : groups ,
" object " : " list " ,
" continuationToken " : null ,
2022-10-20 15:31:53 +02:00
} ) ) )
}
2025-01-04 19:31:59 +01:00
#[ get( " /organizations/<org_id>/groups/details " , rank = 1) ]
2025-01-09 18:37:23 +01:00
async fn get_groups_details ( org_id : OrganizationId , headers : ManagerHeadersLoose , conn : DbConn ) -> JsonResult {
2025-01-04 19:31:59 +01:00
get_groups ( org_id , headers , conn ) . await
}
2022-10-20 15:31:53 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2022-10-20 15:31:53 +02:00
struct GroupRequest {
2024-06-23 21:31:02 +02:00
name : String ,
2024-08-01 19:45:42 +02:00
#[ serde(default) ]
access_all : bool ,
2024-06-23 21:31:02 +02:00
external_id : Option < String > ,
2025-01-09 18:37:23 +01:00
collections : Vec < SelectedCollection > ,
users : Vec < MembershipId > ,
2022-10-20 15:31:53 +02:00
}
impl GroupRequest {
2025-01-09 18:37:23 +01:00
pub fn to_group ( & self , org_uuid : & OrganizationId ) -> Group {
Group ::new ( org_uuid . clone ( ) , self . name . clone ( ) , self . access_all , self . external_id . clone ( ) )
2022-10-20 15:31:53 +02:00
}
2023-07-12 21:58:45 +02:00
pub fn update_group ( & self , mut group : Group ) -> Group {
2024-06-23 21:31:02 +02:00
group . name . clone_from ( & self . name ) ;
2024-08-01 19:45:42 +02:00
group . access_all = self . access_all ;
2023-07-12 21:58:45 +02:00
// Group Updates do not support changing the external_id
// These input fields are in a disabled state, and can only be updated/added via ldap_import
2022-10-20 15:31:53 +02:00
2023-07-12 21:58:45 +02:00
group
2022-10-20 15:31:53 +02:00
}
}
#[ derive(Deserialize, Serialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2025-01-09 18:37:23 +01:00
struct SelectedCollection {
id : CollectionId ,
2024-06-23 21:31:02 +02:00
read_only : bool ,
hide_passwords : bool ,
2025-01-21 23:33:41 +01:00
manage : bool ,
2022-10-20 15:31:53 +02:00
}
2025-01-09 18:37:23 +01:00
impl SelectedCollection {
pub fn to_collection_group ( & self , groups_uuid : GroupId ) -> CollectionGroup {
2025-01-21 23:33:41 +01:00
CollectionGroup ::new ( self . id . clone ( ) , groups_uuid , self . read_only , self . hide_passwords , self . manage )
2022-10-20 15:31:53 +02:00
}
}
2022-11-20 19:15:45 +01:00
#[ post( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
2022-10-20 15:31:53 +02:00
async fn post_group (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
group_id : GroupId ,
2024-06-23 21:31:02 +02:00
data : Json < GroupRequest > ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-10-20 15:31:53 +02:00
conn : DbConn ,
) -> JsonResult {
2023-03-09 16:31:28 +01:00
put_group ( org_id , group_id , data , headers , conn ) . await
2022-10-20 15:31:53 +02:00
}
#[ post( " /organizations/<org_id>/groups " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn post_groups (
org_id : OrganizationId ,
headers : AdminHeaders ,
data : Json < GroupRequest > ,
mut conn : DbConn ,
) -> JsonResult {
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2024-06-23 21:31:02 +02:00
let group_request = data . into_inner ( ) ;
2025-01-09 18:37:23 +01:00
let group = group_request . to_group ( & org_id ) ;
2022-10-20 15:31:53 +02:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::GroupCreated as i32 ,
& group . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2024-06-23 21:31:02 +02:00
add_update_group ( group , group_request . collections , group_request . users , org_id , & headers , & mut conn ) . await
2022-10-20 15:31:53 +02:00
}
2022-11-20 19:15:45 +01:00
#[ put( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
2022-10-20 15:31:53 +02:00
async fn put_group (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
group_id : GroupId ,
2024-06-23 21:31:02 +02:00
data : Json < GroupRequest > ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-10-20 15:31:53 +02:00
) -> JsonResult {
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( group ) = Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Group not found " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
2024-06-23 21:31:02 +02:00
let group_request = data . into_inner ( ) ;
2023-07-12 21:58:45 +02:00
let updated_group = group_request . update_group ( group ) ;
2022-10-20 15:31:53 +02:00
2025-01-09 18:37:23 +01:00
CollectionGroup ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
GroupUser ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
2022-10-20 15:31:53 +02:00
2022-11-20 19:15:45 +01:00
log_event (
EventType ::GroupUpdated as i32 ,
& updated_group . uuid ,
2025-01-09 18:37:23 +01:00
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2024-06-23 21:31:02 +02:00
add_update_group ( updated_group , group_request . collections , group_request . users , org_id , & headers , & mut conn ) . await
2022-10-20 15:31:53 +02:00
}
2023-02-27 16:37:58 +01:00
async fn add_update_group (
mut group : Group ,
2025-01-09 18:37:23 +01:00
collections : Vec < SelectedCollection > ,
members : Vec < MembershipId > ,
org_id : OrganizationId ,
2023-02-27 16:37:58 +01:00
headers : & AdminHeaders ,
conn : & mut DbConn ,
) -> JsonResult {
2022-10-20 15:31:53 +02:00
group . save ( conn ) . await ? ;
2025-01-09 18:37:23 +01:00
for col_selection in collections {
let mut collection_group = col_selection . to_collection_group ( group . uuid . clone ( ) ) ;
2022-10-20 15:31:53 +02:00
collection_group . save ( conn ) . await ? ;
}
2025-01-09 18:37:23 +01:00
for assigned_member in members {
let mut user_entry = GroupUser ::new ( group . uuid . clone ( ) , assigned_member . clone ( ) ) ;
2023-02-27 16:37:58 +01:00
user_entry . save ( conn ) . await ? ;
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
2025-01-09 18:37:23 +01:00
& assigned_member ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2023-02-27 16:37:58 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2023-02-27 16:37:58 +01:00
conn ,
)
. await ;
}
2022-10-20 15:31:53 +02:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" id " : group . uuid ,
" organizationId " : group . organizations_uuid ,
" name " : group . name ,
" accessAll " : group . access_all ,
" externalId " : group . external_id
2022-10-20 15:31:53 +02:00
} ) ) )
}
2024-12-14 00:55:34 +01:00
#[ get( " /organizations/<org_id>/groups/<group_id>/details " ) ]
2025-01-09 18:37:23 +01:00
async fn get_group_details (
org_id : OrganizationId ,
group_id : GroupId ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( group ) = Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Group not found " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
2024-11-13 19:19:19 +01:00
Ok ( Json ( group . to_json_details ( & mut conn ) . await ) )
2022-10-20 15:31:53 +02:00
}
#[ post( " /organizations/<org_id>/groups/<group_id>/delete " ) ]
2025-01-09 18:37:23 +01:00
async fn post_delete_group (
org_id : OrganizationId ,
group_id : GroupId ,
headers : AdminHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
_delete_group ( & org_id , & group_id , & headers , & mut conn ) . await
2022-10-20 15:31:53 +02:00
}
2022-11-20 19:15:45 +01:00
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
2025-01-09 18:37:23 +01:00
async fn delete_group (
org_id : OrganizationId ,
group_id : GroupId ,
headers : AdminHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
_delete_group ( & org_id , & group_id , & headers , & mut conn ) . await
2023-03-18 21:13:06 +01:00
}
2025-01-09 18:37:23 +01:00
async fn _delete_group (
org_id : & OrganizationId ,
group_id : & GroupId ,
headers : & AdminHeaders ,
conn : & mut DbConn ,
) -> EmptyResult {
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2024-12-14 00:55:34 +01:00
let Some ( group ) = Group ::find_by_uuid_and_org ( group_id , org_id , conn ) . await else {
err! ( " Group not found " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
2022-11-20 19:15:45 +01:00
log_event (
EventType ::GroupDeleted as i32 ,
& group . uuid ,
org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2023-03-18 21:13:06 +01:00
conn ,
2022-11-20 19:15:45 +01:00
)
. await ;
2023-03-18 21:13:06 +01:00
group . delete ( conn ) . await
}
#[ delete( " /organizations/<org_id>/groups " , data = " <data> " ) ]
async fn bulk_delete_groups (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
data : Json < BulkGroupIds > ,
2023-03-18 21:13:06 +01:00
headers : AdminHeaders ,
mut conn : DbConn ,
) -> EmptyResult {
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
let data : BulkGroupIds = data . into_inner ( ) ;
2023-03-18 21:13:06 +01:00
2024-06-23 21:31:02 +02:00
for group_id in data . ids {
2025-01-09 18:37:23 +01:00
_delete_group ( & org_id , & group_id , & headers , & mut conn ) . await ?
2023-03-18 21:13:06 +01:00
}
Ok ( ( ) )
2022-10-20 15:31:53 +02:00
}
2025-01-04 19:31:59 +01:00
#[ get( " /organizations/<org_id>/groups/<group_id> " , rank = 2) ]
2025-01-25 01:32:09 +01:00
async fn get_group ( org_id : OrganizationId , group_id : GroupId , headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( group ) = Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Group not found " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
Ok ( Json ( group . to_json ( ) ) )
}
2024-12-14 00:55:34 +01:00
#[ get( " /organizations/<org_id>/groups/<group_id>/users " ) ]
2025-01-09 18:37:23 +01:00
async fn get_group_members (
org_id : OrganizationId ,
group_id : GroupId ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " Group could not be found! " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
2025-01-09 18:37:23 +01:00
let group_members : Vec < MembershipId > = GroupUser ::find_by_group ( & group_id , & mut conn )
2022-10-20 15:31:53 +02:00
. await
. iter ( )
. map ( | entry | entry . users_organizations_uuid . clone ( ) )
. collect ( ) ;
2025-01-09 18:37:23 +01:00
Ok ( Json ( json! ( group_members ) ) )
2022-10-20 15:31:53 +02:00
}
2022-11-20 19:15:45 +01:00
#[ put( " /organizations/<org_id>/groups/<group_id>/users " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn put_group_members (
org_id : OrganizationId ,
group_id : GroupId ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2025-01-09 18:37:23 +01:00
data : Json < Vec < MembershipId > > ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-10-20 15:31:53 +02:00
) -> EmptyResult {
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " Group could not be found! " , " Group uuid is invalid or does not belong to the organization " )
2022-10-20 15:31:53 +02:00
} ;
2025-01-09 18:37:23 +01:00
GroupUser ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
2022-10-20 15:31:53 +02:00
2025-01-09 18:37:23 +01:00
let assigned_members = data . into_inner ( ) ;
for assigned_member in assigned_members {
let mut user_entry = GroupUser ::new ( group_id . clone ( ) , assigned_member . clone ( ) ) ;
2022-05-20 23:39:47 +02:00
user_entry . save ( & mut conn ) . await ? ;
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
2025-01-09 18:37:23 +01:00
& assigned_member ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2022-10-20 15:31:53 +02:00
}
Ok ( ( ) )
}
2025-01-09 18:37:23 +01:00
#[ get( " /organizations/<org_id>/users/<member_id>/groups " ) ]
async fn get_user_groups (
org_id : OrganizationId ,
member_id : MembershipId ,
2025-01-25 01:32:09 +01:00
headers : AdminHeaders ,
2025-01-09 18:37:23 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
if Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " User could not be found! " )
2022-10-20 15:31:53 +02:00
} ;
2025-01-09 18:37:23 +01:00
let user_groups : Vec < GroupId > =
GroupUser ::find_by_member ( & member_id , & mut conn ) . await . iter ( ) . map ( | entry | entry . groups_uuid . clone ( ) ) . collect ( ) ;
2022-10-20 15:31:53 +02:00
Ok ( Json ( json! ( user_groups ) ) )
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2022-10-20 15:31:53 +02:00
struct OrganizationUserUpdateGroupsRequest {
2025-01-09 18:37:23 +01:00
group_ids : Vec < GroupId > ,
2022-10-20 15:31:53 +02:00
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/users/<member_id>/groups " , data = " <data> " ) ]
2022-10-20 15:31:53 +02:00
async fn post_user_groups (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUserUpdateGroupsRequest > ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-10-20 15:31:53 +02:00
conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
put_user_groups ( org_id , member_id , data , headers , conn ) . await
2022-10-20 15:31:53 +02:00
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/groups " , data = " <data> " ) ]
2022-10-20 15:31:53 +02:00
async fn put_user_groups (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUserUpdateGroupsRequest > ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-10-20 15:31:53 +02:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
if Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " User could not be found or does not belong to the organization. " ) ;
2023-07-03 19:58:14 +02:00
}
2025-01-09 18:37:23 +01:00
GroupUser ::delete_all_by_member ( & member_id , & mut conn ) . await ? ;
2022-10-20 15:31:53 +02:00
2024-06-23 21:31:02 +02:00
let assigned_group_ids = data . into_inner ( ) ;
for assigned_group_id in assigned_group_ids . group_ids {
2025-01-09 18:37:23 +01:00
let mut group_user = GroupUser ::new ( assigned_group_id . clone ( ) , member_id . clone ( ) ) ;
2022-05-20 23:39:47 +02:00
group_user . save ( & mut conn ) . await ? ;
2022-10-20 15:31:53 +02:00
}
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
2025-01-09 18:37:23 +01:00
& member_id ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2022-10-20 15:31:53 +02:00
Ok ( ( ) )
}
2025-01-09 18:37:23 +01:00
#[ post( " /organizations/<org_id>/groups/<group_id>/delete-user/<member_id> " ) ]
async fn post_delete_group_member (
org_id : OrganizationId ,
group_id : GroupId ,
member_id : MembershipId ,
2022-10-20 15:31:53 +02:00
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
delete_group_member ( org_id , group_id , member_id , headers , conn ) . await
2022-10-20 15:31:53 +02:00
}
2025-01-09 18:37:23 +01:00
#[ delete( " /organizations/<org_id>/groups/<group_id>/users/<member_id> " ) ]
async fn delete_group_member (
org_id : OrganizationId ,
group_id : GroupId ,
member_id : MembershipId ,
2022-11-20 19:15:45 +01:00
headers : AdminHeaders ,
2022-05-20 23:39:47 +02:00
mut conn : DbConn ,
2022-10-20 15:31:53 +02:00
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-15 17:15:48 +01:00
if ! CONFIG . org_groups_enabled ( ) {
err! ( " Group support is disabled " ) ;
}
2025-01-09 18:37:23 +01:00
if Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " User could not be found or does not belong to the organization. " ) ;
2023-07-03 19:58:14 +02:00
}
2025-01-09 18:37:23 +01:00
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & mut conn ) . await . is_none ( ) {
2024-12-14 00:55:34 +01:00
err! ( " Group could not be found or does not belong to the organization. " ) ;
2023-07-03 19:58:14 +02:00
}
2022-11-20 19:15:45 +01:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
2025-01-09 18:37:23 +01:00
& member_id ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2022-11-20 19:15:45 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2022-11-20 19:15:45 +01:00
& mut conn ,
)
. await ;
2025-01-09 18:37:23 +01:00
GroupUser ::delete_by_group_and_member ( & group_id , & member_id , & mut conn ) . await
2022-10-20 15:31:53 +02:00
}
2023-01-25 08:06:21 +01:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-01-25 08:06:21 +01:00
struct OrganizationUserResetPasswordEnrollmentRequest {
2024-06-23 21:31:02 +02:00
reset_password_key : Option < String > ,
master_password_hash : Option < String > ,
otp : Option < String > ,
2023-01-25 08:06:21 +01:00
}
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-01-25 08:06:21 +01:00
struct OrganizationUserResetPasswordRequest {
2024-06-23 21:31:02 +02:00
new_master_password_hash : String ,
key : String ,
2023-01-25 08:06:21 +01:00
}
2024-11-26 01:35:00 +08:00
// Upstream reports this is the renamed endpoint instead of `/keys`
2024-08-11 19:39:56 +02:00
// But the clients do not seem to use this at all
// Just add it here in case they will
#[ get( " /organizations/<org_id>/public-key " ) ]
2025-01-25 01:32:09 +01:00
async fn get_organization_public_key (
org_id : OrganizationId ,
headers : OrgMemberHeaders ,
mut conn : DbConn ,
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Organization not found " )
2023-01-25 08:06:21 +01:00
} ;
Ok ( Json ( json! ( {
2024-08-11 19:39:56 +02:00
" object " : " organizationPublicKey " ,
2024-06-23 21:31:02 +02:00
" publicKey " : org . public_key ,
2023-01-25 08:06:21 +01:00
} ) ) )
}
2024-08-11 19:39:56 +02:00
// 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
#[ get( " /organizations/<org_id>/keys " ) ]
2025-01-25 01:32:09 +01:00
async fn get_organization_keys ( org_id : OrganizationId , headers : OrgMemberHeaders , conn : DbConn ) -> JsonResult {
2024-08-11 19:39:56 +02:00
get_organization_public_key ( org_id , headers , conn ) . await
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/reset-password " , data = " <data> " ) ]
2023-01-25 08:06:21 +01:00
async fn put_reset_password (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2023-01-25 08:06:21 +01:00
headers : AdminHeaders ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUserResetPasswordRequest > ,
2023-01-25 08:06:21 +01:00
mut conn : DbConn ,
nt : Notify < '_ > ,
) -> EmptyResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Required organization not found " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_uuid_and_org ( & member_id , & org . uuid , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User to reset isn't member of required organization " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
let Some ( user ) = User ::find_by_uuid ( & member . user_uuid , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User not found " )
2023-02-04 13:29:57 +01:00
} ;
2025-01-09 18:37:23 +01:00
check_reset_password_applicable_and_permissions ( & org_id , & member_id , & headers , & mut conn ) . await ? ;
2023-02-04 13:29:57 +01:00
2025-01-09 18:37:23 +01:00
if member . reset_password_key . is_none ( ) {
2023-02-04 13:29:57 +01:00
err! ( " Password reset not or not correctly enrolled " ) ;
2023-01-25 08:06:21 +01:00
}
2025-01-09 18:37:23 +01:00
if member . status ! = ( MembershipStatus ::Confirmed as i32 ) {
2023-01-25 08:06:21 +01:00
err! ( " Organization user must be confirmed for password reset functionality " ) ;
}
2023-02-04 13:29:57 +01:00
// Sending email before resetting password to ensure working email configuration and the resulting
// user notification. Also this might add some protection against security flaws and misuse
2023-02-05 16:34:48 +01:00
if let Err ( e ) = mail ::send_admin_reset_password ( & user . email , & user . name , & org . name ) . await {
2023-03-29 15:43:58 +02:00
err! ( format! ( " Error sending user reset password email: {e:#?} " ) ) ;
2023-01-25 08:06:21 +01:00
}
2024-06-23 21:31:02 +02:00
let reset_request = data . into_inner ( ) ;
2023-01-25 08:06:21 +01:00
2023-03-29 15:43:58 +02:00
let mut user = user ;
2024-06-23 21:31:02 +02:00
user . set_password ( reset_request . new_master_password_hash . as_str ( ) , Some ( reset_request . key ) , true , None ) ;
2023-01-25 08:06:21 +01:00
user . save ( & mut conn ) . await ? ;
2023-06-16 23:34:16 +02:00
nt . send_logout ( & user , None ) . await ;
2023-01-25 08:06:21 +01:00
log_event (
EventType ::OrganizationUserAdminResetPassword as i32 ,
2025-01-09 18:37:23 +01:00
& member_id ,
& org_id ,
2024-01-01 19:41:40 +01:00
& headers . user . uuid ,
2023-01-25 08:06:21 +01:00
headers . device . atype ,
2023-03-09 16:31:28 +01:00
& headers . ip . ip ,
2023-01-25 08:06:21 +01:00
& mut conn ,
)
. await ;
Ok ( ( ) )
}
2025-01-09 18:37:23 +01:00
#[ get( " /organizations/<org_id>/users/<member_id>/reset-password-details " ) ]
2023-01-25 08:06:21 +01:00
async fn get_reset_password_details (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2023-02-04 13:29:57 +01:00
headers : AdminHeaders ,
2023-01-25 08:06:21 +01:00
mut conn : DbConn ,
) -> JsonResult {
2025-01-25 01:32:09 +01:00
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2025-01-09 18:37:23 +01:00
let Some ( org ) = Organization ::find_by_uuid ( & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Required organization not found " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
let Some ( member ) = Membership ::find_by_uuid_and_org ( & member_id , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User to reset isn't member of required organization " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
let Some ( user ) = User ::find_by_uuid ( & member . user_uuid , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User not found " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
check_reset_password_applicable_and_permissions ( & org_id , & member_id , & headers , & mut conn ) . await ? ;
2023-02-04 13:29:57 +01:00
2023-03-29 15:43:58 +02:00
// https://github.com/bitwarden/server/blob/3b50ccb9f804efaacdc46bed5b60e5b28eddefcf/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs#L111
2023-01-25 08:06:21 +01:00
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" object " : " organizationUserResetPasswordDetails " ,
" kdf " :user . client_kdf_type ,
" kdfIterations " :user . client_kdf_iter ,
" kdfMemory " :user . client_kdf_memory ,
" kdfParallelism " :user . client_kdf_parallelism ,
2025-01-09 18:37:23 +01:00
" resetPasswordKey " :member . reset_password_key ,
2024-06-23 21:31:02 +02:00
" encryptedPrivateKey " :org . private_key ,
2023-01-25 08:06:21 +01:00
} ) ) )
}
2023-02-04 13:29:57 +01:00
async fn check_reset_password_applicable_and_permissions (
2025-01-09 18:37:23 +01:00
org_id : & OrganizationId ,
member_id : & MembershipId ,
2023-02-04 13:29:57 +01:00
headers : & AdminHeaders ,
conn : & mut DbConn ,
2023-01-25 08:06:21 +01:00
) -> EmptyResult {
2023-02-04 13:29:57 +01:00
check_reset_password_applicable ( org_id , conn ) . await ? ;
2025-01-09 18:37:23 +01:00
let Some ( target_user ) = Membership ::find_by_uuid_and_org ( member_id , org_id , conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " Reset target user not found " )
2023-02-04 13:29:57 +01:00
} ;
// Resetting user must be higher/equal to user to reset
2025-01-09 18:37:23 +01:00
match headers . membership_type {
MembershipType ::Owner = > Ok ( ( ) ) ,
MembershipType ::Admin if target_user . atype < = MembershipType ::Admin = > Ok ( ( ) ) ,
2023-02-05 16:34:48 +01:00
_ = > err! ( " No permission to reset this user's password " ) ,
2023-02-04 13:29:57 +01:00
}
}
2025-01-09 18:37:23 +01:00
async fn check_reset_password_applicable ( org_id : & OrganizationId , conn : & mut DbConn ) -> EmptyResult {
2023-02-04 13:29:57 +01:00
if ! CONFIG . mail_enabled ( ) {
err! ( " Password reset is not supported on an email-disabled instance. " ) ;
}
2024-12-14 00:55:34 +01:00
let Some ( policy ) = OrgPolicy ::find_by_org_and_type ( org_id , OrgPolicyType ::ResetPassword , conn ) . await else {
err! ( " Policy not found " )
2023-01-25 08:06:21 +01:00
} ;
if ! policy . enabled {
err! ( " Reset password policy not enabled " ) ;
}
2023-02-04 13:29:57 +01:00
Ok ( ( ) )
}
2025-01-09 18:37:23 +01:00
#[ put( " /organizations/<org_id>/users/<member_id>/reset-password-enrollment " , data = " <data> " ) ]
2023-02-04 13:29:57 +01:00
async fn put_reset_password_enrollment (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
member_id : MembershipId ,
2023-02-04 13:29:57 +01:00
headers : Headers ,
2024-06-23 21:31:02 +02:00
data : Json < OrganizationUserResetPasswordEnrollmentRequest > ,
2023-02-04 13:29:57 +01:00
mut conn : DbConn ,
) -> EmptyResult {
2025-01-09 18:37:23 +01:00
let Some ( mut member ) = Membership ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await else {
2024-12-14 00:55:34 +01:00
err! ( " User to enroll isn't member of required organization " )
2023-01-25 08:06:21 +01:00
} ;
2025-01-09 18:37:23 +01:00
check_reset_password_applicable ( & org_id , & mut conn ) . await ? ;
2023-02-04 13:29:57 +01:00
2024-06-23 21:31:02 +02:00
let reset_request = data . into_inner ( ) ;
2023-01-25 08:06:21 +01:00
2024-06-23 21:31:02 +02:00
if reset_request . reset_password_key . is_none ( )
2025-01-09 18:37:23 +01:00
& & OrgPolicy ::org_is_reset_password_auto_enroll ( & org_id , & mut conn ) . await
2023-01-25 08:06:21 +01:00
{
2025-01-04 19:31:59 +01:00
err! ( " Reset password can't be withdrawn due to an enterprise policy " ) ;
2023-01-25 08:06:21 +01:00
}
2024-06-23 21:31:02 +02:00
if reset_request . reset_password_key . is_some ( ) {
2024-02-08 22:16:29 +01:00
PasswordOrOtpData {
2024-06-23 21:31:02 +02:00
master_password_hash : reset_request . master_password_hash ,
otp : reset_request . otp ,
2024-02-08 22:16:29 +01:00
}
. validate ( & headers . user , true , & mut conn )
. await ? ;
2023-07-04 18:57:49 +02:00
}
2025-01-09 18:37:23 +01:00
member . reset_password_key = reset_request . reset_password_key ;
member . save ( & mut conn ) . await ? ;
2023-01-25 08:06:21 +01:00
2025-01-09 18:37:23 +01:00
let log_id = if member . reset_password_key . is_some ( ) {
2023-01-25 08:06:21 +01:00
EventType ::OrganizationUserResetPasswordEnroll as i32
} else {
EventType ::OrganizationUserResetPasswordWithdraw as i32
} ;
2025-01-09 18:37:23 +01:00
log_event ( log_id , & member_id , & org_id , & headers . user . uuid , headers . device . atype , & headers . ip . ip , & mut conn ) . await ;
2023-01-25 08:06:21 +01:00
Ok ( ( ) )
}
2022-09-24 18:27:13 +02:00
// This is a new function active since the v2022.9.x clients.
// It combines the previous two calls done before.
2023-10-07 22:07:38 +03:00
// We call those two functions here and combine them ourselves.
2022-09-24 18:27:13 +02:00
//
// 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.
// Else the export will be just an empty JSON file.
#[ get( " /organizations/<org_id>/export " ) ]
2024-11-15 18:38:16 +01:00
async fn get_org_export (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2024-11-15 18:38:16 +01:00
headers : AdminHeaders ,
client_version : Option < ClientVersion > ,
mut conn : DbConn ,
2025-01-25 01:32:09 +01:00
) -> JsonResult {
if org_id ! = headers . org_id {
err! ( " Organization not found " , " Organization id's do not match " ) ;
}
2022-12-11 18:17:53 +01:00
// Since version v2023.1.0 the format of the export is different.
2022-11-07 17:13:34 +01:00
// Also, this endpoint was created since v2022.9.0.
2022-12-11 18:17:53 +01:00
// Therefore, we will check for any version smaller then v2023.1.0 and return a different response.
// If we can't determine the version, we will use the latest default v2023.1.0 and higher.
// https://github.com/bitwarden/server/blob/9ca93381ce416454734418c3a9f99ab49747f1b6/src/Api/Controllers/OrganizationExportController.cs#L44
2024-11-15 18:38:16 +01:00
let use_list_response_model = if let Some ( client_version ) = client_version {
let ver_match = semver ::VersionReq ::parse ( " <2023.1.0 " ) . unwrap ( ) ;
ver_match . matches ( & client_version . 0 )
2022-11-07 17:13:34 +01:00
} else {
false
} ;
2022-09-24 18:27:13 +02:00
// Also both main keys here need to be lowercase, else the export will fail.
2022-11-07 17:13:34 +01:00
if use_list_response_model {
2022-12-11 18:17:53 +01:00
// Backwards compatible pre v2023.1.0 response
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2022-11-07 17:13:34 +01:00
" collections " : {
2025-01-09 18:37:23 +01:00
" data " : convert_json_key_lcase_first ( _get_org_collections ( & org_id , & mut conn ) . await ) ,
2022-11-07 17:13:34 +01:00
" object " : " list " ,
" continuationToken " : null ,
} ,
" ciphers " : {
2025-01-09 18:37:23 +01:00
" data " : convert_json_key_lcase_first ( _get_org_details ( & org_id , & headers . host , & headers . user . uuid , & mut conn ) . await ) ,
2022-11-07 17:13:34 +01:00
" object " : " list " ,
" continuationToken " : null ,
}
2025-01-25 01:32:09 +01:00
} ) ) )
2022-11-07 17:13:34 +01:00
} else {
2022-12-11 18:17:53 +01:00
// v2023.1.0 and newer response
2025-01-25 01:32:09 +01:00
Ok ( Json ( json! ( {
2025-01-09 18:37:23 +01:00
" collections " : convert_json_key_lcase_first ( _get_org_collections ( & org_id , & mut conn ) . await ) ,
" ciphers " : convert_json_key_lcase_first ( _get_org_details ( & org_id , & headers . host , & headers . user . uuid , & mut conn ) . await ) ,
2025-01-25 01:32:09 +01:00
} ) ) )
2022-11-07 17:13:34 +01:00
}
2022-09-24 18:27:13 +02:00
}
2023-06-02 21:36:15 +02:00
async fn _api_key (
2025-01-09 18:37:23 +01:00
org_id : & OrganizationId ,
2024-06-23 21:31:02 +02:00
data : Json < PasswordOrOtpData > ,
2023-06-02 21:36:15 +02:00
rotate : bool ,
headers : AdminHeaders ,
2023-11-12 22:15:44 +01:00
mut conn : DbConn ,
2023-06-02 21:36:15 +02:00
) -> JsonResult {
2024-06-23 21:31:02 +02:00
let data : PasswordOrOtpData = data . into_inner ( ) ;
2023-06-02 21:36:15 +02:00
let user = headers . user ;
2023-11-12 22:15:44 +01:00
// Validate the admin users password/otp
data . validate ( & user , true , & mut conn ) . await ? ;
2023-06-02 21:36:15 +02:00
2023-06-09 22:50:44 +02:00
let org_api_key = match OrganizationApiKey ::find_by_org_uuid ( org_id , & conn ) . await {
2023-06-02 21:36:15 +02:00
Some ( mut org_api_key ) = > {
if rotate {
org_api_key . api_key = crate ::crypto ::generate_api_key ( ) ;
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
}
None = > {
let api_key = crate ::crypto ::generate_api_key ( ) ;
2025-01-09 18:37:23 +01:00
let new_org_api_key = OrganizationApiKey ::new ( org_id . clone ( ) , api_key ) ;
2023-06-02 21:36:15 +02:00
new_org_api_key . save ( & conn ) . await . expect ( " Error creating organization API Key " ) ;
new_org_api_key
}
} ;
Ok ( Json ( json! ( {
2024-06-23 21:31:02 +02:00
" apiKey " : org_api_key . api_key ,
" revisionDate " : crate ::util ::format_date ( & org_api_key . revision_date ) ,
" object " : " apiKey " ,
2023-06-02 21:36:15 +02:00
} ) ) )
}
#[ post( " /organizations/<org_id>/api-key " , data = " <data> " ) ]
2025-01-09 18:37:23 +01:00
async fn api_key (
org_id : OrganizationId ,
data : Json < PasswordOrOtpData > ,
headers : AdminHeaders ,
conn : DbConn ,
) -> JsonResult {
_api_key ( & org_id , data , false , headers , conn ) . await
2023-06-02 21:36:15 +02:00
}
#[ post( " /organizations/<org_id>/rotate-api-key " , data = " <data> " ) ]
async fn rotate_api_key (
2025-01-09 18:37:23 +01:00
org_id : OrganizationId ,
2024-06-23 21:31:02 +02:00
data : Json < PasswordOrOtpData > ,
2023-06-02 21:36:15 +02:00
headers : AdminHeaders ,
conn : DbConn ,
) -> JsonResult {
2025-01-09 18:37:23 +01:00
_api_key ( & org_id , data , true , headers , conn ) . await
2023-06-02 21:36:15 +02:00
}