2020-07-14 16:00:09 +00:00
use num_traits ::FromPrimitive ;
2021-11-07 17:53:39 +00:00
use rocket ::serde ::json ::Json ;
use rocket ::Route ;
2018-10-10 18:40:39 +00:00
use serde_json ::Value ;
2020-07-14 16:00:09 +00:00
use crate ::{
2022-05-04 19:13:05 +00:00
api ::{
2022-11-20 18:15:45 +00:00
core ::{ log_event , CipherSyncData , CipherSyncType } ,
2022-10-20 13:31:53 +00:00
ApiResult , EmptyResult , JsonResult , JsonUpcase , JsonUpcaseVec , JsonVec , Notify , NumberOrString , PasswordData ,
UpdateType ,
2022-05-04 19:13:05 +00:00
} ,
2022-11-20 18:15:45 +00:00
auth ::{ decode_invite , AdminHeaders , ClientIp , Headers , ManagerHeaders , ManagerHeadersLoose , OwnerHeaders } ,
2020-07-14 16:00:09 +00:00
db ::{ models ::* , DbConn } ,
2022-05-20 21:39:47 +00:00
error ::Error ,
2022-09-24 16:27:13 +00:00
mail ,
util ::convert_json_key_lcase_first ,
CONFIG ,
2019-01-25 16:43:51 +00:00
} ;
2018-10-10 18:40:39 +00:00
pub fn routes ( ) -> Vec < Route > {
routes! [
get_organization ,
create_organization ,
delete_organization ,
post_delete_organization ,
leave_organization ,
get_user_collections ,
get_org_collections ,
get_org_collection_detail ,
get_collection_users ,
2019-01-25 16:43:51 +00:00
put_collection_users ,
2018-10-10 18:40:39 +00:00
put_organization ,
post_organization ,
post_organization_collections ,
delete_organization_collection_user ,
post_organization_collection_delete_user ,
post_organization_collection_update ,
put_organization_collection_update ,
delete_organization_collection ,
post_organization_collection_delete ,
get_org_details ,
get_org_users ,
send_invite ,
2018-12-30 04:24:38 +00:00
reinvite_user ,
2021-09-18 12:22:14 +00:00
bulk_reinvite_user ,
2018-10-10 18:40:39 +00:00
confirm_invite ,
2021-09-18 12:22:14 +00:00
bulk_confirm_invite ,
2018-12-15 02:56:00 +00:00
accept_invite ,
2018-10-10 18:40:39 +00:00
get_user ,
edit_user ,
put_organization_user ,
delete_user ,
2021-09-18 12:22:14 +00:00
bulk_delete_user ,
2018-10-10 18:40:39 +00:00
post_delete_user ,
post_org_import ,
2020-03-14 12:22:30 +00:00
list_policies ,
2020-03-20 09:51:17 +00:00
list_policies_token ,
2020-03-14 12:22:30 +00:00
get_policy ,
put_policy ,
2021-01-31 20:46:37 +00:00
get_organization_tax ,
2020-09-14 06:34:17 +00:00
get_plans ,
2021-01-31 20:46:37 +00:00
get_plans_tax_rates ,
2021-02-06 17:22:39 +00: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 21:02:56 +00:00
post_org_keys ,
2021-09-18 12:22:14 +00:00
bulk_public_keys ,
2022-08-20 14:42:36 +00:00
deactivate_organization_user ,
bulk_deactivate_organization_user ,
2022-09-12 14:08:36 +00:00
revoke_organization_user ,
bulk_revoke_organization_user ,
2022-08-20 14:42:36 +00:00
activate_organization_user ,
2022-09-12 14:08:36 +00:00
bulk_activate_organization_user ,
restore_organization_user ,
2022-09-24 16:27:13 +00:00
bulk_restore_organization_user ,
2022-10-20 13:31:53 +00:00
get_groups ,
post_groups ,
get_group ,
put_group ,
post_group ,
get_group_details ,
delete_group ,
post_delete_group ,
get_group_users ,
put_group_users ,
get_user_groups ,
post_user_groups ,
put_user_groups ,
delete_group_user ,
post_delete_group_user ,
2022-09-24 16:27:13 +00:00
get_org_export
2018-10-10 18:40:39 +00:00
]
}
2018-04-24 20:01:55 +00:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct OrgData {
2018-05-31 22:18:50 +00:00
BillingEmail : String ,
CollectionName : String ,
Key : String ,
Name : 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 21:02:56 +00:00
Keys : Option < OrgKeyData > ,
2018-05-31 22:18:50 +00:00
#[ serde(rename = " PlanType " ) ]
2018-07-21 15:27:00 +00:00
_PlanType : NumberOrString , // Ignored, always use the same plan
2018-04-24 20:01:55 +00:00
}
2018-04-20 16:35:11 +00:00
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrganizationUpdateData {
2018-05-31 22:18:50 +00:00
BillingEmail : String ,
Name : String ,
2018-04-20 16:35:11 +00:00
}
2022-10-20 13:31:53 +00:00
#[ derive(Deserialize) ]
2018-04-20 16:35:11 +00:00
#[ allow(non_snake_case) ]
struct NewCollectionData {
2018-05-31 22:18:50 +00:00
Name : String ,
2022-10-20 13:31:53 +00:00
Groups : Vec < NewCollectionGroupData > ,
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct NewCollectionGroupData {
HidePasswords : bool ,
Id : String ,
ReadOnly : bool ,
2018-04-20 16:35:11 +00: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 21:02:56 +00:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct OrgKeyData {
EncryptedPrivateKey : String ,
PublicKey : String ,
}
2021-09-18 12:22:14 +00:00
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrgBulkIds {
Ids : Vec < String > ,
}
2018-02-17 21:30:19 +00:00
#[ post( " /organizations " , data = " <data> " ) ]
2022-05-20 21:39:47 +00:00
async fn create_organization ( headers : Headers , data : JsonUpcase < OrgData > , mut conn : DbConn ) -> JsonResult {
2020-08-06 05:35:29 +00:00
if ! CONFIG . is_org_creation_allowed ( & headers . user . email ) {
err! ( " User not allowed to create organizations " )
}
2022-05-20 21:39:47 +00:00
if OrgPolicy ::is_applicable_to_user ( & headers . user . uuid , OrgPolicyType ::SingleOrg , None , & mut conn ) . await {
2021-09-24 15:55:49 +00: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-06 05:35:29 +00:00
2018-05-31 22:18:50 +00:00
let data : OrgData = data . into_inner ( ) . data ;
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 21:02:56 +00:00
let ( private_key , public_key ) = if data . Keys . is_some ( ) {
let keys : OrgKeyData = data . Keys . unwrap ( ) ;
( Some ( keys . EncryptedPrivateKey ) , Some ( keys . PublicKey ) )
} else {
( None , None )
} ;
2018-02-17 21:30:19 +00: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 21:02:56 +00:00
let org = Organization ::new ( data . Name , data . BillingEmail , private_key , public_key ) ;
2019-11-02 16:39:01 +00:00
let mut user_org = UserOrganization ::new ( headers . user . uuid , org . uuid . clone ( ) ) ;
2019-02-22 19:25:50 +00:00
let collection = Collection ::new ( org . uuid . clone ( ) , data . CollectionName ) ;
2018-02-17 21:30:19 +00:00
2019-05-20 19:24:29 +00:00
user_org . akey = data . Key ;
2018-04-24 20:01:55 +00:00
user_org . access_all = true ;
2019-05-20 19:24:29 +00:00
user_org . atype = UserOrgType ::Owner as i32 ;
2018-04-24 20:01:55 +00:00
user_org . status = UserOrgStatus ::Confirmed as i32 ;
2022-05-20 21:39:47 +00:00
org . save ( & mut conn ) . await ? ;
user_org . save ( & mut conn ) . await ? ;
collection . save ( & mut conn ) . await ? ;
2018-04-24 20:01:55 +00:00
Ok ( Json ( org . to_json ( ) ) )
2018-02-17 21:30:19 +00:00
}
2018-08-13 15:45:30 +00:00
#[ delete( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn delete_organization (
2018-12-30 22:34:31 +00:00
org_id : String ,
data : JsonUpcase < PasswordData > ,
headers : OwnerHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2018-05-31 22:18:50 +00:00
let data : PasswordData = data . into_inner ( ) . data ;
let password_hash = data . MasterPasswordHash ;
2018-04-24 22:34:40 +00:00
2018-05-18 15:52:51 +00:00
if ! headers . user . check_valid_password ( & password_hash ) {
err! ( " Invalid password " )
}
2022-05-20 21:39:47 +00:00
match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-05-18 15:52:51 +00:00
None = > err! ( " Organization not found " ) ,
2022-05-20 21:39:47 +00:00
Some ( org ) = > org . delete ( & mut conn ) . await ,
2018-05-18 15:52:51 +00:00
}
2018-04-24 22:34:40 +00:00
}
2018-08-13 15:45:30 +00:00
#[ post( " /organizations/<org_id>/delete " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_delete_organization (
2018-12-30 22:34:31 +00:00
org_id : String ,
data : JsonUpcase < PasswordData > ,
headers : OwnerHeaders ,
conn : DbConn ,
) -> EmptyResult {
2021-11-16 16:07:55 +00:00
delete_organization ( org_id , data , headers , conn ) . await
2018-08-13 15:45:30 +00:00
}
2018-07-11 14:30:03 +00:00
#[ post( " /organizations/<org_id>/leave " ) ]
2022-11-20 18:15:45 +00:00
async fn leave_organization ( org_id : String , headers : Headers , mut conn : DbConn , ip : ClientIp ) -> EmptyResult {
2022-05-20 21:39:47 +00:00
match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
2018-07-11 14:30:03 +00:00
None = > err! ( " User not part of organization " ) ,
Some ( user_org ) = > {
2022-08-20 14:42:36 +00:00
if user_org . atype = = UserOrgType ::Owner
2022-05-20 21:39:47 +00:00
& & UserOrganization ::count_confirmed_by_org_and_type ( & org_id , UserOrgType ::Owner , & mut conn ) . await < = 1
2022-08-20 14:42:36 +00:00
{
err! ( " The last owner can't leave " )
2018-07-11 14:30:03 +00:00
}
2018-12-30 22:34:31 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& user_org . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
user_org . delete ( & mut conn ) . await
2018-07-11 14:30:03 +00:00
}
}
}
2018-04-20 16:35:11 +00:00
#[ get( " /organizations/<org_id> " ) ]
2022-05-20 21:39:47 +00:00
async fn get_organization ( org_id : String , _headers : OwnerHeaders , mut conn : DbConn ) -> JsonResult {
match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
Some ( organization ) = > Ok ( Json ( organization . to_json ( ) ) ) ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 16:35:11 +00:00
}
}
2018-08-21 12:25:52 +00:00
#[ put( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn put_organization (
2018-12-30 22:34:31 +00:00
org_id : String ,
headers : OwnerHeaders ,
data : JsonUpcase < OrganizationUpdateData > ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> JsonResult {
2022-11-20 18:15:45 +00:00
post_organization ( org_id , headers , data , conn , ip ) . await
2018-08-21 12:25:52 +00:00
}
2018-04-20 16:35:11 +00:00
#[ post( " /organizations/<org_id> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_organization (
2018-12-30 22:34:31 +00:00
org_id : String ,
2022-11-20 18:15:45 +00:00
headers : OwnerHeaders ,
2018-12-30 22:34:31 +00:00
data : JsonUpcase < OrganizationUpdateData > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> JsonResult {
2018-05-31 22:18:50 +00:00
let data : OrganizationUpdateData = data . into_inner ( ) . data ;
2018-04-20 16:35:11 +00:00
2022-05-20 21:39:47 +00:00
let mut org = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
Some ( organization ) = > organization ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 16:35:11 +00:00
} ;
2018-05-31 22:18:50 +00:00
org . name = data . Name ;
org . billing_email = data . BillingEmail ;
2018-04-20 16:35:11 +00:00
2022-05-20 21:39:47 +00:00
org . save ( & mut conn ) . await ? ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUpdated as i32 ,
& org_id ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2018-12-19 20:52:53 +00:00
Ok ( Json ( org . to_json ( ) ) )
2018-04-20 16:35:11 +00:00
}
2018-02-17 21:30:19 +00:00
// GET /api/collections?writeOnly=false
#[ get( " /collections " ) ]
2022-05-20 21:39:47 +00:00
async fn get_user_collections ( headers : Headers , mut conn : DbConn ) -> Json < Value > {
2021-03-27 15:07:26 +00:00
Json ( json! ( {
2018-04-20 16:35:11 +00:00
" Data " :
2022-05-20 21:39:47 +00:00
Collection ::find_by_user_uuid ( headers . user . uuid . clone ( ) , & mut conn ) . await
2018-04-20 16:35:11 +00:00
. iter ( )
2018-09-13 13:16:24 +00:00
. map ( Collection ::to_json )
. collect ::< Value > ( ) ,
2018-10-01 16:02:58 +00:00
" Object " : " list " ,
" ContinuationToken " : null ,
2021-03-27 15:07:26 +00:00
} ) )
2018-02-17 21:30:19 +00:00
}
#[ get( " /organizations/<org_id>/collections " ) ]
2022-05-20 21:39:47 +00:00
async fn get_org_collections ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> Json < Value > {
2022-11-07 16:13:34 +00:00
Json ( json! ( {
" Data " : _get_org_collections ( & org_id , & mut conn ) . await ,
" Object " : " list " ,
" ContinuationToken " : null ,
} ) )
2022-09-24 16:27:13 +00:00
}
2022-05-20 21:39:47 +00:00
async fn _get_org_collections ( org_id : & str , conn : & mut DbConn ) -> Value {
2022-11-07 16:13:34 +00:00
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
2018-02-17 21:30:19 +00:00
}
2018-04-20 16:35:11 +00:00
#[ post( " /organizations/<org_id>/collections " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_organization_collections (
2018-12-30 22:34:31 +00:00
org_id : String ,
2020-12-02 21:50:51 +00:00
headers : ManagerHeadersLoose ,
2018-12-30 22:34:31 +00:00
data : JsonUpcase < NewCollectionData > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> JsonResult {
2018-05-31 22:18:50 +00:00
let data : NewCollectionData = data . into_inner ( ) . data ;
2018-04-20 16:35:11 +00:00
2022-05-20 21:39:47 +00:00
let org = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
Some ( organization ) = > organization ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 16:35:11 +00:00
} ;
2020-12-02 21:50:51 +00:00
// Get the user_organization record so that we can check if the user has access to all collections.
2022-05-20 21:39:47 +00:00
let user_org = match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
2020-12-02 21:50:51 +00:00
Some ( u ) = > u ,
None = > err! ( " User is not part of organization " ) ,
} ;
2019-11-02 16:39:01 +00:00
let collection = Collection ::new ( org . uuid , data . Name ) ;
2022-05-20 21:39:47 +00:00
collection . save ( & mut conn ) . await ? ;
2018-05-04 17:25:50 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::CollectionCreated as i32 ,
& collection . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-10-20 13:31:53 +00:00
for group in data . Groups {
CollectionGroup ::new ( collection . uuid . clone ( ) , group . Id , group . ReadOnly , group . HidePasswords )
2022-05-20 21:39:47 +00:00
. save ( & mut conn )
2022-10-20 13:31:53 +00:00
. await ? ;
}
2020-12-02 21:50:51 +00:00
// If the user doesn't have access to all collections, only in case of a Manger,
// then we need to save the creating user uuid (Manager) to the users_collection table.
// Else the user will not have access to his own created collection.
if ! user_org . access_all {
2022-05-20 21:39:47 +00:00
CollectionUser ::save ( & headers . user . uuid , & collection . uuid , false , false , & mut conn ) . await ? ;
2020-12-02 21:50:51 +00:00
}
2018-04-20 16:35:11 +00:00
Ok ( Json ( collection . to_json ( ) ) )
}
2018-08-13 15:45:30 +00:00
#[ put( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn put_organization_collection_update (
2018-12-30 22:34:31 +00:00
org_id : String ,
col_id : String ,
2020-12-02 21:50:51 +00:00
headers : ManagerHeaders ,
2018-12-30 22:34:31 +00:00
data : JsonUpcase < NewCollectionData > ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> JsonResult {
2022-11-20 18:15:45 +00:00
post_organization_collection_update ( org_id , col_id , headers , data , conn , ip ) . await
2018-08-13 15:45:30 +00:00
}
2018-04-20 16:35:11 +00:00
#[ post( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_organization_collection_update (
2018-12-30 22:34:31 +00:00
org_id : String ,
col_id : String ,
2022-11-20 18:15:45 +00:00
headers : ManagerHeaders ,
2018-12-30 22:34:31 +00:00
data : JsonUpcase < NewCollectionData > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> JsonResult {
2018-05-31 22:18:50 +00:00
let data : NewCollectionData = data . into_inner ( ) . data ;
2018-04-20 16:35:11 +00:00
2022-05-20 21:39:47 +00:00
let org = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
Some ( organization ) = > organization ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 16:35:11 +00:00
} ;
2022-05-20 21:39:47 +00:00
let mut collection = match Collection ::find_by_uuid ( & col_id , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
Some ( collection ) = > collection ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Collection not found " ) ,
2018-04-20 16:35:11 +00:00
} ;
2018-05-30 20:30:45 +00:00
if collection . org_uuid ! = org . uuid {
err! ( " Collection is not owned by organization " ) ;
}
2019-11-02 16:39:01 +00:00
collection . name = data . Name ;
2022-05-20 21:39:47 +00:00
collection . save ( & mut conn ) . await ? ;
2018-04-20 16:35:11 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::CollectionUpdated as i32 ,
& collection . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
CollectionGroup ::delete_all_by_collection ( & col_id , & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
for group in data . Groups {
2022-05-20 21:39:47 +00:00
CollectionGroup ::new ( col_id . clone ( ) , group . Id , group . ReadOnly , group . HidePasswords ) . save ( & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
}
2018-04-20 16:35:11 +00:00
Ok ( Json ( collection . to_json ( ) ) )
}
2018-08-13 15:45:30 +00:00
#[ delete( " /organizations/<org_id>/collections/<col_id>/user/<org_user_id> " ) ]
2021-11-16 16:07:55 +00:00
async fn delete_organization_collection_user (
2018-12-30 22:34:31 +00:00
org_id : String ,
col_id : String ,
org_user_id : String ,
_headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2022-05-20 21:39:47 +00:00
let collection = match Collection ::find_by_uuid ( & col_id , & mut conn ) . await {
2018-05-30 15:01:56 +00:00
None = > err! ( " Collection not found " ) ,
2018-12-30 22:34:31 +00:00
Some ( collection ) = > {
if collection . org_uuid = = org_id {
collection
} else {
err! ( " Collection and Organization id do not match " )
}
2018-05-30 15:01:56 +00:00
}
} ;
2022-05-20 21:39:47 +00:00
match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & mut conn ) . await {
2018-05-30 15:01:56 +00:00
None = > err! ( " User not found in organization " ) ,
Some ( user_org ) = > {
2022-05-20 21:39:47 +00:00
match CollectionUser ::find_by_collection_and_user ( & collection . uuid , & user_org . user_uuid , & mut conn ) . await {
2018-05-30 15:01:56 +00:00
None = > err! ( " User not assigned to collection " ) ,
2022-05-20 21:39:47 +00:00
Some ( col_user ) = > col_user . delete ( & mut conn ) . await ,
2018-05-29 15:01:38 +00:00
}
}
}
}
2018-08-13 15:45:30 +00:00
#[ post( " /organizations/<org_id>/collections/<col_id>/delete-user/<org_user_id> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_organization_collection_delete_user (
2018-12-30 22:34:31 +00:00
org_id : String ,
col_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2021-11-16 16:07:55 +00:00
delete_organization_collection_user ( org_id , col_id , org_user_id , headers , conn ) . await
2018-05-16 22:05:50 +00:00
}
2018-08-13 15:45:30 +00:00
#[ delete( " /organizations/<org_id>/collections/<col_id> " ) ]
2021-11-16 16:07:55 +00:00
async fn delete_organization_collection (
2021-03-31 20:18:35 +00:00
org_id : String ,
col_id : String ,
2022-11-20 18:15:45 +00:00
headers : ManagerHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2021-03-31 20:18:35 +00:00
) -> EmptyResult {
2022-05-20 21:39:47 +00:00
match Collection ::find_by_uuid ( & col_id , & mut conn ) . await {
2018-05-30 16:12:18 +00:00
None = > err! ( " Collection not found " ) ,
2018-12-30 22:34:31 +00:00
Some ( collection ) = > {
if collection . org_uuid = = org_id {
2022-11-20 18:15:45 +00:00
log_event (
EventType ::CollectionDeleted as i32 ,
& collection . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
collection . delete ( & mut conn ) . await
2018-12-30 22:34:31 +00:00
} else {
err! ( " Collection and Organization id do not match " )
}
2018-05-16 22:05:50 +00:00
}
}
}
2018-08-13 15:45:30 +00:00
#[ derive(Deserialize, Debug) ]
2021-09-22 19:39:31 +00:00
#[ allow(non_snake_case, dead_code) ]
2018-08-13 15:45:30 +00:00
struct DeleteCollectionData {
Id : String ,
OrgId : String ,
}
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " , data = " <_data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_organization_collection_delete (
2018-12-30 22:34:31 +00:00
org_id : String ,
col_id : String ,
2020-12-02 21:50:51 +00:00
headers : ManagerHeaders ,
2018-12-30 22:34:31 +00:00
_data : JsonUpcase < DeleteCollectionData > ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
delete_organization_collection ( org_id , col_id , headers , conn , ip ) . await
2018-08-13 15:45:30 +00:00
}
2018-04-20 16:35:11 +00:00
#[ get( " /organizations/<org_id>/collections/<coll_id>/details " ) ]
2021-11-16 16:07:55 +00:00
async fn get_org_collection_detail (
org_id : String ,
coll_id : String ,
headers : ManagerHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2021-11-16 16:07:55 +00:00
) -> JsonResult {
2022-05-20 21:39:47 +00:00
match Collection ::find_by_uuid_and_user ( & coll_id , headers . user . uuid . clone ( ) , & mut conn ) . await {
2018-04-20 16:35:11 +00:00
None = > err! ( " Collection not found " ) ,
2018-05-30 20:30:45 +00:00
Some ( collection ) = > {
if collection . org_uuid ! = org_id {
err! ( " Collection is not owned by organization " )
}
2022-05-20 21:39:47 +00:00
let groups : Vec < Value > = CollectionGroup ::find_by_collection ( & collection . uuid , & mut conn )
2022-10-20 13:31:53 +00:00
. await
. iter ( )
. map ( | collection_group | {
SelectionReadOnly ::to_collection_group_details_read_only ( collection_group ) . to_json ( )
} )
. collect ( ) ;
let mut json_object = collection . to_json ( ) ;
json_object [ " Groups " ] = json! ( groups ) ;
json_object [ " Object " ] = json! ( " collectionGroupDetails " ) ;
Ok ( Json ( json_object ) )
2018-05-30 20:30:45 +00:00
}
2018-04-20 16:35:11 +00:00
}
}
2018-04-24 22:34:40 +00:00
#[ get( " /organizations/<org_id>/collections/<coll_id>/users " ) ]
2022-05-20 21:39:47 +00:00
async fn get_collection_users (
org_id : String ,
coll_id : String ,
_headers : ManagerHeaders ,
mut conn : DbConn ,
) -> JsonResult {
2018-04-24 22:34:40 +00:00
// Get org and collection, check that collection is from org
2022-05-20 21:39:47 +00:00
let collection = match Collection ::find_by_uuid_and_org ( & coll_id , & org_id , & mut conn ) . await {
2018-05-29 15:01:38 +00:00
None = > err! ( " Collection not found in Organization " ) ,
2018-12-30 22:34:31 +00:00
Some ( collection ) = > collection ,
2018-05-29 15:01:38 +00:00
} ;
2018-04-24 22:34:40 +00:00
2022-05-20 21:39:47 +00:00
let mut user_list = Vec ::new ( ) ;
for col_user in CollectionUser ::find_by_collection ( & collection . uuid , & mut conn ) . await {
user_list . push (
UserOrganization ::find_by_user_and_org ( & col_user . user_uuid , & org_id , & mut conn )
2021-11-16 16:07:55 +00:00
. await
2018-12-30 22:34:31 +00:00
. unwrap ( )
2022-05-20 21:39:47 +00:00
. to_json_user_access_restrictions ( & col_user ) ,
) ;
}
2018-04-24 22:34:40 +00:00
2019-01-25 14:18:06 +00:00
Ok ( Json ( json! ( user_list ) ) )
2018-04-24 22:34:40 +00:00
}
2019-01-25 16:43:51 +00:00
#[ put( " /organizations/<org_id>/collections/<coll_id>/users " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn put_collection_users (
2019-01-25 16:43:51 +00:00
org_id : String ,
coll_id : String ,
data : JsonUpcaseVec < CollectionData > ,
2020-12-02 21:50:51 +00:00
_headers : ManagerHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2019-01-25 16:43:51 +00:00
) -> EmptyResult {
// Get org and collection, check that collection is from org
2022-05-20 21:39:47 +00:00
if Collection ::find_by_uuid_and_org ( & coll_id , & org_id , & mut conn ) . await . is_none ( ) {
2019-01-25 16:43:51 +00:00
err! ( " Collection not found in Organization " )
}
// Delete all the user-collections
2022-05-20 21:39:47 +00:00
CollectionUser ::delete_all_by_collection ( & coll_id , & mut conn ) . await ? ;
2019-01-25 16:43:51 +00:00
// And then add all the received ones (except if the user has access_all)
for d in data . iter ( ) . map ( | d | & d . data ) {
2022-05-20 21:39:47 +00:00
let user = match UserOrganization ::find_by_uuid ( & d . Id , & mut conn ) . await {
2019-01-25 16:43:51 +00:00
Some ( u ) = > u ,
None = > err! ( " User is not part of organization " ) ,
} ;
if user . access_all {
continue ;
}
2022-05-20 21:39:47 +00:00
CollectionUser ::save ( & user . user_uuid , & coll_id , d . ReadOnly , d . HidePasswords , & mut conn ) . await ? ;
2019-01-25 16:43:51 +00:00
}
Ok ( ( ) )
}
2018-04-24 20:01:55 +00:00
#[ derive(FromForm) ]
struct OrgIdData {
2021-11-07 17:53:39 +00:00
#[ field(name = " organizationId " ) ]
2018-12-30 22:34:31 +00:00
organization_id : String ,
2018-04-24 20:01:55 +00:00
}
2018-10-10 18:40:39 +00:00
#[ get( " /ciphers/organization-details?<data..> " ) ]
2022-05-20 21:39:47 +00:00
async fn get_org_details ( data : OrgIdData , headers : Headers , mut conn : DbConn ) -> Json < Value > {
2022-11-07 16:13:34 +00:00
Json ( json! ( {
" Data " : _get_org_details ( & data . organization_id , & headers . host , & headers . user . uuid , & mut conn ) . await ,
" Object " : " list " ,
" ContinuationToken " : null ,
} ) )
2022-09-24 16:27:13 +00:00
}
2022-05-20 21:39:47 +00:00
async fn _get_org_details ( org_id : & str , host : & str , user_uuid : & str , conn : & mut DbConn ) -> Value {
2022-09-24 16:27:13 +00:00
let ciphers = Cipher ::find_by_org ( org_id , conn ) . await ;
let cipher_sync_data = CipherSyncData ::new ( user_uuid , & ciphers , CipherSyncType ::Organization , conn ) . await ;
2022-05-04 19:13:05 +00:00
2022-05-20 21:39:47 +00:00
let mut ciphers_json = Vec ::new ( ) ;
for c in ciphers {
ciphers_json . push ( c . to_json ( host , user_uuid , Some ( & cipher_sync_data ) , conn ) . await ) ;
}
2022-11-07 16:13:34 +00:00
json! ( ciphers_json )
2018-04-24 20:01:55 +00:00
}
#[ get( " /organizations/<org_id>/users " ) ]
2022-05-20 21:39:47 +00:00
async fn get_org_users ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> Json < Value > {
let mut users_json = Vec ::new ( ) ;
for u in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await {
users_json . push ( u . to_json_user_details ( & mut conn ) . await ) ;
}
2018-04-24 20:01:55 +00:00
2021-03-27 15:07:26 +00:00
Json ( json! ( {
2018-04-24 20:01:55 +00:00
" Data " : users_json ,
2018-10-01 16:02:58 +00:00
" Object " : " list " ,
" ContinuationToken " : null ,
2021-03-27 15:07:26 +00:00
} ) )
2018-04-24 20:01:55 +00:00
}
2018-02-17 21:30:19 +00: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 21:02:56 +00:00
#[ post( " /organizations/<org_id>/keys " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_org_keys (
org_id : String ,
data : JsonUpcase < OrgKeyData > ,
_headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2021-11-16 16:07:55 +00:00
) -> JsonResult {
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 21:02:56 +00:00
let data : OrgKeyData = data . into_inner ( ) . data ;
2022-05-20 21:39:47 +00: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 21:02:56 +00: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 " ) ,
} ;
org . private_key = Some ( data . EncryptedPrivateKey ) ;
org . public_key = Some ( data . PublicKey ) ;
2022-05-20 21:39:47 +00: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 21:02:56 +00:00
Ok ( Json ( json! ( {
" Object " : " organizationKeys " ,
" PublicKey " : org . public_key ,
" PrivateKey " : org . private_key ,
} ) ) )
}
2018-04-24 20:01:55 +00:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
2018-04-24 22:34:40 +00:00
struct CollectionData {
2018-06-13 12:25:50 +00:00
Id : String ,
ReadOnly : bool ,
2020-07-03 04:51:20 +00:00
HidePasswords : bool ,
2018-04-24 20:01:55 +00:00
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct InviteData {
2018-05-31 22:18:50 +00:00
Emails : Vec < String > ,
Type : NumberOrString ,
2019-02-08 18:12:08 +00:00
Collections : Option < Vec < CollectionData > > ,
2018-05-31 22:18:50 +00:00
AccessAll : Option < bool > ,
2018-04-24 20:01:55 +00:00
}
#[ post( " /organizations/<org_id>/users/invite " , data = " <data> " ) ]
2022-05-20 21:39:47 +00:00
async fn send_invite (
org_id : String ,
data : JsonUpcase < InviteData > ,
headers : AdminHeaders ,
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-05-20 21:39:47 +00:00
) -> EmptyResult {
2018-05-31 22:18:50 +00:00
let data : InviteData = data . into_inner ( ) . data ;
2018-04-24 20:01:55 +00:00
2018-06-11 13:44:37 +00:00
let new_type = match UserOrgType ::from_str ( & data . Type . into_string ( ) ) {
2018-04-24 22:34:40 +00:00
Some ( new_type ) = > new_type as i32 ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Invalid type " ) ,
2018-04-24 22:34:40 +00:00
} ;
2018-12-30 22:34:31 +00:00
if new_type ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-11-12 17:13:25 +00:00
err! ( " Only Owners can invite Managers, Admins or Owners " )
2018-04-24 21:04:10 +00:00
}
2018-04-24 20:01:55 +00:00
2018-09-10 13:51:40 +00:00
for email in data . Emails . iter ( ) {
2021-09-09 11:50:18 +00:00
let email = email . to_lowercase ( ) ;
2022-09-27 21:19:35 +00:00
let mut user_org_status = UserOrgStatus ::Invited as i32 ;
2022-05-20 21:39:47 +00:00
let user = match User ::find_by_mail ( & email , & mut conn ) . await {
2018-12-30 22:34:31 +00:00
None = > {
2019-01-25 17:23:51 +00:00
if ! CONFIG . invitations_allowed ( ) {
2020-04-09 08:51:05 +00:00
err! ( format! ( " User does not exist: {} " , email ) )
}
2021-09-09 11:50:18 +00:00
if ! CONFIG . is_email_domain_allowed ( & email ) {
2020-04-09 08:51:05 +00:00
err! ( " Email domain not eligible for invitations " )
2019-01-08 14:11:16 +00:00
}
2019-02-02 00:09:21 +00:00
if ! CONFIG . mail_enabled ( ) {
2022-11-26 18:07:28 +00:00
let invitation = Invitation ::new ( & email ) ;
2022-05-20 21:39:47 +00:00
invitation . save ( & mut conn ) . await ? ;
2018-12-30 22:34:31 +00:00
}
2019-01-08 14:11:16 +00:00
let mut user = User ::new ( email . clone ( ) ) ;
2022-05-20 21:39:47 +00:00
user . save ( & mut conn ) . await ? ;
2019-01-08 14:11:16 +00:00
user
2018-12-30 22:34:31 +00:00
}
Some ( user ) = > {
2022-05-20 21:39:47 +00:00
if UserOrganization ::find_by_user_and_org ( & user . uuid , & org_id , & mut conn ) . await . is_some ( ) {
2018-12-30 22:34:31 +00:00
err! ( format! ( " User already in organization: {} " , email ) )
} else {
2022-09-27 21:19:35 +00:00
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
user_org_status = UserOrgStatus ::Accepted as i32 ;
}
2018-12-30 22:34:31 +00:00
user
}
2018-09-10 13:51:40 +00:00
}
} ;
2018-12-19 21:51:08 +00:00
let mut new_user = UserOrganization ::new ( user . uuid . clone ( ) , org_id . clone ( ) ) ;
let access_all = data . AccessAll . unwrap_or ( false ) ;
new_user . access_all = access_all ;
2019-05-20 19:24:29 +00:00
new_user . atype = new_type ;
2018-12-19 21:51:08 +00:00
new_user . status = user_org_status ;
// If no accessAll, add the collections received
if ! access_all {
2019-02-08 18:12:08 +00:00
for col in data . Collections . iter ( ) . flatten ( ) {
2022-05-20 21:39:47 +00:00
match Collection ::find_by_uuid_and_org ( & col . Id , & org_id , & mut conn ) . await {
2018-12-19 21:51:08 +00:00
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2022-05-20 21:39:47 +00:00
CollectionUser ::save ( & user . uuid , & collection . uuid , col . ReadOnly , col . HidePasswords , & mut conn )
2021-11-16 16:07:55 +00:00
. await ? ;
2018-05-04 17:25:50 +00:00
}
2018-04-24 20:01:55 +00:00
}
}
2018-10-12 14:20:10 +00:00
}
2018-12-15 02:56:00 +00:00
2022-05-20 21:39:47 +00:00
new_user . save ( & mut conn ) . await ? ;
2018-12-19 21:51:08 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserInvited as i32 ,
& new_user . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2019-02-02 00:09:21 +00:00
if CONFIG . mail_enabled ( ) {
2022-05-20 21:39:47 +00:00
let org_name = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2018-12-15 02:56:00 +00:00
Some ( org ) = > org . name ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Error looking up organization " ) ,
2018-12-15 02:56:00 +00:00
} ;
2019-01-08 14:11:16 +00:00
2019-01-06 04:03:49 +00:00
mail ::send_invite (
2021-09-09 11:50:18 +00:00
& email ,
2019-01-08 14:11:16 +00:00
& user . uuid ,
Some ( org_id . clone ( ) ) ,
Some ( new_user . uuid ) ,
& org_name ,
Some ( headers . user . email . clone ( ) ) ,
2022-07-06 21:57:37 +00:00
)
. await ? ;
2018-12-15 02:56:00 +00:00
}
}
Ok ( ( ) )
}
2021-09-18 12:22:14 +00:00
#[ post( " /organizations/<org_id>/users/reinvite " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn bulk_reinvite_user (
2021-09-18 12:22:14 +00:00
org_id : String ,
data : JsonUpcase < OrgBulkIds > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2021-09-18 12:22:14 +00:00
) -> Json < Value > {
let data : OrgBulkIds = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
for org_user_id in data . Ids {
2022-05-20 21:39:47 +00:00
let err_msg = match _reinvite_user ( & org_id , & org_user_id , & headers . user . email , & mut conn ) . await {
2022-11-04 11:56:02 +00:00
Ok ( _ ) = > String ::new ( ) ,
2021-09-18 12:22:14 +00:00
Err ( e ) = > format! ( " {:?} " , e ) ,
} ;
bulk_response . push ( json! (
{
" Object " : " OrganizationBulkConfirmResponseModel " ,
" Id " : org_user_id ,
" Error " : err_msg
}
) )
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2018-12-30 05:19:01 +00:00
#[ post( " /organizations/<org_id>/users/<user_org>/reinvite " ) ]
2022-05-20 21:39:47 +00:00
async fn reinvite_user ( org_id : String , user_org : String , headers : AdminHeaders , mut conn : DbConn ) -> EmptyResult {
_reinvite_user ( & org_id , & user_org , & headers . user . email , & mut conn ) . await
2021-09-18 12:22:14 +00:00
}
2022-05-20 21:39:47 +00:00
async fn _reinvite_user ( org_id : & str , user_org : & str , invited_by_email : & str , conn : & mut DbConn ) -> EmptyResult {
2019-01-25 17:23:51 +00:00
if ! CONFIG . invitations_allowed ( ) {
2018-12-30 04:24:38 +00:00
err! ( " Invitations are not allowed. " )
}
2019-02-02 00:09:21 +00:00
if ! CONFIG . mail_enabled ( ) {
2018-12-30 04:24:38 +00:00
err! ( " SMTP is not configured. " )
}
2021-11-16 16:07:55 +00:00
let user_org = match UserOrganization ::find_by_uuid ( user_org , conn ) . await {
2018-12-30 05:19:01 +00:00
Some ( user_org ) = > user_org ,
2019-01-07 14:29:57 +00:00
None = > err! ( " The user hasn't been invited to the organization. " ) ,
2018-12-30 05:19:01 +00:00
} ;
2019-01-07 14:29:57 +00:00
if user_org . status ! = UserOrgStatus ::Invited as i32 {
err! ( " The user is already accepted or confirmed to the organization " )
}
2021-11-16 16:07:55 +00:00
let user = match User ::find_by_uuid ( & user_org . user_uuid , conn ) . await {
2018-12-30 04:24:38 +00:00
Some ( user ) = > user ,
None = > err! ( " User not found. " ) ,
} ;
2019-01-08 13:05:05 +00:00
2021-11-16 16:07:55 +00:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
2018-12-30 04:24:38 +00:00
Some ( org ) = > org . name ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Error looking up organization. " ) ,
2018-12-30 04:24:38 +00:00
} ;
2019-02-02 00:09:21 +00:00
if CONFIG . mail_enabled ( ) {
2019-01-04 15:32:51 +00:00
mail ::send_invite (
2019-01-08 14:11:16 +00:00
& user . email ,
& user . uuid ,
2021-09-18 12:22:14 +00:00
Some ( org_id . to_string ( ) ) ,
2019-01-08 14:11:16 +00:00
Some ( user_org . uuid ) ,
& org_name ,
2021-09-18 12:22:14 +00:00
Some ( invited_by_email . to_string ( ) ) ,
2022-07-06 21:57:37 +00:00
)
. await ? ;
2019-01-08 14:11:16 +00:00
} else {
2022-11-26 18:07:28 +00:00
let invitation = Invitation ::new ( & user . email ) ;
2021-11-16 16:07:55 +00:00
invitation . save ( conn ) . await ? ;
2018-12-30 04:24:38 +00:00
}
2018-12-30 22:34:31 +00:00
2018-12-30 04:24:38 +00:00
Ok ( ( ) )
}
2018-12-19 04:16:03 +00:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct AcceptData {
Token : String ,
}
2022-08-20 14:42:36 +00:00
#[ post( " /organizations/<org_id>/users/<_org_user_id>/accept " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn accept_invite (
2022-08-20 14:42:36 +00:00
org_id : String ,
2021-11-16 16:07:55 +00:00
_org_user_id : String ,
data : JsonUpcase < AcceptData > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2021-11-16 16:07:55 +00:00
) -> EmptyResult {
2018-12-30 22:34:31 +00:00
// The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead
2018-12-19 04:16:03 +00:00
let data : AcceptData = data . into_inner ( ) . data ;
2022-08-20 14:42:36 +00:00
let claims = decode_invite ( & data . Token ) ? ;
2018-12-15 02:56:00 +00:00
2022-05-20 21:39:47 +00:00
match User ::find_by_mail ( & claims . email , & mut conn ) . await {
2018-12-19 04:16:03 +00:00
Some ( _ ) = > {
2022-05-20 21:39:47 +00:00
Invitation ::take ( & claims . email , & mut conn ) . await ;
2019-01-08 14:11:16 +00:00
if let ( Some ( user_org ) , Some ( org ) ) = ( & claims . user_org_id , & claims . org_id ) {
2022-05-20 21:39:47 +00:00
let mut user_org = match UserOrganization ::find_by_uuid_and_org ( user_org , org , & mut conn ) . await {
2019-01-08 14:11:16 +00:00
Some ( user_org ) = > user_org ,
None = > err! ( " Error accepting the invitation " ) ,
} ;
if user_org . status ! = UserOrgStatus ::Invited as i32 {
err! ( " User already accepted the invitation " )
2018-12-15 02:56:00 +00:00
}
2019-01-08 14:11:16 +00:00
2022-08-20 14:42:36 +00:00
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if user_org . atype < UserOrgType ::Admin {
2022-05-20 21:39:47 +00:00
match OrgPolicy ::is_user_allowed ( & user_org . user_uuid , & org_id , false , & mut conn ) . await {
2022-08-20 14:42:36 +00:00
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
err! ( " You cannot join this organization until you enable two-step login on your user account " ) ;
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
err! ( " You cannot join this organization because you are a member of an organization which forbids it " ) ;
}
2021-09-24 15:55:49 +00:00
}
}
2019-01-08 14:11:16 +00:00
user_org . status = UserOrgStatus ::Accepted as i32 ;
2022-05-20 21:39:47 +00:00
user_org . save ( & mut conn ) . await ? ;
2018-12-15 02:56:00 +00:00
}
2018-12-30 22:34:31 +00:00
}
None = > err! ( " Invited user not found " ) ,
2018-04-24 20:01:55 +00:00
}
2019-02-02 00:09:21 +00:00
if CONFIG . mail_enabled ( ) {
2021-03-04 07:03:55 +00:00
let mut org_name = CONFIG . invitation_org_name ( ) ;
2019-01-06 04:03:49 +00:00
if let Some ( org_id ) = & claims . org_id {
2022-05-20 21:39:47 +00:00
org_name = match Organization ::find_by_uuid ( org_id , & mut conn ) . await {
2019-01-04 15:32:51 +00:00
Some ( org ) = > org . name ,
2019-01-08 14:11:16 +00:00
None = > err! ( " Organization not found. " ) ,
2019-01-06 04:03:49 +00:00
} ;
2019-01-03 03:20:39 +00:00
} ;
2019-01-05 18:46:45 +00:00
if let Some ( invited_by_email ) = & claims . invited_by_email {
2019-01-04 15:32:51 +00:00
// User was invited to an organization, so they must be confirmed manually after acceptance
2022-07-06 21:57:37 +00:00
mail ::send_invite_accepted ( & claims . email , invited_by_email , & org_name ) . await ? ;
2019-01-04 15:32:51 +00:00
} else {
// User was invited from /admin, so they are automatically confirmed
2022-07-06 21:57:37 +00:00
mail ::send_invite_confirmed ( & claims . email , & org_name ) . await ? ;
2019-01-04 15:32:51 +00:00
}
2019-01-03 03:20:39 +00:00
}
2018-04-24 20:01:55 +00:00
Ok ( ( ) )
}
2021-09-18 12:22:14 +00:00
#[ post( " /organizations/<org_id>/users/confirm " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn bulk_confirm_invite (
org_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2021-11-16 16:07:55 +00:00
) -> Json < Value > {
2021-09-18 12:22:14 +00:00
let data = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
match data [ " Keys " ] . as_array ( ) {
Some ( keys ) = > {
for invite in keys {
let org_user_id = invite [ " Id " ] . as_str ( ) . unwrap_or_default ( ) ;
let user_key = invite [ " Key " ] . as_str ( ) . unwrap_or_default ( ) ;
2022-11-20 18:15:45 +00:00
let err_msg = match _confirm_invite ( & org_id , org_user_id , user_key , & headers , & mut conn , & ip ) . await {
2022-11-04 11:56:02 +00:00
Ok ( _ ) = > String ::new ( ) ,
2021-09-18 12:22:14 +00:00
Err ( e ) = > format! ( " {:?} " , e ) ,
} ;
bulk_response . push ( json! (
{
" Object " : " OrganizationBulkConfirmResponseModel " ,
" Id " : org_user_id ,
" Error " : err_msg
}
) ) ;
}
}
None = > error! ( " No keys to confirm " ) ,
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2018-09-04 10:24:53 +00:00
#[ post( " /organizations/<org_id>/users/<org_user_id>/confirm " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn confirm_invite (
2018-12-30 22:34:31 +00:00
org_id : String ,
org_user_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2018-05-31 22:18:50 +00:00
let data = data . into_inner ( ) . data ;
2021-09-18 12:22:14 +00:00
let user_key = data [ " Key " ] . as_str ( ) . unwrap_or_default ( ) ;
2022-11-20 18:15:45 +00:00
_confirm_invite ( & org_id , & org_user_id , user_key , & headers , & mut conn , & ip ) . await
2021-09-18 12:22:14 +00:00
}
2018-05-31 22:18:50 +00:00
2021-11-16 16:07:55 +00:00
async fn _confirm_invite (
org_id : & str ,
org_user_id : & str ,
key : & str ,
headers : & AdminHeaders ,
2022-05-20 21:39:47 +00:00
conn : & mut DbConn ,
2022-11-20 18:15:45 +00:00
ip : & ClientIp ,
2021-11-16 16:07:55 +00:00
) -> EmptyResult {
2021-09-18 12:22:14 +00:00
if key . is_empty ( ) | | org_user_id . is_empty ( ) {
err! ( " Key or UserId is not set, unable to process request " ) ;
}
2021-11-16 16:07:55 +00:00
let mut user_to_confirm = match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) . await {
2018-04-24 22:34:40 +00:00
Some ( user ) = > user ,
2018-12-30 22:34:31 +00:00
None = > err! ( " The specified user isn't a member of the organization " ) ,
2018-04-24 22:34:40 +00:00
} ;
2019-05-20 19:24:29 +00:00
if user_to_confirm . atype ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-11-12 17:13:25 +00:00
err! ( " Only Owners can confirm Managers, Admins or Owners " )
2018-04-24 21:04:10 +00:00
}
2018-04-24 20:01:55 +00:00
2018-04-24 22:34:40 +00:00
if user_to_confirm . status ! = UserOrgStatus ::Accepted as i32 {
2018-04-24 20:01:55 +00:00
err! ( " User in invalid state " )
}
2022-08-20 14:42:36 +00:00
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if user_to_confirm . atype < UserOrgType ::Admin {
match OrgPolicy ::is_user_allowed ( & user_to_confirm . user_uuid , org_id , true , conn ) . await {
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
err! ( " You cannot confirm this user because it has no two-step login method activated " ) ;
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
err! ( " You cannot confirm this user because it is a member of an organization which forbids it " ) ;
}
}
}
2018-04-24 22:34:40 +00:00
user_to_confirm . status = UserOrgStatus ::Confirmed as i32 ;
2021-09-18 12:22:14 +00:00
user_to_confirm . akey = key . to_string ( ) ;
2018-04-24 20:01:55 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserConfirmed as i32 ,
& user_to_confirm . uuid ,
String ::from ( org_id ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
conn ,
)
. await ;
2019-02-02 00:09:21 +00:00
if CONFIG . mail_enabled ( ) {
2021-11-16 16:07:55 +00:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
2019-01-03 03:20:39 +00:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization. " ) ,
} ;
2021-11-16 16:07:55 +00:00
let address = match User ::find_by_uuid ( & user_to_confirm . user_uuid , conn ) . await {
2019-01-03 03:20:39 +00:00
Some ( user ) = > user . email ,
None = > err! ( " Error looking up user. " ) ,
} ;
2022-07-06 21:57:37 +00:00
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
2019-01-03 03:20:39 +00:00
}
2021-11-16 16:07:55 +00:00
user_to_confirm . save ( conn ) . await
2018-04-24 22:34:40 +00:00
}
2018-09-04 10:24:53 +00:00
#[ get( " /organizations/<org_id>/users/<org_user_id> " ) ]
2022-05-20 21:39:47 +00:00
async fn get_user ( org_id : String , org_user_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
let user = match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & mut conn ) . await {
2018-04-24 22:34:40 +00:00
Some ( user ) = > user ,
2018-12-30 22:34:31 +00:00
None = > err! ( " The specified user isn't a member of the organization " ) ,
2018-04-24 22:34:40 +00:00
} ;
2022-05-20 21:39:47 +00:00
Ok ( Json ( user . to_json_details ( & mut conn ) . await ) )
2018-04-24 22:34:40 +00:00
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct EditUserData {
2018-05-31 22:18:50 +00:00
Type : NumberOrString ,
2019-02-08 18:12:08 +00:00
Collections : Option < Vec < CollectionData > > ,
2018-05-31 22:18:50 +00:00
AccessAll : bool ,
2018-04-24 22:34:40 +00:00
}
2018-09-04 10:24:53 +00:00
#[ put( " /organizations/<org_id>/users/<org_user_id> " , data = " <data> " , rank = 1) ]
2021-11-16 16:07:55 +00:00
async fn put_organization_user (
2018-12-30 22:34:31 +00:00
org_id : String ,
org_user_id : String ,
data : JsonUpcase < EditUserData > ,
headers : AdminHeaders ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
edit_user ( org_id , org_user_id , data , headers , conn , ip ) . await
2018-08-13 15:45:30 +00:00
}
2018-09-04 10:24:53 +00:00
#[ post( " /organizations/<org_id>/users/<org_user_id> " , data = " <data> " , rank = 1) ]
2021-11-16 16:07:55 +00:00
async fn edit_user (
2018-12-30 22:34:31 +00:00
org_id : String ,
org_user_id : String ,
data : JsonUpcase < EditUserData > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2018-05-31 22:18:50 +00:00
let data : EditUserData = data . into_inner ( ) . data ;
2018-04-24 22:34:40 +00:00
2018-06-11 13:44:37 +00:00
let new_type = match UserOrgType ::from_str ( & data . Type . into_string ( ) ) {
2018-11-12 17:13:25 +00:00
Some ( new_type ) = > new_type ,
2018-12-30 22:34:31 +00:00
None = > err! ( " Invalid type " ) ,
2018-04-24 22:34:40 +00:00
} ;
2022-05-20 21:39:47 +00:00
let mut user_to_edit = match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & mut conn ) . await {
2018-04-24 22:34:40 +00:00
Some ( user ) = > user ,
2018-12-30 22:34:31 +00:00
None = > err! ( " The specified user isn't member of the organization " ) ,
2018-04-24 22:34:40 +00:00
} ;
2019-05-20 19:24:29 +00:00
if new_type ! = user_to_edit . atype
& & ( user_to_edit . atype > = UserOrgType ::Admin | | new_type > = UserOrgType ::Admin )
2018-12-30 22:34:31 +00:00
& & headers . org_user_type ! = UserOrgType ::Owner
{
2018-09-04 10:24:53 +00:00
err! ( " Only Owners can grant and remove Admin or Owner privileges " )
2018-04-24 22:34:40 +00:00
}
2019-05-20 19:24:29 +00:00
if user_to_edit . atype = = UserOrgType ::Owner & & headers . org_user_type ! = UserOrgType ::Owner {
2018-09-04 10:24:53 +00:00
err! ( " Only Owners can edit Owner users " )
2018-04-24 22:34:40 +00:00
}
2022-09-27 08:10:09 +00:00
if user_to_edit . atype = = UserOrgType ::Owner
& & new_type ! = UserOrgType ::Owner
& & user_to_edit . status = = UserOrgStatus ::Confirmed as i32
{
// Removing owner permission, check that there is at least one other confirmed owner
2022-05-20 21:39:47 +00:00
if UserOrganization ::count_confirmed_by_org_and_type ( & org_id , UserOrgType ::Owner , & mut conn ) . await < = 1 {
2018-04-24 22:34:40 +00:00
err! ( " Can't delete the last owner " )
}
}
2022-08-20 14:42:36 +00:00
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if new_type < UserOrgType ::Admin {
2022-05-20 21:39:47 +00:00
match OrgPolicy ::is_user_allowed ( & user_to_edit . user_uuid , & org_id , true , & mut conn ) . await {
2022-08-20 14:42:36 +00:00
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
err! ( " You cannot modify this user to this type because it has no two-step login method activated " ) ;
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
err! ( " You cannot modify this user to this type because it is a member of an organization which forbids it " ) ;
}
}
}
2018-05-31 22:18:50 +00:00
user_to_edit . access_all = data . AccessAll ;
2019-05-20 19:24:29 +00:00
user_to_edit . atype = new_type as i32 ;
2018-04-24 22:34:40 +00:00
2018-05-04 17:25:50 +00:00
// Delete all the odd collections
2022-05-20 21:39:47 +00:00
for c in CollectionUser ::find_by_organization_and_user_uuid ( & org_id , & user_to_edit . user_uuid , & mut conn ) . await {
c . delete ( & mut conn ) . await ? ;
2018-05-04 17:25:50 +00:00
}
// If no accessAll, add the collections received
2018-05-31 22:18:50 +00:00
if ! data . AccessAll {
2019-02-08 18:12:08 +00:00
for col in data . Collections . iter ( ) . flatten ( ) {
2022-05-20 21:39:47 +00:00
match Collection ::find_by_uuid_and_org ( & col . Id , & org_id , & mut conn ) . await {
2018-05-28 16:26:02 +00:00
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2021-03-31 20:18:35 +00:00
CollectionUser ::save (
& user_to_edit . user_uuid ,
& collection . uuid ,
col . ReadOnly ,
col . HidePasswords ,
2022-05-20 21:39:47 +00:00
& mut conn ,
2021-11-16 16:07:55 +00:00
)
. await ? ;
2018-05-28 16:26:02 +00:00
}
}
2018-05-04 17:25:50 +00:00
}
2018-04-24 22:34:40 +00:00
}
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserUpdated as i32 ,
& user_to_edit . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
user_to_edit . save ( & mut conn ) . await
2018-04-24 20:01:55 +00:00
}
2021-09-18 12:22:14 +00:00
#[ delete( " /organizations/<org_id>/users " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn bulk_delete_user (
org_id : String ,
data : JsonUpcase < OrgBulkIds > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2021-11-16 16:07:55 +00:00
) -> Json < Value > {
2021-09-18 12:22:14 +00:00
let data : OrgBulkIds = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
for org_user_id in data . Ids {
2022-11-20 18:15:45 +00:00
let err_msg = match _delete_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await {
2022-11-04 11:56:02 +00:00
Ok ( _ ) = > String ::new ( ) ,
2021-09-18 12:22:14 +00:00
Err ( e ) = > format! ( " {:?} " , e ) ,
} ;
bulk_response . push ( json! (
{
" Object " : " OrganizationBulkConfirmResponseModel " ,
" Id " : org_user_id ,
" Error " : err_msg
}
) )
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2018-09-04 10:24:53 +00:00
#[ delete( " /organizations/<org_id>/users/<org_user_id> " ) ]
2022-11-20 18:15:45 +00:00
async fn delete_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
mut conn : DbConn ,
ip : ClientIp ,
) -> EmptyResult {
_delete_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
}
#[ post( " /organizations/<org_id>/users/<org_user_id>/delete " ) ]
async fn post_delete_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
mut conn : DbConn ,
ip : ClientIp ,
) -> EmptyResult {
_delete_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
2021-09-18 12:22:14 +00:00
}
2022-11-20 18:15:45 +00:00
async fn _delete_user (
org_id : & str ,
org_user_id : & str ,
headers : & AdminHeaders ,
conn : & mut DbConn ,
ip : & ClientIp ,
) -> EmptyResult {
2021-11-16 16:07:55 +00:00
let user_to_delete = match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) . await {
2018-04-24 22:34:40 +00:00
Some ( user ) = > user ,
2018-12-30 22:34:31 +00:00
None = > err! ( " User to delete isn't member of the organization " ) ,
2018-04-24 22:34:40 +00:00
} ;
2019-05-20 19:24:29 +00:00
if user_to_delete . atype ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-04-24 21:04:10 +00:00
err! ( " Only Owners can delete Admins or Owners " )
}
2018-02-17 21:30:19 +00:00
2022-09-27 08:10:09 +00:00
if user_to_delete . atype = = UserOrgType ::Owner & & user_to_delete . status = = UserOrgStatus ::Confirmed as i32 {
2022-08-20 14:42:36 +00:00
// Removing owner, check that there is at least one other confirmed owner
if UserOrganization ::count_confirmed_by_org_and_type ( org_id , UserOrgType ::Owner , conn ) . await < = 1 {
2018-04-24 22:34:40 +00:00
err! ( " Can't delete the last owner " )
}
}
2018-02-17 21:30:19 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& user_to_delete . uuid ,
String ::from ( org_id ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
conn ,
)
. await ;
2018-08-13 15:45:30 +00:00
2022-11-20 18:15:45 +00:00
user_to_delete . delete ( conn ) . await
2018-09-13 13:16:24 +00:00
}
2021-09-18 12:22:14 +00:00
#[ post( " /organizations/<org_id>/users/public-keys " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn bulk_public_keys (
org_id : String ,
data : JsonUpcase < OrgBulkIds > ,
_headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2021-11-16 16:07:55 +00:00
) -> Json < Value > {
2021-09-18 12:22:14 +00:00
let data : OrgBulkIds = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
// Check all received UserOrg UUID's and find the matching User to retreive the public-key.
// If the user does not exists, just ignore it, and do not return any information regarding that UserOrg UUID.
// The web-vault will then ignore that user for the folowing steps.
for user_org_id in data . Ids {
2022-05-20 21:39:47 +00:00
match UserOrganization ::find_by_uuid_and_org ( & user_org_id , & org_id , & mut conn ) . await {
Some ( user_org ) = > match User ::find_by_uuid ( & user_org . user_uuid , & mut conn ) . await {
2021-09-18 12:22:14 +00:00
Some ( user ) = > bulk_response . push ( json! (
{
" Object " : " organizationUserPublicKeyResponseModel " ,
" Id " : user_org_id ,
" UserId " : user . uuid ,
" Key " : user . public_key
}
) ) ,
None = > debug! ( " User doesn't exist " ) ,
} ,
None = > debug! ( " UserOrg doesn't exist " ) ,
}
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2018-09-13 13:16:24 +00:00
use super ::ciphers ::update_cipher_from_data ;
2018-12-30 22:34:31 +00:00
use super ::ciphers ::CipherData ;
2018-09-13 13:16:24 +00:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct ImportData {
Ciphers : Vec < CipherData > ,
Collections : Vec < NewCollectionData > ,
CollectionRelationships : Vec < RelationsData > ,
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct RelationsData {
// Cipher index
Key : usize ,
// Collection index
Value : usize ,
}
2018-10-10 18:40:39 +00:00
#[ post( " /ciphers/import-organization?<query..> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn post_org_import (
2021-11-07 17:53:39 +00:00
query : OrgIdData ,
2018-12-30 22:34:31 +00:00
data : JsonUpcase < ImportData > ,
2020-03-14 12:22:30 +00:00
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2021-11-16 16:07:55 +00:00
nt : Notify < '_ > ,
2018-12-30 22:34:31 +00:00
) -> EmptyResult {
2018-09-13 13:16:24 +00:00
let data : ImportData = data . into_inner ( ) . data ;
2021-11-07 17:53:39 +00:00
let org_id = query . organization_id ;
2018-09-13 13:16:24 +00:00
2022-05-20 21:39:47 +00:00
let mut collections = Vec ::new ( ) ;
for coll in data . Collections {
let collection = Collection ::new ( org_id . clone ( ) , coll . Name ) ;
if collection . save ( & mut conn ) . await . is_err ( ) {
collections . push ( Err ( Error ::new ( " Failed to create Collection " , " Failed to create Collection " ) ) ) ;
} else {
collections . push ( Ok ( collection ) ) ;
}
}
2018-09-13 13:16:24 +00:00
// Read the relations between collections and ciphers
let mut relations = Vec ::new ( ) ;
for relation in data . CollectionRelationships {
relations . push ( ( relation . Key , relation . Value ) ) ;
}
2020-03-14 12:22:30 +00:00
let headers : Headers = headers . into ( ) ;
2022-05-20 21:39:47 +00:00
let mut ciphers = Vec ::new ( ) ;
for cipher_data in data . Ciphers {
let mut cipher = Cipher ::new ( cipher_data . Type , cipher_data . Name . clone ( ) ) ;
2022-11-20 18:15:45 +00:00
update_cipher_from_data ( & mut cipher , cipher_data , & headers , false , & mut conn , & ip , & nt , UpdateType ::None )
. await
. ok ( ) ;
2022-05-20 21:39:47 +00:00
ciphers . push ( cipher ) ;
}
2018-09-13 13:16:24 +00:00
// Assign the collections
for ( cipher_index , coll_index ) in relations {
let cipher_id = & ciphers [ cipher_index ] . uuid ;
2018-10-01 16:50:31 +00:00
let coll = & collections [ coll_index ] ;
let coll_id = match coll {
Ok ( coll ) = > coll . uuid . as_str ( ) ,
2018-12-30 22:34:31 +00:00
Err ( _ ) = > err! ( " Failed to assign to collection " ) ,
2018-10-01 16:50:31 +00:00
} ;
2018-12-30 22:34:31 +00:00
2022-05-20 21:39:47 +00:00
CollectionCipher ::save ( cipher_id , coll_id , & mut conn ) . await ? ;
2018-09-13 13:16:24 +00:00
}
let mut user = headers . user ;
2022-05-20 21:39:47 +00:00
user . update_revision ( & mut conn ) . await
2018-10-10 18:40:39 +00:00
}
2020-03-14 12:22:30 +00:00
#[ get( " /organizations/<org_id>/policies " ) ]
2022-05-20 21:39:47 +00:00
async fn list_policies ( org_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> Json < Value > {
let policies = OrgPolicy ::find_by_org ( & org_id , & mut conn ) . await ;
2020-03-14 12:22:30 +00:00
let policies_json : Vec < Value > = policies . iter ( ) . map ( OrgPolicy ::to_json ) . collect ( ) ;
2021-03-27 15:07:26 +00:00
Json ( json! ( {
2020-03-14 12:22:30 +00:00
" Data " : policies_json ,
" Object " : " list " ,
" ContinuationToken " : null
2021-03-27 15:07:26 +00:00
} ) )
2020-03-14 12:22:30 +00:00
}
2020-03-20 09:51:17 +00:00
#[ get( " /organizations/<org_id>/policies/token?<token> " ) ]
2022-05-20 21:39:47 +00:00
async fn list_policies_token ( org_id : String , token : String , mut conn : DbConn ) -> JsonResult {
2020-03-20 09:51:17 +00:00
let invite = crate ::auth ::decode_invite ( & token ) ? ;
let invite_org_id = match invite . org_id {
Some ( invite_org_id ) = > invite_org_id ,
None = > err! ( " Invalid token " ) ,
} ;
if invite_org_id ! = org_id {
err! ( " Token doesn't match request organization " ) ;
}
2020-07-14 16:00:09 +00:00
2020-03-20 09:51:17 +00:00
// TODO: We receive the invite token as ?token=<>, validate it contains the org id
2022-05-20 21:39:47 +00:00
let policies = OrgPolicy ::find_by_org ( & org_id , & mut conn ) . await ;
2020-03-20 09:51:17 +00:00
let policies_json : Vec < Value > = policies . iter ( ) . map ( OrgPolicy ::to_json ) . collect ( ) ;
Ok ( Json ( json! ( {
" Data " : policies_json ,
" Object " : " list " ,
" ContinuationToken " : null
} ) ) )
}
2020-03-14 12:22:30 +00:00
#[ get( " /organizations/<org_id>/policies/<pol_type> " ) ]
2022-05-20 21:39:47 +00:00
async fn get_policy ( org_id : String , pol_type : i32 , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
2020-03-14 12:22:30 +00:00
let pol_type_enum = match OrgPolicyType ::from_i32 ( pol_type ) {
Some ( pt ) = > pt ,
2021-01-24 04:50:06 +00:00
None = > err! ( " Invalid or unsupported policy type " ) ,
2020-03-14 12:22:30 +00:00
} ;
2022-05-20 21:39:47 +00:00
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & mut conn ) . await {
2020-03-14 12:22:30 +00:00
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id , pol_type_enum , " {} " . to_string ( ) ) ,
} ;
Ok ( Json ( policy . to_json ( ) ) )
}
#[ derive(Deserialize) ]
struct PolicyData {
enabled : bool ,
#[ serde(rename = " type " ) ]
_type : i32 ,
2021-09-24 15:20:44 +00:00
data : Option < Value > ,
2020-03-14 12:22:30 +00:00
}
#[ put( " /organizations/<org_id>/policies/<pol_type> " , data = " <data> " ) ]
2021-11-16 16:07:55 +00:00
async fn put_policy (
2021-03-31 20:18:35 +00:00
org_id : String ,
pol_type : i32 ,
data : Json < PolicyData > ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2021-03-31 20:18:35 +00:00
) -> JsonResult {
2020-03-14 12:22:30 +00:00
let data : PolicyData = data . into_inner ( ) ;
let pol_type_enum = match OrgPolicyType ::from_i32 ( pol_type ) {
Some ( pt ) = > pt ,
2022-08-20 14:42:36 +00:00
None = > err! ( " Invalid or unsupported policy type " ) ,
2020-03-14 12:22:30 +00:00
} ;
2022-08-20 14:42:36 +00:00
// When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
2021-04-16 18:49:59 +00:00
if pol_type_enum = = OrgPolicyType ::TwoFactorAuthentication & & data . enabled {
2022-05-20 21:39:47 +00:00
for member in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await . into_iter ( ) {
let user_twofactor_disabled = TwoFactor ::find_by_user ( & member . user_uuid , & mut conn ) . await . is_empty ( ) ;
2021-04-12 02:57:17 +00:00
2021-10-09 12:54:30 +00:00
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
2022-08-20 14:42:36 +00:00
// Invited users still need to accept the invite and will get an error when they try to accept the invite.
2021-10-09 14:42:06 +00:00
if user_twofactor_disabled
& & member . atype < UserOrgType ::Admin
& & member . status ! = UserOrgStatus ::Invited as i32
{
2021-04-13 01:54:57 +00:00
if CONFIG . mail_enabled ( ) {
2022-05-20 21:39:47 +00: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 ( ) ;
2021-04-12 02:57:17 +00:00
2022-07-06 21:57:37 +00:00
mail ::send_2fa_removed_from_org ( & user . email , & org . name ) . await ? ;
2021-04-13 01:54:57 +00:00
}
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& member . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
member . delete ( & mut conn ) . await ? ;
2021-04-12 02:57:17 +00:00
}
2021-04-16 18:49:59 +00:00
}
2021-04-12 02:57:17 +00:00
}
2020-03-14 12:22:30 +00:00
2022-08-20 14:42:36 +00:00
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
2021-09-24 15:55:49 +00:00
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
2022-05-20 21:39:47 +00:00
for member in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await . into_iter ( ) {
2021-09-24 15:55:49 +00:00
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
2022-08-20 14:42:36 +00: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.
if member . atype < UserOrgType ::Admin
& & member . status ! = UserOrgStatus ::Invited as i32
2022-05-20 21:39:47 +00:00
& & UserOrganization ::count_accepted_and_confirmed_by_user ( & member . user_uuid , & mut conn ) . await > 1
2022-08-20 14:42:36 +00:00
{
if CONFIG . mail_enabled ( ) {
2022-05-20 21:39:47 +00: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 14:42:36 +00:00
mail ::send_single_org_removed_from_org ( & user . email , & org . name ) . await ? ;
2021-09-24 15:55:49 +00:00
}
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& member . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
member . delete ( & mut conn ) . await ? ;
2021-09-24 15:55:49 +00:00
}
}
}
2022-05-20 21:39:47 +00:00
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & mut conn ) . await {
2020-03-14 12:22:30 +00:00
Some ( p ) = > p ,
2022-11-20 18:15:45 +00:00
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , " {} " . to_string ( ) ) ,
2020-03-14 12:22:30 +00:00
} ;
policy . enabled = data . enabled ;
policy . data = serde_json ::to_string ( & data . data ) ? ;
2022-05-20 21:39:47 +00:00
policy . save ( & mut conn ) . await ? ;
2020-03-14 12:22:30 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::PolicyUpdated as i32 ,
& policy . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2020-03-14 12:22:30 +00:00
Ok ( Json ( policy . to_json ( ) ) )
2020-04-09 08:51:05 +00:00
}
2020-09-14 06:34:17 +00:00
2021-01-31 20:46:37 +00:00
#[ allow(unused_variables) ]
#[ get( " /organizations/<org_id>/tax " ) ]
2021-11-05 18:18:54 +00:00
fn get_organization_tax ( org_id : String , _headers : Headers ) -> Json < Value > {
2021-01-31 20:46:37 +00:00
// Prevent a 404 error, which also causes Javascript errors.
2021-11-05 18:18:54 +00: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 20:46:37 +00:00
}
2020-09-14 06:34:17 +00:00
#[ get( " /plans " ) ]
2022-09-12 12:10:54 +00:00
fn get_plans ( ) -> Json < Value > {
2021-11-05 18:18:54 +00: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! ( {
2020-09-14 06:34:17 +00:00
" Object " : " list " ,
2021-11-05 18:18:54 +00:00
" Data " : [ {
2020-09-14 06:34:17 +00:00
" Object " : " plan " ,
" Type " : 0 ,
" Product " : 0 ,
" Name " : " Free " ,
" NameLocalizationKey " : " planNameFree " ,
2021-11-05 18:18:54 +00:00
" DescriptionLocalizationKey " : " planDescFree "
} ] ,
2020-09-14 06:34:17 +00:00
" ContinuationToken " : null
2021-03-27 15:07:26 +00:00
} ) )
2021-01-24 04:50:06 +00:00
}
2021-01-31 20:46:37 +00:00
#[ get( " /plans/sales-tax-rates " ) ]
2021-11-05 18:18:54 +00:00
fn get_plans_tax_rates ( _headers : Headers ) -> Json < Value > {
2021-01-31 20:46:37 +00:00
// Prevent a 404 error, which also causes Javascript errors.
2021-11-05 18:18:54 +00:00
Json ( _empty_data_json ( ) )
}
fn _empty_data_json ( ) -> Value {
json! ( {
2021-01-31 20:46:37 +00:00
" Object " : " list " ,
" Data " : [ ] ,
" ContinuationToken " : null
2021-11-05 18:18:54 +00:00
} )
2021-01-31 20:46:37 +00:00
}
2021-02-06 17:22:39 +00:00
#[ derive(Deserialize, Debug) ]
2021-09-22 19:39:31 +00:00
#[ allow(non_snake_case, dead_code) ]
2021-02-06 17:22:39 +00:00
struct OrgImportGroupData {
Name : String , // "GroupName"
ExternalId : String , // "cn=GroupName,ou=Groups,dc=example,dc=com"
Users : Vec < String > , // ["uid=user,ou=People,dc=example,dc=com"]
}
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrgImportUserData {
2021-09-22 19:39:31 +00:00
Email : String , // "user@maildomain.net"
#[ allow(dead_code) ]
2021-02-06 17:22:39 +00:00
ExternalId : String , // "uid=user,ou=People,dc=example,dc=com"
Deleted : bool ,
}
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrgImportData {
2021-09-22 19:39:31 +00:00
#[ allow(dead_code) ]
2021-02-06 17:22:39 +00:00
Groups : Vec < OrgImportGroupData > ,
OverwriteExisting : bool ,
Users : Vec < OrgImportUserData > ,
}
#[ post( " /organizations/<org_id>/import " , data = " <data> " ) ]
2022-11-20 18:15:45 +00:00
async fn import (
org_id : String ,
data : JsonUpcase < OrgImportData > ,
headers : Headers ,
mut conn : DbConn ,
ip : ClientIp ,
) -> EmptyResult {
2021-02-06 17:22:39 +00:00
let data = data . into_inner ( ) . data ;
// 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.
// User needs to be admin or owner to use the Directry Connector
2022-05-20 21:39:47 +00:00
match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
2021-02-06 17:22:39 +00:00
Some ( user_org ) if user_org . atype > = UserOrgType ::Admin = > { /* Okay, nothing to do */ }
Some ( _ ) = > err! ( " User has insufficient permissions to use Directory Connector " ) ,
None = > err! ( " User not part of organization " ) ,
} ;
for user_data in & data . Users {
if user_data . Deleted {
// If user is marked for deletion and it exists, delete it
2022-05-20 21:39:47 +00:00
if let Some ( user_org ) = UserOrganization ::find_by_email_and_org ( & user_data . Email , & org_id , & mut conn ) . await
{
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& user_org . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
user_org . delete ( & mut conn ) . await ? ;
2021-02-06 17:22:39 +00:00
}
// If user is not part of the organization, but it exists
2022-05-20 21:39:47 +00:00
} else if UserOrganization ::find_by_email_and_org ( & user_data . Email , & org_id , & mut conn ) . await . is_none ( ) {
if let Some ( user ) = User ::find_by_mail ( & user_data . Email , & mut conn ) . await {
2021-02-06 17:22:39 +00:00
let user_org_status = if CONFIG . mail_enabled ( ) {
UserOrgStatus ::Invited as i32
} else {
UserOrgStatus ::Accepted as i32 // Automatically mark user as accepted if no email invites
} ;
let mut new_org_user = UserOrganization ::new ( user . uuid . clone ( ) , org_id . clone ( ) ) ;
new_org_user . access_all = false ;
new_org_user . atype = UserOrgType ::User as i32 ;
new_org_user . status = user_org_status ;
2022-05-20 21:39:47 +00:00
new_org_user . save ( & mut conn ) . await ? ;
2021-02-06 17:22:39 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserInvited as i32 ,
& new_org_user . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2021-02-06 17:22:39 +00:00
if CONFIG . mail_enabled ( ) {
2022-05-20 21:39:47 +00:00
let org_name = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
2021-02-06 17:22:39 +00:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization " ) ,
} ;
mail ::send_invite (
& user_data . Email ,
& user . uuid ,
Some ( org_id . clone ( ) ) ,
Some ( new_org_user . uuid ) ,
& org_name ,
Some ( headers . user . email . clone ( ) ) ,
2022-07-06 21:57:37 +00:00
)
. await ? ;
2021-02-06 17:22:39 +00:00
}
2021-03-27 15:07:26 +00:00
}
2021-02-06 17:22:39 +00: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)
if data . OverwriteExisting {
2022-05-20 21:39:47 +00:00
for user_org in UserOrganization ::find_by_org_and_type ( & org_id , UserOrgType ::User , & mut conn ) . await {
if let Some ( user_email ) = User ::find_by_uuid ( & user_org . user_uuid , & mut conn ) . await . map ( | u | u . email ) {
2021-02-06 17:22:39 +00:00
if ! data . Users . iter ( ) . any ( | u | u . Email = = user_email ) {
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRemoved as i32 ,
& user_org . uuid ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
user_org . delete ( & mut conn ) . await ? ;
2021-02-06 17:22:39 +00:00
}
2021-03-27 15:07:26 +00:00
}
2021-02-06 17:22:39 +00:00
}
}
Ok ( ( ) )
}
2022-08-20 14:42:36 +00:00
2022-09-12 14:08:36 +00:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 14:42:36 +00:00
#[ put( " /organizations/<org_id>/users/<org_user_id>/deactivate " ) ]
async fn deactivate_organization_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-08-20 14:42:36 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
_revoke_organization_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
2022-08-20 14:42:36 +00:00
}
2022-09-12 14:08:36 +00:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 14:42:36 +00:00
#[ put( " /organizations/<org_id>/users/deactivate " , data = " <data> " ) ]
async fn bulk_deactivate_organization_user (
org_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-09-12 14:08:36 +00:00
) -> Json < Value > {
2022-11-20 18:15:45 +00:00
bulk_revoke_organization_user ( org_id , data , headers , conn , ip ) . await
2022-09-12 14:08:36 +00:00
}
#[ put( " /organizations/<org_id>/users/<org_user_id>/revoke " ) ]
async fn revoke_organization_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-09-12 14:08:36 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
_revoke_organization_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
2022-09-12 14:08:36 +00:00
}
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
async fn bulk_revoke_organization_user (
org_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-08-20 14:42:36 +00:00
) -> Json < Value > {
let data = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
match data [ " Ids " ] . as_array ( ) {
Some ( org_users ) = > {
for org_user_id in org_users {
let org_user_id = org_user_id . as_str ( ) . unwrap_or_default ( ) ;
2022-11-20 18:15:45 +00:00
let err_msg = match _revoke_organization_user ( & org_id , org_user_id , & headers , & mut conn , & ip ) . await {
2022-11-04 11:56:02 +00:00
Ok ( _ ) = > String ::new ( ) ,
2022-08-20 14:42:36 +00:00
Err ( e ) = > format! ( " {:?} " , e ) ,
} ;
bulk_response . push ( json! (
{
" Object " : " OrganizationUserBulkResponseModel " ,
" Id " : org_user_id ,
" Error " : err_msg
}
) ) ;
}
}
None = > error! ( " No users to revoke " ) ,
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2022-09-12 14:08:36 +00:00
async fn _revoke_organization_user (
2022-08-20 14:42:36 +00:00
org_id : & str ,
org_user_id : & str ,
headers : & AdminHeaders ,
2022-05-20 21:39:47 +00:00
conn : & mut DbConn ,
2022-11-20 18:15:45 +00:00
ip : & ClientIp ,
2022-08-20 14:42:36 +00:00
) -> EmptyResult {
match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) . await {
Some ( mut user_org ) if user_org . status > UserOrgStatus ::Revoked as i32 = > {
if user_org . user_uuid = = headers . user . uuid {
err! ( " You cannot revoke yourself " )
}
if user_org . atype = = UserOrgType ::Owner & & headers . org_user_type ! = UserOrgType ::Owner {
err! ( " Only owners can revoke other owners " )
}
if user_org . atype = = UserOrgType ::Owner
& & UserOrganization ::count_confirmed_by_org_and_type ( org_id , UserOrgType ::Owner , conn ) . await < = 1
{
err! ( " Organization must have at least one confirmed owner " )
}
user_org . revoke ( ) ;
user_org . save ( conn ) . await ? ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRevoked as i32 ,
& user_org . uuid ,
org_id . to_string ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
conn ,
)
. await ;
2022-08-20 14:42:36 +00:00
}
Some ( _ ) = > err! ( " User is already revoked " ) ,
None = > err! ( " User not found in organization " ) ,
}
Ok ( ( ) )
}
2022-09-12 14:08:36 +00:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 14:42:36 +00:00
#[ put( " /organizations/<org_id>/users/<org_user_id>/activate " ) ]
async fn activate_organization_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-08-20 14:42:36 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
_restore_organization_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
2022-08-20 14:42:36 +00:00
}
2022-09-12 14:08:36 +00:00
// Pre web-vault v2022.9.x endpoint
2022-08-20 14:42:36 +00:00
#[ put( " /organizations/<org_id>/users/activate " , data = " <data> " ) ]
async fn bulk_activate_organization_user (
org_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-09-12 14:08:36 +00:00
) -> Json < Value > {
2022-11-20 18:15:45 +00:00
bulk_restore_organization_user ( org_id , data , headers , conn , ip ) . await
2022-09-12 14:08:36 +00:00
}
#[ put( " /organizations/<org_id>/users/<org_user_id>/restore " ) ]
async fn restore_organization_user (
org_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-09-12 14:08:36 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
_restore_organization_user ( & org_id , & org_user_id , & headers , & mut conn , & ip ) . await
2022-09-12 14:08:36 +00:00
}
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
async fn bulk_restore_organization_user (
org_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-08-20 14:42:36 +00:00
) -> Json < Value > {
let data = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
match data [ " Ids " ] . as_array ( ) {
Some ( org_users ) = > {
for org_user_id in org_users {
let org_user_id = org_user_id . as_str ( ) . unwrap_or_default ( ) ;
2022-11-20 18:15:45 +00:00
let err_msg = match _restore_organization_user ( & org_id , org_user_id , & headers , & mut conn , & ip ) . await {
2022-11-04 11:56:02 +00:00
Ok ( _ ) = > String ::new ( ) ,
2022-08-20 14:42:36 +00:00
Err ( e ) = > format! ( " {:?} " , e ) ,
} ;
bulk_response . push ( json! (
{
" Object " : " OrganizationUserBulkResponseModel " ,
" Id " : org_user_id ,
" Error " : err_msg
}
) ) ;
}
}
None = > error! ( " No users to restore " ) ,
}
Json ( json! ( {
" Data " : bulk_response ,
" Object " : " list " ,
" ContinuationToken " : null
} ) )
}
2022-09-12 14:08:36 +00:00
async fn _restore_organization_user (
2022-08-20 14:42:36 +00:00
org_id : & str ,
org_user_id : & str ,
headers : & AdminHeaders ,
2022-05-20 21:39:47 +00:00
conn : & mut DbConn ,
2022-11-20 18:15:45 +00:00
ip : & ClientIp ,
2022-08-20 14:42:36 +00:00
) -> EmptyResult {
match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) . await {
Some ( mut user_org ) if user_org . status < UserOrgStatus ::Accepted as i32 = > {
if user_org . user_uuid = = headers . user . uuid {
err! ( " You cannot restore yourself " )
}
if user_org . atype = = UserOrgType ::Owner & & headers . org_user_type ! = UserOrgType ::Owner {
err! ( " Only owners can restore other owners " )
}
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if user_org . atype < UserOrgType ::Admin {
match OrgPolicy ::is_user_allowed ( & user_org . user_uuid , org_id , false , conn ) . await {
Ok ( _ ) = > { }
Err ( OrgPolicyErr ::TwoFactorMissing ) = > {
err! ( " You cannot restore this user because it has no two-step login method activated " ) ;
}
Err ( OrgPolicyErr ::SingleOrgEnforced ) = > {
err! ( " You cannot restore this user because it is a member of an organization which forbids it " ) ;
}
}
}
2022-09-12 14:08:36 +00:00
user_org . restore ( ) ;
2022-08-20 14:42:36 +00:00
user_org . save ( conn ) . await ? ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserRestored as i32 ,
& user_org . uuid ,
org_id . to_string ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
conn ,
)
. await ;
2022-08-20 14:42:36 +00:00
}
Some ( _ ) = > err! ( " User is already active " ) ,
None = > err! ( " User not found in organization " ) ,
}
Ok ( ( ) )
}
2022-09-24 16:27:13 +00:00
2022-10-20 13:31:53 +00:00
#[ get( " /organizations/<org_id>/groups " ) ]
2022-11-23 14:47:45 +00:00
async fn get_groups ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
2022-05-20 21:39:47 +00:00
let groups = Group ::find_by_organization ( & org_id , & mut conn ) . await . iter ( ) . map ( Group ::to_json ) . collect ::< Value > ( ) ;
2022-10-20 13:31:53 +00:00
Ok ( Json ( json! ( {
" Data " : groups ,
" Object " : " list " ,
" ContinuationToken " : null ,
} ) ) )
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct GroupRequest {
Name : String ,
AccessAll : Option < bool > ,
ExternalId : Option < String > ,
Collections : Vec < SelectionReadOnly > ,
}
impl GroupRequest {
pub fn to_group ( & self , organizations_uuid : & str ) -> ApiResult < Group > {
match self . AccessAll {
Some ( access_all_value ) = > Ok ( Group ::new (
organizations_uuid . to_owned ( ) ,
self . Name . clone ( ) ,
access_all_value ,
self . ExternalId . clone ( ) ,
) ) ,
_ = > err! ( " Could not convert GroupRequest to Group, because AccessAll has no value! " ) ,
}
}
pub fn update_group ( & self , mut group : Group ) -> ApiResult < Group > {
match self . AccessAll {
Some ( access_all_value ) = > {
group . name = self . Name . clone ( ) ;
group . access_all = access_all_value ;
group . set_external_id ( self . ExternalId . clone ( ) ) ;
Ok ( group )
}
_ = > err! ( " Could not update group, because AccessAll has no value! " ) ,
}
}
}
#[ derive(Deserialize, Serialize) ]
#[ allow(non_snake_case) ]
struct SelectionReadOnly {
Id : String ,
ReadOnly : bool ,
HidePasswords : bool ,
}
impl SelectionReadOnly {
pub fn to_collection_group ( & self , groups_uuid : String ) -> CollectionGroup {
CollectionGroup ::new ( self . Id . clone ( ) , groups_uuid , self . ReadOnly , self . HidePasswords )
}
pub fn to_group_details_read_only ( collection_group : & CollectionGroup ) -> SelectionReadOnly {
SelectionReadOnly {
Id : collection_group . collections_uuid . clone ( ) ,
ReadOnly : collection_group . read_only ,
HidePasswords : collection_group . hide_passwords ,
}
}
pub fn to_collection_group_details_read_only ( collection_group : & CollectionGroup ) -> SelectionReadOnly {
SelectionReadOnly {
Id : collection_group . groups_uuid . clone ( ) ,
ReadOnly : collection_group . read_only ,
HidePasswords : collection_group . hide_passwords ,
}
}
pub fn to_json ( & self ) -> Value {
json! ( self )
}
}
2022-11-20 18:15:45 +00:00
#[ post( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
2022-10-20 13:31:53 +00:00
async fn post_group (
2022-11-20 18:15:45 +00:00
org_id : String ,
2022-10-20 13:31:53 +00:00
group_id : String ,
data : JsonUpcase < GroupRequest > ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-10-20 13:31:53 +00:00
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> JsonResult {
2022-11-20 18:15:45 +00:00
put_group ( org_id , group_id , data , headers , conn , ip ) . await
2022-10-20 13:31:53 +00:00
}
#[ post( " /organizations/<org_id>/groups " , data = " <data> " ) ]
async fn post_groups (
org_id : String ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-10-20 13:31:53 +00:00
data : JsonUpcase < GroupRequest > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> JsonResult {
let group_request = data . into_inner ( ) . data ;
let group = group_request . to_group ( & org_id ) ? ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::GroupCreated as i32 ,
& group . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
add_update_group ( group , group_request . Collections , & mut conn ) . await
2022-10-20 13:31:53 +00:00
}
2022-11-20 18:15:45 +00:00
#[ put( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
2022-10-20 13:31:53 +00:00
async fn put_group (
2022-11-20 18:15:45 +00:00
org_id : String ,
2022-10-20 13:31:53 +00:00
group_id : String ,
data : JsonUpcase < GroupRequest > ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> JsonResult {
2022-05-20 21:39:47 +00:00
let group = match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( group ) = > group ,
None = > err! ( " Group not found " ) ,
} ;
let group_request = data . into_inner ( ) . data ;
let updated_group = group_request . update_group ( group ) ? ;
2022-05-20 21:39:47 +00:00
CollectionGroup ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
2022-11-20 18:15:45 +00:00
log_event (
EventType ::GroupUpdated as i32 ,
& updated_group . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
add_update_group ( updated_group , group_request . Collections , & mut conn ) . await
2022-10-20 13:31:53 +00:00
}
2022-05-20 21:39:47 +00:00
async fn add_update_group ( mut group : Group , collections : Vec < SelectionReadOnly > , conn : & mut DbConn ) -> JsonResult {
2022-10-20 13:31:53 +00:00
group . save ( conn ) . await ? ;
for selection_read_only_request in collections {
let mut collection_group = selection_read_only_request . to_collection_group ( group . uuid . clone ( ) ) ;
collection_group . save ( conn ) . await ? ;
}
Ok ( Json ( json! ( {
" Id " : group . uuid ,
" OrganizationId " : group . organizations_uuid ,
" Name " : group . name ,
" AccessAll " : group . access_all ,
" ExternalId " : group . get_external_id ( )
} ) ) )
}
#[ get( " /organizations/<_org_id>/groups/<group_id>/details " ) ]
2022-05-20 21:39:47 +00:00
async fn get_group_details ( _org_id : String , group_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
let group = match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( group ) = > group ,
_ = > err! ( " Group could not be found! " ) ,
} ;
2022-05-20 21:39:47 +00:00
let collections_groups = CollectionGroup ::find_by_group ( & group_id , & mut conn )
2022-10-20 13:31:53 +00:00
. await
. iter ( )
. map ( | entry | SelectionReadOnly ::to_group_details_read_only ( entry ) . to_json ( ) )
. collect ::< Value > ( ) ;
Ok ( Json ( json! ( {
" Id " : group . uuid ,
" OrganizationId " : group . organizations_uuid ,
" Name " : group . name ,
" AccessAll " : group . access_all ,
" ExternalId " : group . get_external_id ( ) ,
" Collections " : collections_groups
} ) ) )
}
#[ post( " /organizations/<org_id>/groups/<group_id>/delete " ) ]
2022-11-20 18:15:45 +00:00
async fn post_delete_group (
org_id : String ,
group_id : String ,
headers : AdminHeaders ,
conn : DbConn ,
ip : ClientIp ,
) -> EmptyResult {
delete_group ( org_id , group_id , headers , conn , ip ) . await
2022-10-20 13:31:53 +00:00
}
2022-11-20 18:15:45 +00:00
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
async fn delete_group (
org_id : String ,
group_id : String ,
headers : AdminHeaders ,
mut conn : DbConn ,
ip : ClientIp ,
) -> EmptyResult {
2022-05-20 21:39:47 +00:00
let group = match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( group ) = > group ,
_ = > err! ( " Group not found " ) ,
} ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::GroupDeleted as i32 ,
& group . uuid ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-05-20 21:39:47 +00:00
group . delete ( & mut conn ) . await
2022-10-20 13:31:53 +00:00
}
#[ get( " /organizations/<_org_id>/groups/<group_id> " ) ]
2022-05-20 21:39:47 +00:00
async fn get_group ( _org_id : String , group_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
let group = match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( group ) = > group ,
_ = > err! ( " Group not found " ) ,
} ;
Ok ( Json ( group . to_json ( ) ) )
}
#[ get( " /organizations/<_org_id>/groups/<group_id>/users " ) ]
2022-05-20 21:39:47 +00:00
async fn get_group_users ( _org_id : String , group_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " Group could not be found! " ) ,
} ;
2022-05-20 21:39:47 +00:00
let group_users : Vec < String > = GroupUser ::find_by_group ( & group_id , & mut conn )
2022-10-20 13:31:53 +00:00
. await
. iter ( )
. map ( | entry | entry . users_organizations_uuid . clone ( ) )
. collect ( ) ;
Ok ( Json ( json! ( group_users ) ) )
}
2022-11-20 18:15:45 +00:00
#[ put( " /organizations/<org_id>/groups/<group_id>/users " , data = " <data> " ) ]
2022-10-20 13:31:53 +00:00
async fn put_group_users (
2022-11-20 18:15:45 +00:00
org_id : String ,
2022-10-20 13:31:53 +00:00
group_id : String ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-10-20 13:31:53 +00:00
data : JsonVec < String > ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> EmptyResult {
2022-05-20 21:39:47 +00:00
match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " Group could not be found! " ) ,
} ;
2022-05-20 21:39:47 +00:00
GroupUser ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
let assigned_user_ids = data . into_inner ( ) ;
for assigned_user_id in assigned_user_ids {
2022-11-20 18:15:45 +00:00
let mut user_entry = GroupUser ::new ( group_id . clone ( ) , assigned_user_id . clone ( ) ) ;
2022-05-20 21:39:47 +00:00
user_entry . save ( & mut conn ) . await ? ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
& assigned_user_id ,
org_id . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-10-20 13:31:53 +00:00
}
Ok ( ( ) )
}
#[ get( " /organizations/<_org_id>/users/<user_id>/groups " ) ]
2022-05-20 21:39:47 +00:00
async fn get_user_groups ( _org_id : String , user_id : String , _headers : AdminHeaders , mut conn : DbConn ) -> JsonResult {
match UserOrganization ::find_by_uuid ( & user_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " User could not be found! " ) ,
} ;
let user_groups : Vec < String > =
2022-05-20 21:39:47 +00:00
GroupUser ::find_by_user ( & user_id , & mut conn ) . await . iter ( ) . map ( | entry | entry . groups_uuid . clone ( ) ) . collect ( ) ;
2022-10-20 13:31:53 +00:00
Ok ( Json ( json! ( user_groups ) ) )
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct OrganizationUserUpdateGroupsRequest {
GroupIds : Vec < String > ,
}
2022-11-20 18:15:45 +00:00
#[ post( " /organizations/<org_id>/users/<org_user_id>/groups " , data = " <data> " ) ]
2022-10-20 13:31:53 +00:00
async fn post_user_groups (
2022-11-20 18:15:45 +00:00
org_id : String ,
org_user_id : String ,
2022-10-20 13:31:53 +00:00
data : JsonUpcase < OrganizationUserUpdateGroupsRequest > ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-10-20 13:31:53 +00:00
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
put_user_groups ( org_id , org_user_id , data , headers , conn , ip ) . await
2022-10-20 13:31:53 +00:00
}
2022-11-20 18:15:45 +00:00
#[ put( " /organizations/<org_id>/users/<org_user_id>/groups " , data = " <data> " ) ]
2022-10-20 13:31:53 +00:00
async fn put_user_groups (
2022-11-20 18:15:45 +00:00
org_id : String ,
org_user_id : String ,
2022-10-20 13:31:53 +00:00
data : JsonUpcase < OrganizationUserUpdateGroupsRequest > ,
2022-11-20 18:15:45 +00:00
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
match UserOrganization ::find_by_uuid ( & org_user_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " User could not be found! " ) ,
} ;
2022-11-20 18:15:45 +00:00
GroupUser ::delete_all_by_user ( & org_user_id , & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
let assigned_group_ids = data . into_inner ( ) . data ;
for assigned_group_id in assigned_group_ids . GroupIds {
2022-11-20 18:15:45 +00:00
let mut group_user = GroupUser ::new ( assigned_group_id . clone ( ) , org_user_id . clone ( ) ) ;
2022-05-20 21:39:47 +00:00
group_user . save ( & mut conn ) . await ? ;
2022-10-20 13:31:53 +00:00
}
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
& org_user_id ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
2022-10-20 13:31:53 +00:00
Ok ( ( ) )
}
2022-11-20 18:15:45 +00:00
#[ post( " /organizations/<org_id>/groups/<group_id>/delete-user/<org_user_id> " ) ]
2022-10-20 13:31:53 +00:00
async fn post_delete_group_user (
org_id : String ,
group_id : String ,
2022-11-20 18:15:45 +00:00
org_user_id : String ,
2022-10-20 13:31:53 +00:00
headers : AdminHeaders ,
conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
delete_group_user ( org_id , group_id , org_user_id , headers , conn , ip ) . await
2022-10-20 13:31:53 +00:00
}
2022-11-20 18:15:45 +00:00
#[ delete( " /organizations/<org_id>/groups/<group_id>/users/<org_user_id> " ) ]
2022-10-20 13:31:53 +00:00
async fn delete_group_user (
2022-11-20 18:15:45 +00:00
org_id : String ,
2022-10-20 13:31:53 +00:00
group_id : String ,
2022-11-20 18:15:45 +00:00
org_user_id : String ,
headers : AdminHeaders ,
2022-05-20 21:39:47 +00:00
mut conn : DbConn ,
2022-11-20 18:15:45 +00:00
ip : ClientIp ,
2022-10-20 13:31:53 +00:00
) -> EmptyResult {
2022-11-20 18:15:45 +00:00
match UserOrganization ::find_by_uuid ( & org_user_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " User could not be found! " ) ,
} ;
2022-05-20 21:39:47 +00:00
match Group ::find_by_uuid ( & group_id , & mut conn ) . await {
2022-10-20 13:31:53 +00:00
Some ( _ ) = > { /* Do nothing */ }
_ = > err! ( " Group could not be found! " ) ,
} ;
2022-11-20 18:15:45 +00:00
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
& org_user_id ,
org_id ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
& mut conn ,
)
. await ;
GroupUser ::delete_by_group_id_and_user_id ( & group_id , & org_user_id , & mut conn ) . await
2022-10-20 13:31:53 +00:00
}
2022-09-24 16:27:13 +00:00
// This is a new function active since the v2022.9.x clients.
// It combines the previous two calls done before.
// We call those two functions here and combine them our selfs.
//
// 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 " ) ]
2022-05-20 21:39:47 +00:00
async fn get_org_export ( org_id : String , headers : AdminHeaders , mut conn : DbConn ) -> Json < Value > {
2022-11-07 16:13:34 +00:00
use semver ::{ Version , VersionReq } ;
// Since version v2022.11.0 the format of the export is different.
// Also, this endpoint was created since v2022.9.0.
// Therefore, we will check for any version smaller then 2022.11.0 and return a different response.
// If we can't determine the version, we will use the latest default v2022.11.0 and higher.
// https://github.com/bitwarden/server/blob/8a6f780d55cf0768e1869f1f097452328791983e/src/Api/Controllers/OrganizationExportController.cs#L44-L45
let use_list_response_model = if let Some ( client_version ) = headers . client_version {
let ver_match = VersionReq ::parse ( " <2022.11.0 " ) . unwrap ( ) ;
let client_version = Version ::parse ( & client_version ) . unwrap ( ) ;
ver_match . matches ( & client_version )
} else {
false
} ;
2022-09-24 16:27:13 +00:00
// Also both main keys here need to be lowercase, else the export will fail.
2022-11-07 16:13:34 +00:00
if use_list_response_model {
// Backwards compatible pre v2022.11.0 response
Json ( json! ( {
" collections " : {
" data " : convert_json_key_lcase_first ( _get_org_collections ( & org_id , & mut conn ) . await ) ,
" object " : " list " ,
" continuationToken " : null ,
} ,
" ciphers " : {
" data " : convert_json_key_lcase_first ( _get_org_details ( & org_id , & headers . host , & headers . user . uuid , & mut conn ) . await ) ,
" object " : " list " ,
" continuationToken " : null ,
}
} ) )
} else {
// v2022.11.0 and newer response
Json ( json! ( {
" 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 ) ,
} ) )
}
2022-09-24 16:27:13 +00:00
}