2020-07-14 12:00:09 -04:00
use num_traits ::FromPrimitive ;
use rocket ::{ request ::Form , Route } ;
2018-10-10 14:40:39 -04:00
use rocket_contrib ::json ::Json ;
use serde_json ::Value ;
2020-07-14 12:00:09 -04:00
use crate ::{
api ::{ EmptyResult , JsonResult , JsonUpcase , JsonUpcaseVec , Notify , NumberOrString , PasswordData , UpdateType } ,
2021-03-31 16:18:35 -04:00
auth ::{ decode_invite , AdminHeaders , Headers , ManagerHeaders , ManagerHeadersLoose , OwnerHeaders } ,
2020-07-14 12:00:09 -04:00
db ::{ models ::* , DbConn } ,
mail , CONFIG ,
2019-01-25 11:43:51 -05:00
} ;
2018-10-10 14:40:39 -04: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 11:43:51 -05:00
put_collection_users ,
2018-10-10 14:40:39 -04: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-29 23:24:38 -05:00
reinvite_user ,
2021-09-18 08:22:14 -04:00
bulk_reinvite_user ,
2018-10-10 14:40:39 -04:00
confirm_invite ,
2021-09-18 08:22:14 -04:00
bulk_confirm_invite ,
2018-12-14 21:56:00 -05:00
accept_invite ,
2018-10-10 14:40:39 -04:00
get_user ,
edit_user ,
put_organization_user ,
delete_user ,
2021-09-18 08:22:14 -04:00
bulk_delete_user ,
2018-10-10 14:40:39 -04:00
post_delete_user ,
post_org_import ,
2020-03-14 08:22:30 -04:00
list_policies ,
2020-03-20 05:51:17 -04:00
list_policies_token ,
2020-03-14 08:22:30 -04:00
get_policy ,
put_policy ,
2021-01-31 15:46:37 -05:00
get_organization_tax ,
2020-09-14 02:34:17 -04:00
get_plans ,
2021-01-31 15:46:37 -05:00
get_plans_tax_rates ,
2021-02-06 12:22:39 -05: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 17:02:56 -04:00
post_org_keys ,
2021-09-18 08:22:14 -04:00
bulk_public_keys ,
2018-10-10 14:40:39 -04:00
]
}
2018-04-24 16:01:55 -04:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct OrgData {
2018-05-31 18:18:50 -04: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 17:02:56 -04:00
Keys : Option < OrgKeyData > ,
2018-05-31 18:18:50 -04:00
#[ serde(rename = " PlanType " ) ]
2018-07-21 11:27:00 -04:00
_PlanType : NumberOrString , // Ignored, always use the same plan
2018-04-24 16:01:55 -04:00
}
2018-04-20 12:35:11 -04:00
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrganizationUpdateData {
2018-05-31 18:18:50 -04:00
BillingEmail : String ,
Name : String ,
2018-04-20 12:35:11 -04:00
}
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct NewCollectionData {
2018-05-31 18:18:50 -04:00
Name : String ,
2018-04-20 12:35:11 -04: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 17:02:56 -04:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct OrgKeyData {
EncryptedPrivateKey : String ,
PublicKey : String ,
}
2021-09-18 08:22:14 -04:00
#[ derive(Deserialize, Debug) ]
#[ allow(non_snake_case) ]
struct OrgBulkIds {
Ids : Vec < String > ,
}
2018-02-17 16:30:19 -05:00
#[ post( " /organizations " , data = " <data> " ) ]
2018-05-31 18:18:50 -04:00
fn create_organization ( headers : Headers , data : JsonUpcase < OrgData > , conn : DbConn ) -> JsonResult {
2020-08-06 01:35:29 -04:00
if ! CONFIG . is_org_creation_allowed ( & headers . user . email ) {
err! ( " User not allowed to create organizations " )
}
2021-09-24 11:55:49 -04:00
if OrgPolicy ::is_applicable_to_user ( & headers . user . uuid , OrgPolicyType ::SingleOrg , & conn ) {
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 01:35:29 -04:00
2018-05-31 18:18:50 -04: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 17:02:56 -04: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 16:30:19 -05: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 17:02:56 -04:00
let org = Organization ::new ( data . Name , data . BillingEmail , private_key , public_key ) ;
2019-11-02 12:39:01 -04:00
let mut user_org = UserOrganization ::new ( headers . user . uuid , org . uuid . clone ( ) ) ;
2019-02-22 14:25:50 -05:00
let collection = Collection ::new ( org . uuid . clone ( ) , data . CollectionName ) ;
2018-02-17 16:30:19 -05:00
2019-05-20 15:24:29 -04:00
user_org . akey = data . Key ;
2018-04-24 16:01:55 -04:00
user_org . access_all = true ;
2019-05-20 15:24:29 -04:00
user_org . atype = UserOrgType ::Owner as i32 ;
2018-04-24 16:01:55 -04:00
user_org . status = UserOrgStatus ::Confirmed as i32 ;
2018-12-19 15:52:53 -05:00
org . save ( & conn ) ? ;
user_org . save ( & conn ) ? ;
collection . save ( & conn ) ? ;
2018-04-24 16:01:55 -04:00
Ok ( Json ( org . to_json ( ) ) )
2018-02-17 16:30:19 -05:00
}
2018-08-13 11:45:30 -04:00
#[ delete( " /organizations/<org_id> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn delete_organization (
org_id : String ,
data : JsonUpcase < PasswordData > ,
headers : OwnerHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-05-31 18:18:50 -04:00
let data : PasswordData = data . into_inner ( ) . data ;
let password_hash = data . MasterPasswordHash ;
2018-04-24 18:34:40 -04:00
2018-05-18 11:52:51 -04:00
if ! headers . user . check_valid_password ( & password_hash ) {
err! ( " Invalid password " )
}
match Organization ::find_by_uuid ( & org_id , & conn ) {
None = > err! ( " Organization not found " ) ,
2018-12-30 17:34:31 -05:00
Some ( org ) = > org . delete ( & conn ) ,
2018-05-18 11:52:51 -04:00
}
2018-04-24 18:34:40 -04:00
}
2018-08-13 11:45:30 -04:00
#[ post( " /organizations/<org_id>/delete " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_delete_organization (
org_id : String ,
data : JsonUpcase < PasswordData > ,
headers : OwnerHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-08-13 11:45:30 -04:00
delete_organization ( org_id , data , headers , conn )
}
2018-07-11 10:30:03 -04:00
#[ post( " /organizations/<org_id>/leave " ) ]
fn leave_organization ( org_id : String , headers : Headers , conn : DbConn ) -> EmptyResult {
match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & conn ) {
None = > err! ( " User not part of organization " ) ,
Some ( user_org ) = > {
2019-05-20 15:24:29 -04:00
if user_org . atype = = UserOrgType ::Owner {
2018-12-30 17:34:31 -05:00
let num_owners =
UserOrganization ::find_by_org_and_type ( & org_id , UserOrgType ::Owner as i32 , & conn ) . len ( ) ;
2018-07-11 10:30:03 -04:00
if num_owners < = 1 {
err! ( " The last owner can't leave " )
}
}
2018-12-30 17:34:31 -05:00
2018-12-19 15:52:53 -05:00
user_org . delete ( & conn )
2018-07-11 10:30:03 -04:00
}
}
}
2018-04-20 12:35:11 -04:00
#[ get( " /organizations/<org_id> " ) ]
2018-05-30 16:30:45 -04:00
fn get_organization ( org_id : String , _headers : OwnerHeaders , conn : DbConn ) -> JsonResult {
2018-04-20 12:35:11 -04:00
match Organization ::find_by_uuid ( & org_id , & conn ) {
Some ( organization ) = > Ok ( Json ( organization . to_json ( ) ) ) ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 12:35:11 -04:00
}
}
2018-08-21 08:25:52 -04:00
#[ put( " /organizations/<org_id> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn put_organization (
org_id : String ,
headers : OwnerHeaders ,
data : JsonUpcase < OrganizationUpdateData > ,
conn : DbConn ,
) -> JsonResult {
2018-08-21 08:25:52 -04:00
post_organization ( org_id , headers , data , conn )
}
2018-04-20 12:35:11 -04:00
#[ post( " /organizations/<org_id> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_organization (
org_id : String ,
_headers : OwnerHeaders ,
data : JsonUpcase < OrganizationUpdateData > ,
conn : DbConn ,
) -> JsonResult {
2018-05-31 18:18:50 -04:00
let data : OrganizationUpdateData = data . into_inner ( ) . data ;
2018-04-20 12:35:11 -04:00
let mut org = match Organization ::find_by_uuid ( & org_id , & conn ) {
Some ( organization ) = > organization ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 12:35:11 -04:00
} ;
2018-05-31 18:18:50 -04:00
org . name = data . Name ;
org . billing_email = data . BillingEmail ;
2018-04-20 12:35:11 -04:00
2018-12-19 15:52:53 -05:00
org . save ( & conn ) ? ;
Ok ( Json ( org . to_json ( ) ) )
2018-04-20 12:35:11 -04:00
}
2018-02-17 16:30:19 -05:00
// GET /api/collections?writeOnly=false
#[ get( " /collections " ) ]
2021-03-27 11:07:26 -04:00
fn get_user_collections ( headers : Headers , conn : DbConn ) -> Json < Value > {
Json ( json! ( {
2018-04-20 12:35:11 -04:00
" Data " :
Collection ::find_by_user_uuid ( & headers . user . uuid , & conn )
. iter ( )
2018-09-13 09:16:24 -04:00
. map ( Collection ::to_json )
. collect ::< Value > ( ) ,
2018-10-01 12:02:58 -04:00
" Object " : " list " ,
" ContinuationToken " : null ,
2021-03-27 11:07:26 -04:00
} ) )
2018-02-17 16:30:19 -05:00
}
#[ get( " /organizations/<org_id>/collections " ) ]
2021-03-27 11:07:26 -04:00
fn get_org_collections ( org_id : String , _headers : AdminHeaders , conn : DbConn ) -> Json < Value > {
Json ( json! ( {
2018-04-20 12:35:11 -04:00
" Data " :
2018-05-11 14:08:02 -04:00
Collection ::find_by_organization ( & org_id , & conn )
2018-04-20 12:35:11 -04:00
. iter ( )
2018-09-13 09:16:24 -04:00
. map ( Collection ::to_json )
. collect ::< Value > ( ) ,
2018-10-01 12:02:58 -04:00
" Object " : " list " ,
" ContinuationToken " : null ,
2021-03-27 11:07:26 -04:00
} ) )
2018-02-17 16:30:19 -05:00
}
2018-04-20 12:35:11 -04:00
#[ post( " /organizations/<org_id>/collections " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_organization_collections (
org_id : String ,
2020-12-02 16:50:51 -05:00
headers : ManagerHeadersLoose ,
2018-12-30 17:34:31 -05:00
data : JsonUpcase < NewCollectionData > ,
conn : DbConn ,
) -> JsonResult {
2018-05-31 18:18:50 -04:00
let data : NewCollectionData = data . into_inner ( ) . data ;
2018-04-20 12:35:11 -04:00
let org = match Organization ::find_by_uuid ( & org_id , & conn ) {
Some ( organization ) = > organization ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 12:35:11 -04:00
} ;
2020-12-02 16:50:51 -05:00
// Get the user_organization record so that we can check if the user has access to all collections.
let user_org = match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & conn ) {
Some ( u ) = > u ,
None = > err! ( " User is not part of organization " ) ,
} ;
2019-11-02 12:39:01 -04:00
let collection = Collection ::new ( org . uuid , data . Name ) ;
2018-12-19 15:52:53 -05:00
collection . save ( & conn ) ? ;
2018-05-04 13:25:50 -04:00
2020-12-02 16:50:51 -05: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 {
CollectionUser ::save ( & headers . user . uuid , & collection . uuid , false , false , & conn ) ? ;
}
2018-04-20 12:35:11 -04:00
Ok ( Json ( collection . to_json ( ) ) )
}
2018-08-13 11:45:30 -04:00
#[ put( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn put_organization_collection_update (
org_id : String ,
col_id : String ,
2020-12-02 16:50:51 -05:00
headers : ManagerHeaders ,
2018-12-30 17:34:31 -05:00
data : JsonUpcase < NewCollectionData > ,
conn : DbConn ,
) -> JsonResult {
2018-08-13 11:45:30 -04:00
post_organization_collection_update ( org_id , col_id , headers , data , conn )
}
2018-04-20 12:35:11 -04:00
#[ post( " /organizations/<org_id>/collections/<col_id> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_organization_collection_update (
org_id : String ,
col_id : String ,
2020-12-02 16:50:51 -05:00
_headers : ManagerHeaders ,
2018-12-30 17:34:31 -05:00
data : JsonUpcase < NewCollectionData > ,
conn : DbConn ,
) -> JsonResult {
2018-05-31 18:18:50 -04:00
let data : NewCollectionData = data . into_inner ( ) . data ;
2018-04-20 12:35:11 -04:00
let org = match Organization ::find_by_uuid ( & org_id , & conn ) {
Some ( organization ) = > organization ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Can't find organization details " ) ,
2018-04-20 12:35:11 -04:00
} ;
let mut collection = match Collection ::find_by_uuid ( & col_id , & conn ) {
Some ( collection ) = > collection ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Collection not found " ) ,
2018-04-20 12:35:11 -04:00
} ;
2018-05-30 16:30:45 -04:00
if collection . org_uuid ! = org . uuid {
err! ( " Collection is not owned by organization " ) ;
}
2019-11-02 12:39:01 -04:00
collection . name = data . Name ;
2018-12-19 15:52:53 -05:00
collection . save ( & conn ) ? ;
2018-04-20 12:35:11 -04:00
Ok ( Json ( collection . to_json ( ) ) )
}
2018-08-13 11:45:30 -04:00
#[ delete( " /organizations/<org_id>/collections/<col_id>/user/<org_user_id> " ) ]
2018-12-30 17:34:31 -05:00
fn delete_organization_collection_user (
org_id : String ,
col_id : String ,
org_user_id : String ,
_headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-05-30 11:01:56 -04:00
let collection = match Collection ::find_by_uuid ( & col_id , & conn ) {
None = > err! ( " Collection not found " ) ,
2018-12-30 17:34:31 -05:00
Some ( collection ) = > {
if collection . org_uuid = = org_id {
collection
} else {
err! ( " Collection and Organization id do not match " )
}
2018-05-30 11:01:56 -04:00
}
} ;
2018-09-04 08:37:44 -04:00
match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & conn ) {
2018-05-30 11:01:56 -04:00
None = > err! ( " User not found in organization " ) ,
Some ( user_org ) = > {
match CollectionUser ::find_by_collection_and_user ( & collection . uuid , & user_org . user_uuid , & conn ) {
None = > err! ( " User not assigned to collection " ) ,
2018-12-30 17:34:31 -05:00
Some ( col_user ) = > col_user . delete ( & conn ) ,
2018-05-29 11:01:38 -04:00
}
}
}
}
2018-08-13 11:45:30 -04:00
#[ post( " /organizations/<org_id>/collections/<col_id>/delete-user/<org_user_id> " ) ]
2018-12-30 17:34:31 -05:00
fn post_organization_collection_delete_user (
org_id : String ,
col_id : String ,
org_user_id : String ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-08-13 11:45:30 -04:00
delete_organization_collection_user ( org_id , col_id , org_user_id , headers , conn )
2018-05-16 18:05:50 -04:00
}
2018-08-13 11:45:30 -04:00
#[ delete( " /organizations/<org_id>/collections/<col_id> " ) ]
2021-03-31 16:18:35 -04:00
fn delete_organization_collection (
org_id : String ,
col_id : String ,
_headers : ManagerHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-05-30 12:12:18 -04:00
match Collection ::find_by_uuid ( & col_id , & conn ) {
None = > err! ( " Collection not found " ) ,
2018-12-30 17:34:31 -05:00
Some ( collection ) = > {
if collection . org_uuid = = org_id {
collection . delete ( & conn )
} else {
err! ( " Collection and Organization id do not match " )
}
2018-05-16 18:05:50 -04:00
}
}
}
2018-08-13 11:45:30 -04:00
#[ derive(Deserialize, Debug) ]
2021-09-22 15:39:31 -04:00
#[ allow(non_snake_case, dead_code) ]
2018-08-13 11:45:30 -04:00
struct DeleteCollectionData {
Id : String ,
OrgId : String ,
}
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " , data = " <_data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_organization_collection_delete (
org_id : String ,
col_id : String ,
2020-12-02 16:50:51 -05:00
headers : ManagerHeaders ,
2018-12-30 17:34:31 -05:00
_data : JsonUpcase < DeleteCollectionData > ,
conn : DbConn ,
) -> EmptyResult {
2018-08-13 11:45:30 -04:00
delete_organization_collection ( org_id , col_id , headers , conn )
}
2018-04-20 12:35:11 -04:00
#[ get( " /organizations/<org_id>/collections/<coll_id>/details " ) ]
2020-12-02 16:50:51 -05:00
fn get_org_collection_detail ( org_id : String , coll_id : String , headers : ManagerHeaders , conn : DbConn ) -> JsonResult {
2018-04-20 12:35:11 -04:00
match Collection ::find_by_uuid_and_user ( & coll_id , & headers . user . uuid , & conn ) {
None = > err! ( " Collection not found " ) ,
2018-05-30 16:30:45 -04:00
Some ( collection ) = > {
if collection . org_uuid ! = org_id {
err! ( " Collection is not owned by organization " )
}
Ok ( Json ( collection . to_json ( ) ) )
}
2018-04-20 12:35:11 -04:00
}
}
2018-04-24 18:34:40 -04:00
#[ get( " /organizations/<org_id>/collections/<coll_id>/users " ) ]
2020-12-02 16:50:51 -05:00
fn get_collection_users ( org_id : String , coll_id : String , _headers : ManagerHeaders , conn : DbConn ) -> JsonResult {
2018-04-24 18:34:40 -04:00
// Get org and collection, check that collection is from org
2018-05-29 11:01:38 -04:00
let collection = match Collection ::find_by_uuid_and_org ( & coll_id , & org_id , & conn ) {
None = > err! ( " Collection not found in Organization " ) ,
2018-12-30 17:34:31 -05:00
Some ( collection ) = > collection ,
2018-05-29 11:01:38 -04:00
} ;
2018-04-24 18:34:40 -04:00
// Get the users from collection
2018-05-29 11:01:38 -04:00
let user_list : Vec < Value > = CollectionUser ::find_by_collection ( & collection . uuid , & conn )
2018-12-30 17:34:31 -05:00
. iter ( )
. map ( | col_user | {
UserOrganization ::find_by_user_and_org ( & col_user . user_uuid , & org_id , & conn )
. unwrap ( )
2021-06-19 16:02:03 -04:00
. to_json_user_access_restrictions ( col_user )
2018-12-30 17:34:31 -05:00
} )
. collect ( ) ;
2018-04-24 18:34:40 -04:00
2019-01-25 09:18:06 -05:00
Ok ( Json ( json! ( user_list ) ) )
2018-04-24 18:34:40 -04:00
}
2019-01-25 11:43:51 -05:00
#[ put( " /organizations/<org_id>/collections/<coll_id>/users " , data = " <data> " ) ]
fn put_collection_users (
org_id : String ,
coll_id : String ,
data : JsonUpcaseVec < CollectionData > ,
2020-12-02 16:50:51 -05:00
_headers : ManagerHeaders ,
2019-01-25 11:43:51 -05:00
conn : DbConn ,
) -> EmptyResult {
// Get org and collection, check that collection is from org
if Collection ::find_by_uuid_and_org ( & coll_id , & org_id , & conn ) . is_none ( ) {
err! ( " Collection not found in Organization " )
}
// Delete all the user-collections
CollectionUser ::delete_all_by_collection ( & coll_id , & conn ) ? ;
// And then add all the received ones (except if the user has access_all)
for d in data . iter ( ) . map ( | d | & d . data ) {
let user = match UserOrganization ::find_by_uuid ( & d . Id , & conn ) {
Some ( u ) = > u ,
None = > err! ( " User is not part of organization " ) ,
} ;
if user . access_all {
continue ;
}
2021-03-31 16:18:35 -04:00
CollectionUser ::save ( & user . user_uuid , & coll_id , d . ReadOnly , d . HidePasswords , & conn ) ? ;
2019-01-25 11:43:51 -05:00
}
Ok ( ( ) )
}
2018-04-24 16:01:55 -04:00
#[ derive(FromForm) ]
struct OrgIdData {
2018-10-10 14:40:39 -04:00
#[ form(field = " organizationId " ) ]
2018-12-30 17:34:31 -05:00
organization_id : String ,
2018-04-24 16:01:55 -04:00
}
2018-10-10 14:40:39 -04:00
#[ get( " /ciphers/organization-details?<data..> " ) ]
2021-03-27 11:07:26 -04:00
fn get_org_details ( data : Form < OrgIdData > , headers : Headers , conn : DbConn ) -> Json < Value > {
2018-10-10 14:40:39 -04:00
let ciphers = Cipher ::find_by_org ( & data . organization_id , & conn ) ;
2021-04-06 16:54:42 -04:00
let ciphers_json : Vec < Value > =
ciphers . iter ( ) . map ( | c | c . to_json ( & headers . host , & headers . user . uuid , & conn ) ) . collect ( ) ;
2018-04-24 16:01:55 -04:00
2021-03-27 11:07:26 -04:00
Json ( json! ( {
2018-04-27 07:49:34 -04:00
" Data " : ciphers_json ,
" Object " : " list " ,
2018-10-01 12:02:58 -04:00
" ContinuationToken " : null ,
2021-03-27 11:07:26 -04:00
} ) )
2018-04-24 16:01:55 -04:00
}
#[ get( " /organizations/<org_id>/users " ) ]
2021-03-27 11:07:26 -04:00
fn get_org_users ( org_id : String , _headers : ManagerHeadersLoose , conn : DbConn ) -> Json < Value > {
2018-04-24 16:01:55 -04:00
let users = UserOrganization ::find_by_org ( & org_id , & conn ) ;
2018-04-24 18:34:40 -04:00
let users_json : Vec < Value > = users . iter ( ) . map ( | c | c . to_json_user_details ( & conn ) ) . collect ( ) ;
2018-04-24 16:01:55 -04:00
2021-03-27 11:07:26 -04:00
Json ( json! ( {
2018-04-24 16:01:55 -04:00
" Data " : users_json ,
2018-10-01 12:02:58 -04:00
" Object " : " list " ,
" ContinuationToken " : null ,
2021-03-27 11:07:26 -04:00
} ) )
2018-04-24 16:01:55 -04:00
}
2018-02-17 16:30:19 -05: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 17:02:56 -04:00
#[ post( " /organizations/<org_id>/keys " , data = " <data> " ) ]
fn post_org_keys ( org_id : String , data : JsonUpcase < OrgKeyData > , _headers : AdminHeaders , conn : DbConn ) -> JsonResult {
let data : OrgKeyData = data . into_inner ( ) . data ;
let mut org = match Organization ::find_by_uuid ( & org_id , & conn ) {
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 ) ;
org . save ( & conn ) ? ;
Ok ( Json ( json! ( {
" Object " : " organizationKeys " ,
" PublicKey " : org . public_key ,
" PrivateKey " : org . private_key ,
} ) ) )
}
2018-04-24 16:01:55 -04:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
2018-04-24 18:34:40 -04:00
struct CollectionData {
2018-06-13 08:25:50 -04:00
Id : String ,
ReadOnly : bool ,
2020-07-03 00:51:20 -04:00
HidePasswords : bool ,
2018-04-24 16:01:55 -04:00
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct InviteData {
2018-05-31 18:18:50 -04:00
Emails : Vec < String > ,
Type : NumberOrString ,
2019-02-08 13:12:08 -05:00
Collections : Option < Vec < CollectionData > > ,
2018-05-31 18:18:50 -04:00
AccessAll : Option < bool > ,
2018-04-24 16:01:55 -04:00
}
#[ post( " /organizations/<org_id>/users/invite " , data = " <data> " ) ]
2018-05-31 18:18:50 -04:00
fn send_invite ( org_id : String , data : JsonUpcase < InviteData > , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
let data : InviteData = data . into_inner ( ) . data ;
2018-04-24 16:01:55 -04:00
2018-06-11 09:44:37 -04:00
let new_type = match UserOrgType ::from_str ( & data . Type . into_string ( ) ) {
2018-04-24 18:34:40 -04:00
Some ( new_type ) = > new_type as i32 ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Invalid type " ) ,
2018-04-24 18:34:40 -04:00
} ;
2018-12-30 17:34:31 -05:00
if new_type ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-11-12 12:13:25 -05:00
err! ( " Only Owners can invite Managers, Admins or Owners " )
2018-04-24 17:04:10 -04:00
}
2018-04-24 16:01:55 -04:00
2018-09-10 09:51:40 -04:00
for email in data . Emails . iter ( ) {
2021-09-09 07:50:18 -04:00
let email = email . to_lowercase ( ) ;
2019-02-08 12:45:07 -05:00
let mut 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
2018-12-18 23:16:03 -05:00
} ;
2021-09-09 07:50:18 -04:00
let user = match User ::find_by_mail ( & email , & conn ) {
2018-12-30 17:34:31 -05:00
None = > {
2019-01-25 12:23:51 -05:00
if ! CONFIG . invitations_allowed ( ) {
2020-04-09 04:51:05 -04:00
err! ( format! ( " User does not exist: {} " , email ) )
}
2021-09-09 07:50:18 -04:00
if ! CONFIG . is_email_domain_allowed ( & email ) {
2020-04-09 04:51:05 -04:00
err! ( " Email domain not eligible for invitations " )
2019-01-08 09:11:16 -05:00
}
2019-02-01 19:09:21 -05:00
if ! CONFIG . mail_enabled ( ) {
2019-02-22 14:25:50 -05:00
let invitation = Invitation ::new ( email . clone ( ) ) ;
2018-12-30 17:34:31 -05:00
invitation . save ( & conn ) ? ;
}
2019-01-08 09:11:16 -05:00
let mut user = User ::new ( email . clone ( ) ) ;
user . save ( & conn ) ? ;
user_org_status = UserOrgStatus ::Invited as i32 ;
user
2018-12-30 17:34:31 -05:00
}
Some ( user ) = > {
if UserOrganization ::find_by_user_and_org ( & user . uuid , & org_id , & conn ) . is_some ( ) {
err! ( format! ( " User already in organization: {} " , email ) )
} else {
user
}
2018-09-10 09:51:40 -04:00
}
} ;
2018-12-19 16:51:08 -05: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 15:24:29 -04:00
new_user . atype = new_type ;
2018-12-19 16:51:08 -05:00
new_user . status = user_org_status ;
// If no accessAll, add the collections received
if ! access_all {
2019-02-08 13:12:08 -05:00
for col in data . Collections . iter ( ) . flatten ( ) {
2018-12-19 16:51:08 -05:00
match Collection ::find_by_uuid_and_org ( & col . Id , & org_id , & conn ) {
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2021-03-31 16:18:35 -04:00
CollectionUser ::save ( & user . uuid , & collection . uuid , col . ReadOnly , col . HidePasswords , & conn ) ? ;
2018-05-04 13:25:50 -04:00
}
2018-04-24 16:01:55 -04:00
}
}
2018-10-12 10:20:10 -04:00
}
2018-12-14 21:56:00 -05:00
2018-12-19 16:51:08 -05:00
new_user . save ( & conn ) ? ;
2019-02-01 19:09:21 -05:00
if CONFIG . mail_enabled ( ) {
2018-12-14 21:56:00 -05:00
let org_name = match Organization ::find_by_uuid ( & org_id , & conn ) {
Some ( org ) = > org . name ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Error looking up organization " ) ,
2018-12-14 21:56:00 -05:00
} ;
2019-01-08 09:11:16 -05:00
2019-01-05 23:03:49 -05:00
mail ::send_invite (
2021-09-09 07:50:18 -04:00
& email ,
2019-01-08 09:11:16 -05:00
& user . uuid ,
Some ( org_id . clone ( ) ) ,
Some ( new_user . uuid ) ,
& org_name ,
Some ( headers . user . email . clone ( ) ) ,
) ? ;
2018-12-14 21:56:00 -05:00
}
}
Ok ( ( ) )
}
2021-09-18 08:22:14 -04:00
#[ post( " /organizations/<org_id>/users/reinvite " , data = " <data> " ) ]
fn bulk_reinvite_user (
org_id : String ,
data : JsonUpcase < OrgBulkIds > ,
headers : AdminHeaders ,
conn : DbConn ,
) -> Json < Value > {
let data : OrgBulkIds = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
for org_user_id in data . Ids {
let err_msg = match _reinvite_user ( & org_id , & org_user_id , & headers . user . email , & conn ) {
Ok ( _ ) = > String ::from ( " " ) ,
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 00:19:01 -05:00
#[ post( " /organizations/<org_id>/users/<user_org>/reinvite " ) ]
2019-01-02 22:20:39 -05:00
fn reinvite_user ( org_id : String , user_org : String , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
2021-09-18 08:22:14 -04:00
_reinvite_user ( & org_id , & user_org , & headers . user . email , & conn )
}
fn _reinvite_user ( org_id : & str , user_org : & str , invited_by_email : & str , conn : & DbConn ) -> EmptyResult {
2019-01-25 12:23:51 -05:00
if ! CONFIG . invitations_allowed ( ) {
2018-12-29 23:24:38 -05:00
err! ( " Invitations are not allowed. " )
}
2019-02-01 19:09:21 -05:00
if ! CONFIG . mail_enabled ( ) {
2018-12-29 23:24:38 -05:00
err! ( " SMTP is not configured. " )
}
2021-09-18 08:22:14 -04:00
let user_org = match UserOrganization ::find_by_uuid ( user_org , conn ) {
2018-12-30 00:19:01 -05:00
Some ( user_org ) = > user_org ,
2019-01-07 09:29:57 -05:00
None = > err! ( " The user hasn't been invited to the organization. " ) ,
2018-12-30 00:19:01 -05:00
} ;
2019-01-07 09:29:57 -05:00
if user_org . status ! = UserOrgStatus ::Invited as i32 {
err! ( " The user is already accepted or confirmed to the organization " )
}
2021-09-18 08:22:14 -04:00
let user = match User ::find_by_uuid ( & user_org . user_uuid , conn ) {
2018-12-29 23:24:38 -05:00
Some ( user ) = > user ,
None = > err! ( " User not found. " ) ,
} ;
2019-01-08 08:05:05 -05:00
2021-09-18 08:22:14 -04:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) {
2018-12-29 23:24:38 -05:00
Some ( org ) = > org . name ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Error looking up organization. " ) ,
2018-12-29 23:24:38 -05:00
} ;
2019-02-01 19:09:21 -05:00
if CONFIG . mail_enabled ( ) {
2019-01-04 10:32:51 -05:00
mail ::send_invite (
2019-01-08 09:11:16 -05:00
& user . email ,
& user . uuid ,
2021-09-18 08:22:14 -04:00
Some ( org_id . to_string ( ) ) ,
2019-01-08 09:11:16 -05:00
Some ( user_org . uuid ) ,
& org_name ,
2021-09-18 08:22:14 -04:00
Some ( invited_by_email . to_string ( ) ) ,
2019-01-04 10:32:51 -05:00
) ? ;
2019-01-08 09:11:16 -05:00
} else {
2019-11-02 12:39:01 -04:00
let invitation = Invitation ::new ( user . email ) ;
2021-09-18 08:22:14 -04:00
invitation . save ( conn ) ? ;
2018-12-29 23:24:38 -05:00
}
2018-12-30 17:34:31 -05:00
2018-12-29 23:24:38 -05:00
Ok ( ( ) )
}
2018-12-18 23:16:03 -05:00
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct AcceptData {
Token : String ,
}
2018-12-20 21:37:03 -05:00
#[ post( " /organizations/<_org_id>/users/<_org_user_id>/accept " , data = " <data> " ) ]
fn accept_invite ( _org_id : String , _org_user_id : String , data : JsonUpcase < AcceptData > , conn : DbConn ) -> EmptyResult {
2018-12-30 17:34:31 -05: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-18 23:16:03 -05:00
let data : AcceptData = data . into_inner ( ) . data ;
let token = & data . Token ;
2021-06-19 16:02:03 -04:00
let claims = decode_invite ( token ) ? ;
2018-12-14 21:56:00 -05:00
2018-12-18 23:16:03 -05:00
match User ::find_by_mail ( & claims . email , & conn ) {
Some ( _ ) = > {
2018-12-23 15:15:44 -05:00
Invitation ::take ( & claims . email , & conn ) ;
2019-01-08 09:11:16 -05:00
if let ( Some ( user_org ) , Some ( org ) ) = ( & claims . user_org_id , & claims . org_id ) {
let mut user_org = match UserOrganization ::find_by_uuid_and_org ( user_org , org , & conn ) {
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-14 21:56:00 -05:00
}
2019-01-08 09:11:16 -05:00
2021-04-11 22:57:17 -04:00
let user_twofactor_disabled = TwoFactor ::find_by_user ( & user_org . user_uuid , & conn ) . is_empty ( ) ;
let policy = OrgPolicyType ::TwoFactorAuthentication as i32 ;
2021-04-16 14:49:59 -04:00
let org_twofactor_policy_enabled =
match OrgPolicy ::find_by_org_and_type ( & user_org . org_uuid , policy , & conn ) {
Some ( p ) = > p . enabled ,
None = > false ,
} ;
2021-04-11 22:57:17 -04:00
if org_twofactor_policy_enabled & & user_twofactor_disabled {
2021-04-12 21:54:57 -04:00
err! ( " You cannot join this organization until you enable two-step login on your user account. " )
2021-04-11 22:57:17 -04:00
}
2021-09-24 11:55:49 -04:00
// Enforce Single Organization Policy of organization user is trying to join
let single_org_policy_enabled =
match OrgPolicy ::find_by_org_and_type ( & user_org . org_uuid , OrgPolicyType ::SingleOrg as i32 , & conn ) {
Some ( p ) = > p . enabled ,
None = > false ,
} ;
if single_org_policy_enabled & & user_org . atype < UserOrgType ::Admin {
let is_member_of_another_org = UserOrganization ::find_any_state_by_user ( & user_org . user_uuid , & conn )
. into_iter ( )
. filter ( | uo | uo . org_uuid ! = user_org . org_uuid )
. count ( )
> 1 ;
if is_member_of_another_org {
err! ( " You may not join this organization until you leave or remove all other organizations. " )
}
}
// Enforce Single Organization Policy of other organizations user is a member of
if OrgPolicy ::is_applicable_to_user ( & user_org . user_uuid , OrgPolicyType ::SingleOrg , & conn ) {
err! (
" You cannot join this organization because you are a member of an organization which forbids it "
)
}
2019-01-08 09:11:16 -05:00
user_org . status = UserOrgStatus ::Accepted as i32 ;
user_org . save ( & conn ) ? ;
2018-12-14 21:56:00 -05:00
}
2018-12-30 17:34:31 -05:00
}
None = > err! ( " Invited user not found " ) ,
2018-04-24 16:01:55 -04:00
}
2019-02-01 19:09:21 -05:00
if CONFIG . mail_enabled ( ) {
2021-03-04 02:03:55 -05:00
let mut org_name = CONFIG . invitation_org_name ( ) ;
2019-01-05 23:03:49 -05:00
if let Some ( org_id ) = & claims . org_id {
2021-06-19 16:02:03 -04:00
org_name = match Organization ::find_by_uuid ( org_id , & conn ) {
2019-01-04 10:32:51 -05:00
Some ( org ) = > org . name ,
2019-01-08 09:11:16 -05:00
None = > err! ( " Organization not found. " ) ,
2019-01-05 23:03:49 -05:00
} ;
2019-01-02 22:20:39 -05:00
} ;
2019-01-05 13:46:45 -05:00
if let Some ( invited_by_email ) = & claims . invited_by_email {
2019-01-04 10:32:51 -05:00
// User was invited to an organization, so they must be confirmed manually after acceptance
2019-02-01 19:09:21 -05:00
mail ::send_invite_accepted ( & claims . email , invited_by_email , & org_name ) ? ;
2019-01-04 10:32:51 -05:00
} else {
// User was invited from /admin, so they are automatically confirmed
2019-02-01 19:09:21 -05:00
mail ::send_invite_confirmed ( & claims . email , & org_name ) ? ;
2019-01-04 10:32:51 -05:00
}
2019-01-02 22:20:39 -05:00
}
2018-04-24 16:01:55 -04:00
Ok ( ( ) )
}
2021-09-18 08:22:14 -04:00
#[ post( " /organizations/<org_id>/users/confirm " , data = " <data> " ) ]
fn bulk_confirm_invite ( org_id : String , data : JsonUpcase < Value > , headers : AdminHeaders , conn : DbConn ) -> Json < Value > {
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 ( ) ;
let err_msg = match _confirm_invite ( & org_id , org_user_id , user_key , & headers , & conn ) {
Ok ( _ ) = > String ::from ( " " ) ,
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 06:24:53 -04:00
#[ post( " /organizations/<org_id>/users/<org_user_id>/confirm " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn confirm_invite (
org_id : String ,
org_user_id : String ,
data : JsonUpcase < Value > ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-05-31 18:18:50 -04:00
let data = data . into_inner ( ) . data ;
2021-09-18 08:22:14 -04:00
let user_key = data [ " Key " ] . as_str ( ) . unwrap_or_default ( ) ;
_confirm_invite ( & org_id , & org_user_id , user_key , & headers , & conn )
}
2018-05-31 18:18:50 -04:00
2021-09-18 08:22:14 -04:00
fn _confirm_invite ( org_id : & str , org_user_id : & str , key : & str , headers : & AdminHeaders , conn : & DbConn ) -> EmptyResult {
if key . is_empty ( ) | | org_user_id . is_empty ( ) {
err! ( " Key or UserId is not set, unable to process request " ) ;
}
let mut user_to_confirm = match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) {
2018-04-24 18:34:40 -04:00
Some ( user ) = > user ,
2018-12-30 17:34:31 -05:00
None = > err! ( " The specified user isn't a member of the organization " ) ,
2018-04-24 18:34:40 -04:00
} ;
2019-05-20 15:24:29 -04:00
if user_to_confirm . atype ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-11-12 12:13:25 -05:00
err! ( " Only Owners can confirm Managers, Admins or Owners " )
2018-04-24 17:04:10 -04:00
}
2018-04-24 16:01:55 -04:00
2018-04-24 18:34:40 -04:00
if user_to_confirm . status ! = UserOrgStatus ::Accepted as i32 {
2018-04-24 16:01:55 -04:00
err! ( " User in invalid state " )
}
2018-04-24 18:34:40 -04:00
user_to_confirm . status = UserOrgStatus ::Confirmed as i32 ;
2021-09-18 08:22:14 -04:00
user_to_confirm . akey = key . to_string ( ) ;
2018-04-24 16:01:55 -04:00
2019-02-01 19:09:21 -05:00
if CONFIG . mail_enabled ( ) {
2021-09-18 08:22:14 -04:00
let org_name = match Organization ::find_by_uuid ( org_id , conn ) {
2019-01-02 22:20:39 -05:00
Some ( org ) = > org . name ,
None = > err! ( " Error looking up organization. " ) ,
} ;
2021-09-18 08:22:14 -04:00
let address = match User ::find_by_uuid ( & user_to_confirm . user_uuid , conn ) {
2019-01-02 22:20:39 -05:00
Some ( user ) = > user . email ,
None = > err! ( " Error looking up user. " ) ,
} ;
2019-02-01 19:09:21 -05:00
mail ::send_invite_confirmed ( & address , & org_name ) ? ;
2019-01-02 22:20:39 -05:00
}
2021-09-18 08:22:14 -04:00
user_to_confirm . save ( conn )
2018-04-24 18:34:40 -04:00
}
2018-09-04 06:24:53 -04:00
#[ get( " /organizations/<org_id>/users/<org_user_id> " ) ]
fn get_user ( org_id : String , org_user_id : String , _headers : AdminHeaders , conn : DbConn ) -> JsonResult {
2018-09-04 08:37:44 -04:00
let user = match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & conn ) {
2018-04-24 18:34:40 -04:00
Some ( user ) = > user ,
2018-12-30 17:34:31 -05:00
None = > err! ( " The specified user isn't a member of the organization " ) ,
2018-04-24 18:34:40 -04:00
} ;
2018-05-11 14:08:02 -04:00
Ok ( Json ( user . to_json_details ( & conn ) ) )
2018-04-24 18:34:40 -04:00
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
struct EditUserData {
2018-05-31 18:18:50 -04:00
Type : NumberOrString ,
2019-02-08 13:12:08 -05:00
Collections : Option < Vec < CollectionData > > ,
2018-05-31 18:18:50 -04:00
AccessAll : bool ,
2018-04-24 18:34:40 -04:00
}
2018-09-04 06:24:53 -04:00
#[ put( " /organizations/<org_id>/users/<org_user_id> " , data = " <data> " , rank = 1) ]
2018-12-30 17:34:31 -05:00
fn put_organization_user (
org_id : String ,
org_user_id : String ,
data : JsonUpcase < EditUserData > ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-09-04 06:24:53 -04:00
edit_user ( org_id , org_user_id , data , headers , conn )
2018-08-13 11:45:30 -04:00
}
2018-09-04 06:24:53 -04:00
#[ post( " /organizations/<org_id>/users/<org_user_id> " , data = " <data> " , rank = 1) ]
2018-12-30 17:34:31 -05:00
fn edit_user (
org_id : String ,
org_user_id : String ,
data : JsonUpcase < EditUserData > ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
2018-05-31 18:18:50 -04:00
let data : EditUserData = data . into_inner ( ) . data ;
2018-04-24 18:34:40 -04:00
2018-06-11 09:44:37 -04:00
let new_type = match UserOrgType ::from_str ( & data . Type . into_string ( ) ) {
2018-11-12 12:13:25 -05:00
Some ( new_type ) = > new_type ,
2018-12-30 17:34:31 -05:00
None = > err! ( " Invalid type " ) ,
2018-04-24 18:34:40 -04:00
} ;
2018-09-04 06:24:53 -04:00
let mut user_to_edit = match UserOrganization ::find_by_uuid_and_org ( & org_user_id , & org_id , & conn ) {
2018-04-24 18:34:40 -04:00
Some ( user ) = > user ,
2018-12-30 17:34:31 -05:00
None = > err! ( " The specified user isn't member of the organization " ) ,
2018-04-24 18:34:40 -04:00
} ;
2019-05-20 15:24:29 -04:00
if new_type ! = user_to_edit . atype
& & ( user_to_edit . atype > = UserOrgType ::Admin | | new_type > = UserOrgType ::Admin )
2018-12-30 17:34:31 -05:00
& & headers . org_user_type ! = UserOrgType ::Owner
{
2018-09-04 06:24:53 -04:00
err! ( " Only Owners can grant and remove Admin or Owner privileges " )
2018-04-24 18:34:40 -04:00
}
2019-05-20 15:24:29 -04:00
if user_to_edit . atype = = UserOrgType ::Owner & & headers . org_user_type ! = UserOrgType ::Owner {
2018-09-04 06:24:53 -04:00
err! ( " Only Owners can edit Owner users " )
2018-04-24 18:34:40 -04:00
}
2019-05-20 15:24:29 -04:00
if user_to_edit . atype = = UserOrgType ::Owner & & new_type ! = UserOrgType ::Owner {
2018-04-24 18:34:40 -04:00
// Removing owner permmission, check that there are at least another owner
2018-12-30 17:34:31 -05:00
let num_owners = UserOrganization ::find_by_org_and_type ( & org_id , UserOrgType ::Owner as i32 , & conn ) . len ( ) ;
2018-04-24 18:34:40 -04:00
if num_owners < = 1 {
err! ( " Can't delete the last owner " )
}
}
2018-05-31 18:18:50 -04:00
user_to_edit . access_all = data . AccessAll ;
2019-05-20 15:24:29 -04:00
user_to_edit . atype = new_type as i32 ;
2018-04-24 18:34:40 -04:00
2018-05-04 13:25:50 -04:00
// Delete all the odd collections
2018-05-29 11:01:38 -04:00
for c in CollectionUser ::find_by_organization_and_user_uuid ( & org_id , & user_to_edit . user_uuid , & conn ) {
2018-12-19 15:52:53 -05:00
c . delete ( & conn ) ? ;
2018-05-04 13:25:50 -04:00
}
// If no accessAll, add the collections received
2018-05-31 18:18:50 -04:00
if ! data . AccessAll {
2019-02-08 13:12:08 -05:00
for col in data . Collections . iter ( ) . flatten ( ) {
2018-06-13 08:25:50 -04:00
match Collection ::find_by_uuid_and_org ( & col . Id , & org_id , & conn ) {
2018-05-28 12:26:02 -04:00
None = > err! ( " Collection not found in Organization " ) ,
Some ( collection ) = > {
2021-03-31 16:18:35 -04:00
CollectionUser ::save (
& user_to_edit . user_uuid ,
& collection . uuid ,
col . ReadOnly ,
col . HidePasswords ,
& conn ,
) ? ;
2018-05-28 12:26:02 -04:00
}
}
2018-05-04 13:25:50 -04:00
}
2018-04-24 18:34:40 -04:00
}
2018-12-19 15:52:53 -05:00
user_to_edit . save ( & conn )
2018-04-24 16:01:55 -04:00
}
2021-09-18 08:22:14 -04:00
#[ delete( " /organizations/<org_id>/users " , data = " <data> " ) ]
fn bulk_delete_user ( org_id : String , data : JsonUpcase < OrgBulkIds > , headers : AdminHeaders , conn : DbConn ) -> Json < Value > {
let data : OrgBulkIds = data . into_inner ( ) . data ;
let mut bulk_response = Vec ::new ( ) ;
for org_user_id in data . Ids {
let err_msg = match _delete_user ( & org_id , & org_user_id , & headers , & conn ) {
Ok ( _ ) = > String ::from ( " " ) ,
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 06:24:53 -04:00
#[ delete( " /organizations/<org_id>/users/<org_user_id> " ) ]
fn delete_user ( org_id : String , org_user_id : String , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
2021-09-18 08:22:14 -04:00
_delete_user ( & org_id , & org_user_id , & headers , & conn )
}
fn _delete_user ( org_id : & str , org_user_id : & str , headers : & AdminHeaders , conn : & DbConn ) -> EmptyResult {
let user_to_delete = match UserOrganization ::find_by_uuid_and_org ( org_user_id , org_id , conn ) {
2018-04-24 18:34:40 -04:00
Some ( user ) = > user ,
2018-12-30 17:34:31 -05:00
None = > err! ( " User to delete isn't member of the organization " ) ,
2018-04-24 18:34:40 -04:00
} ;
2019-05-20 15:24:29 -04:00
if user_to_delete . atype ! = UserOrgType ::User & & headers . org_user_type ! = UserOrgType ::Owner {
2018-04-24 17:04:10 -04:00
err! ( " Only Owners can delete Admins or Owners " )
}
2018-02-17 16:30:19 -05:00
2019-05-20 15:24:29 -04:00
if user_to_delete . atype = = UserOrgType ::Owner {
2018-04-24 18:34:40 -04:00
// Removing owner, check that there are at least another owner
2021-09-18 08:22:14 -04:00
let num_owners = UserOrganization ::find_by_org_and_type ( org_id , UserOrgType ::Owner as i32 , conn ) . len ( ) ;
2018-04-24 18:34:40 -04:00
if num_owners < = 1 {
err! ( " Can't delete the last owner " )
}
}
2018-02-17 16:30:19 -05:00
2021-09-18 08:22:14 -04:00
user_to_delete . delete ( conn )
2018-08-13 11:45:30 -04:00
}
2018-09-04 06:24:53 -04:00
#[ post( " /organizations/<org_id>/users/<org_user_id>/delete " ) ]
fn post_delete_user ( org_id : String , org_user_id : String , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
delete_user ( org_id , org_user_id , headers , conn )
2018-09-13 09:16:24 -04:00
}
2021-09-18 08:22:14 -04:00
#[ post( " /organizations/<org_id>/users/public-keys " , data = " <data> " ) ]
fn bulk_public_keys ( org_id : String , data : JsonUpcase < OrgBulkIds > , _headers : AdminHeaders , conn : DbConn ) -> Json < Value > {
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 {
match UserOrganization ::find_by_uuid_and_org ( & user_org_id , & org_id , & conn ) {
Some ( user_org ) = > match User ::find_by_uuid ( & user_org . user_uuid , & conn ) {
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 09:16:24 -04:00
use super ::ciphers ::update_cipher_from_data ;
2018-12-30 17:34:31 -05:00
use super ::ciphers ::CipherData ;
2018-09-13 09:16:24 -04: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 14:40:39 -04:00
#[ post( " /ciphers/import-organization?<query..> " , data = " <data> " ) ]
2018-12-30 17:34:31 -05:00
fn post_org_import (
query : Form < OrgIdData > ,
data : JsonUpcase < ImportData > ,
2020-03-14 08:22:30 -04:00
headers : AdminHeaders ,
2018-12-30 17:34:31 -05:00
conn : DbConn ,
nt : Notify ,
) -> EmptyResult {
2018-09-13 09:16:24 -04:00
let data : ImportData = data . into_inner ( ) . data ;
2018-10-10 14:40:39 -04:00
let org_id = query . into_inner ( ) . organization_id ;
2018-09-13 09:16:24 -04:00
// Read and create the collections
2018-12-30 17:34:31 -05:00
let collections : Vec < _ > = data
. Collections
. into_iter ( )
. map ( | coll | {
2019-02-22 14:25:50 -05:00
let collection = Collection ::new ( org_id . clone ( ) , coll . Name ) ;
2018-12-30 17:34:31 -05:00
if collection . save ( & conn ) . is_err ( ) {
err! ( " Failed to create Collection " ) ;
}
Ok ( collection )
} )
. collect ( ) ;
2018-09-13 09:16:24 -04: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 08:22:30 -04:00
let headers : Headers = headers . into ( ) ;
2018-09-13 09:16:24 -04:00
// Read and create the ciphers
2018-12-30 17:34:31 -05:00
let ciphers : Vec < _ > = data
. Ciphers
. into_iter ( )
. map ( | cipher_data | {
let mut cipher = Cipher ::new ( cipher_data . Type , cipher_data . Name . clone ( ) ) ;
2021-04-06 16:54:42 -04:00
update_cipher_from_data ( & mut cipher , cipher_data , & headers , false , & conn , & nt , UpdateType ::CipherCreate )
. ok ( ) ;
2018-12-30 17:34:31 -05:00
cipher
} )
. collect ( ) ;
2018-09-13 09:16:24 -04:00
// Assign the collections
for ( cipher_index , coll_index ) in relations {
let cipher_id = & ciphers [ cipher_index ] . uuid ;
2018-10-01 12:50:31 -04:00
let coll = & collections [ coll_index ] ;
let coll_id = match coll {
Ok ( coll ) = > coll . uuid . as_str ( ) ,
2018-12-30 17:34:31 -05:00
Err ( _ ) = > err! ( " Failed to assign to collection " ) ,
2018-10-01 12:50:31 -04:00
} ;
2018-12-30 17:34:31 -05:00
2018-12-19 15:52:53 -05:00
CollectionCipher ::save ( cipher_id , coll_id , & conn ) ? ;
2018-09-13 09:16:24 -04:00
}
let mut user = headers . user ;
2018-12-19 15:52:53 -05:00
user . update_revision ( & conn )
2018-10-10 14:40:39 -04:00
}
2020-03-14 08:22:30 -04:00
#[ get( " /organizations/<org_id>/policies " ) ]
2021-03-27 11:07:26 -04:00
fn list_policies ( org_id : String , _headers : AdminHeaders , conn : DbConn ) -> Json < Value > {
2020-03-14 08:22:30 -04:00
let policies = OrgPolicy ::find_by_org ( & org_id , & conn ) ;
let policies_json : Vec < Value > = policies . iter ( ) . map ( OrgPolicy ::to_json ) . collect ( ) ;
2021-03-27 11:07:26 -04:00
Json ( json! ( {
2020-03-14 08:22:30 -04:00
" Data " : policies_json ,
" Object " : " list " ,
" ContinuationToken " : null
2021-03-27 11:07:26 -04:00
} ) )
2020-03-14 08:22:30 -04:00
}
2020-03-20 05:51:17 -04:00
#[ get( " /organizations/<org_id>/policies/token?<token> " ) ]
fn list_policies_token ( org_id : String , token : String , conn : DbConn ) -> JsonResult {
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 12:00:09 -04:00
2020-03-20 05:51:17 -04:00
// TODO: We receive the invite token as ?token=<>, validate it contains the org id
let policies = OrgPolicy ::find_by_org ( & org_id , & conn ) ;
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 08:22:30 -04:00
#[ get( " /organizations/<org_id>/policies/<pol_type> " ) ]
fn get_policy ( org_id : String , pol_type : i32 , _headers : AdminHeaders , conn : DbConn ) -> JsonResult {
let pol_type_enum = match OrgPolicyType ::from_i32 ( pol_type ) {
Some ( pt ) = > pt ,
2021-01-23 23:50:06 -05:00
None = > err! ( " Invalid or unsupported policy type " ) ,
2020-03-14 08:22:30 -04:00
} ;
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type , & conn ) {
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 11:20:44 -04:00
data : Option < Value > ,
2020-03-14 08:22:30 -04:00
}
#[ put( " /organizations/<org_id>/policies/<pol_type> " , data = " <data> " ) ]
2021-03-31 16:18:35 -04:00
fn put_policy (
org_id : String ,
pol_type : i32 ,
data : Json < PolicyData > ,
_headers : AdminHeaders ,
conn : DbConn ,
) -> JsonResult {
2020-03-14 08:22:30 -04:00
let data : PolicyData = data . into_inner ( ) ;
let pol_type_enum = match OrgPolicyType ::from_i32 ( pol_type ) {
Some ( pt ) = > pt ,
None = > err! ( " Invalid policy type " ) ,
} ;
2021-10-09 08:54:30 -04:00
// If enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
2021-04-16 14:49:59 -04:00
if pol_type_enum = = OrgPolicyType ::TwoFactorAuthentication & & data . enabled {
2021-10-09 08:54:30 -04:00
let org_members = UserOrganization ::find_by_org ( & org_id , & conn ) ;
2021-04-11 22:57:17 -04:00
2021-10-09 08:54:30 -04:00
for member in org_members . into_iter ( ) {
let user_twofactor_disabled = TwoFactor ::find_by_user ( & member . user_uuid , & conn ) . is_empty ( ) ;
2021-04-11 22:57:17 -04:00
2021-10-09 08:54:30 -04:00
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
if user_twofactor_disabled & & member . atype < UserOrgType ::Admin & & member . status ! = UserOrgStatus ::Invited as i32 {
2021-04-12 21:54:57 -04:00
if CONFIG . mail_enabled ( ) {
2021-10-09 08:54:30 -04:00
let org = Organization ::find_by_uuid ( & member . org_uuid , & conn ) . unwrap ( ) ;
let user = User ::find_by_uuid ( & member . user_uuid , & conn ) . unwrap ( ) ;
2021-04-11 22:57:17 -04:00
2021-04-12 21:54:57 -04:00
mail ::send_2fa_removed_from_org ( & user . email , & org . name ) ? ;
}
2021-10-09 08:54:30 -04:00
member . delete ( & conn ) ? ;
2021-04-11 22:57:17 -04:00
}
2021-04-16 14:49:59 -04:00
}
2021-04-11 22:57:17 -04:00
}
2020-03-14 08:22:30 -04:00
2021-09-24 11:55:49 -04:00
// If enabling the SingleOrg policy, remove this org's members that are members of other orgs
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
let org_members = UserOrganization ::find_by_org ( & org_id , & conn ) ;
for member in org_members . into_iter ( ) {
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
if member . atype < UserOrgType ::Admin & & member . status ! = UserOrgStatus ::Invited as i32 {
let is_member_of_another_org = UserOrganization ::find_any_state_by_user ( & member . user_uuid , & conn )
. into_iter ( )
// Other UserOrganization's where they have accepted being a member of
. filter ( | uo | uo . uuid ! = member . uuid & & uo . status ! = UserOrgStatus ::Invited as i32 )
. count ( )
> 1 ;
if is_member_of_another_org {
if CONFIG . mail_enabled ( ) {
let org = Organization ::find_by_uuid ( & member . org_uuid , & conn ) . unwrap ( ) ;
let user = User ::find_by_uuid ( & member . user_uuid , & conn ) . unwrap ( ) ;
mail ::send_single_org_removed_from_org ( & user . email , & org . name ) ? ;
}
member . delete ( & conn ) ? ;
}
}
}
}
2020-03-14 08:22:30 -04:00
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type , & conn ) {
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id , pol_type_enum , " {} " . to_string ( ) ) ,
} ;
policy . enabled = data . enabled ;
policy . data = serde_json ::to_string ( & data . data ) ? ;
policy . save ( & conn ) ? ;
Ok ( Json ( policy . to_json ( ) ) )
2020-04-09 04:51:05 -04:00
}
2020-09-14 02:34:17 -04:00
2021-01-31 15:46:37 -05:00
#[ allow(unused_variables) ]
#[ get( " /organizations/<org_id>/tax " ) ]
fn get_organization_tax ( org_id : String , _headers : Headers , _conn : DbConn ) -> EmptyResult {
// Prevent a 404 error, which also causes Javascript errors.
err! ( " Only allowed when not self hosted. " )
}
2020-09-14 02:34:17 -04:00
#[ get( " /plans " ) ]
2021-03-27 11:07:26 -04:00
fn get_plans ( _headers : Headers , _conn : DbConn ) -> Json < Value > {
Json ( json! ( {
2020-09-14 02:34:17 -04:00
" Object " : " list " ,
" Data " : [
{
" Object " : " plan " ,
" Type " : 0 ,
" Product " : 0 ,
" Name " : " Free " ,
" IsAnnual " : false ,
" NameLocalizationKey " : " planNameFree " ,
" DescriptionLocalizationKey " : " planDescFree " ,
" CanBeUsedByBusiness " : false ,
" BaseSeats " : 2 ,
" BaseStorageGb " : null ,
" MaxCollections " : 2 ,
" MaxUsers " : 2 ,
" HasAdditionalSeatsOption " : false ,
" MaxAdditionalSeats " : null ,
" HasAdditionalStorageOption " : false ,
" MaxAdditionalStorage " : null ,
" HasPremiumAccessOption " : false ,
" TrialPeriodDays " : null ,
" HasSelfHost " : false ,
" HasPolicies " : false ,
" HasGroups " : false ,
" HasDirectory " : false ,
" HasEvents " : false ,
" HasTotp " : false ,
" Has2fa " : false ,
" HasApi " : false ,
" HasSso " : false ,
" UsersGetPremium " : false ,
" UpgradeSortOrder " : - 1 ,
" DisplaySortOrder " : - 1 ,
" LegacyYear " : null ,
" Disabled " : false ,
" StripePlanId " : null ,
" StripeSeatPlanId " : null ,
" StripeStoragePlanId " : null ,
" StripePremiumAccessPlanId " : null ,
" BasePrice " : 0.0 ,
" SeatPrice " : 0.0 ,
" AdditionalStoragePricePerGb " : 0.0 ,
" PremiumAccessOptionPrice " : 0.0
}
] ,
" ContinuationToken " : null
2021-03-27 11:07:26 -04:00
} ) )
2021-01-23 23:50:06 -05:00
}
2021-01-31 15:46:37 -05:00
#[ get( " /plans/sales-tax-rates " ) ]
2021-03-27 11:07:26 -04:00
fn get_plans_tax_rates ( _headers : Headers , _conn : DbConn ) -> Json < Value > {
2021-01-31 15:46:37 -05:00
// Prevent a 404 error, which also causes Javascript errors.
2021-03-27 11:07:26 -04:00
Json ( json! ( {
2021-01-31 15:46:37 -05:00
" Object " : " list " ,
" Data " : [ ] ,
" ContinuationToken " : null
2021-03-27 11:07:26 -04:00
} ) )
2021-01-31 15:46:37 -05:00
}
2021-02-06 12:22:39 -05:00
#[ derive(Deserialize, Debug) ]
2021-09-22 15:39:31 -04:00
#[ allow(non_snake_case, dead_code) ]
2021-02-06 12:22:39 -05: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 15:39:31 -04:00
Email : String , // "user@maildomain.net"
#[ allow(dead_code) ]
2021-02-06 12:22:39 -05: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 15:39:31 -04:00
#[ allow(dead_code) ]
2021-02-06 12:22:39 -05:00
Groups : Vec < OrgImportGroupData > ,
OverwriteExisting : bool ,
Users : Vec < OrgImportUserData > ,
}
#[ post( " /organizations/<org_id>/import " , data = " <data> " ) ]
fn import ( org_id : String , data : JsonUpcase < OrgImportData > , headers : Headers , conn : DbConn ) -> EmptyResult {
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
match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & conn ) {
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
if let Some ( user_org ) = UserOrganization ::find_by_email_and_org ( & user_data . Email , & org_id , & conn ) {
user_org . delete ( & conn ) ? ;
}
// If user is not part of the organization, but it exists
} else if UserOrganization ::find_by_email_and_org ( & user_data . Email , & org_id , & conn ) . is_none ( ) {
2021-03-31 16:18:35 -04:00
if let Some ( user ) = User ::find_by_mail ( & user_data . Email , & conn ) {
2021-02-06 12:22:39 -05: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 ;
new_org_user . save ( & conn ) ? ;
if CONFIG . mail_enabled ( ) {
let org_name = match Organization ::find_by_uuid ( & org_id , & conn ) {
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 ( ) ) ,
) ? ;
}
2021-03-27 11:07:26 -04:00
}
2021-02-06 12:22:39 -05: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 {
2021-03-27 11:07:26 -04:00
for user_org in UserOrganization ::find_by_org_and_type ( & org_id , UserOrgType ::User as i32 , & conn ) {
2021-03-31 16:18:35 -04:00
if let Some ( user_email ) = User ::find_by_uuid ( & user_org . user_uuid , & conn ) . map ( | u | u . email ) {
2021-02-06 12:22:39 -05:00
if ! data . Users . iter ( ) . any ( | u | u . Email = = user_email ) {
user_org . delete ( & conn ) ? ;
}
2021-03-27 11:07:26 -04:00
}
2021-02-06 12:22:39 -05:00
}
}
Ok ( ( ) )
}