2023-06-02 22:28:30 +02:00
use chrono ::Utc ;
use rocket ::{
request ::{ self , FromRequest , Outcome } ,
2024-06-23 21:31:02 +02:00
serde ::json ::Json ,
2023-06-02 22:28:30 +02:00
Request , Route ,
} ;
2023-06-09 22:50:44 +02:00
use std ::collections ::HashSet ;
2023-06-02 22:28:30 +02:00
use crate ::{
2024-06-23 21:31:02 +02:00
api ::EmptyResult ,
2023-06-02 22:28:30 +02:00
auth ,
db ::{ models ::* , DbConn } ,
mail , CONFIG ,
} ;
pub fn routes ( ) -> Vec < Route > {
routes! [ ldap_import ]
}
2023-06-09 22:50:44 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-06-02 22:28:30 +02:00
struct OrgImportGroupData {
2024-06-23 21:31:02 +02:00
name : String ,
external_id : String ,
member_external_ids : Vec < String > ,
2023-06-02 22:28:30 +02:00
}
2023-06-09 22:50:44 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-06-02 22:28:30 +02:00
struct OrgImportUserData {
2024-06-23 21:31:02 +02:00
email : String ,
external_id : String ,
deleted : bool ,
2023-06-02 22:28:30 +02:00
}
2023-06-09 22:50:44 +02:00
#[ derive(Deserialize) ]
2024-06-23 21:31:02 +02:00
#[ serde(rename_all = " camelCase " ) ]
2023-06-02 22:28:30 +02:00
struct OrgImportData {
2024-06-23 21:31:02 +02:00
groups : Vec < OrgImportGroupData > ,
members : Vec < OrgImportUserData > ,
overwrite_existing : bool ,
// largeImport: bool, // For now this will not be used, upstream uses this to prevent syncs of more then 2000 users or groups without the flag set.
2023-06-02 22:28:30 +02:00
}
#[ post( " /public/organization/import " , data = " <data> " ) ]
2024-06-23 21:31:02 +02:00
async fn ldap_import ( data : Json < OrgImportData > , token : PublicToken , mut conn : DbConn ) -> EmptyResult {
2023-06-09 22:50:44 +02:00
// Most of the logic for this function can be found here
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
2023-06-02 22:28:30 +02:00
let org_id = token . 0 ;
2024-06-23 21:31:02 +02:00
let data = data . into_inner ( ) ;
2023-06-02 22:28:30 +02:00
2024-06-23 21:31:02 +02:00
for user_data in & data . members {
if user_data . deleted {
2023-06-02 22:28:30 +02:00
// If user is marked for deletion and it exists, revoke it
if let Some ( mut user_org ) =
2024-06-23 21:31:02 +02:00
UserOrganization ::find_by_email_and_org ( & user_data . email , & org_id , & mut conn ) . await
2023-06-02 22:28:30 +02:00
{
2023-09-02 23:57:43 +02:00
// Only revoke a user if it is not the last confirmed owner
let revoked = if user_org . atype = = UserOrgType ::Owner
& & user_org . status = = UserOrgStatus ::Confirmed as i32
{
if UserOrganization ::count_confirmed_by_org_and_type ( & org_id , UserOrgType ::Owner , & mut conn ) . await
< = 1
{
warn! ( " Can't revoke the last owner " ) ;
false
} else {
user_org . revoke ( )
}
} else {
user_org . revoke ( )
} ;
2023-06-02 22:28:30 +02:00
2024-06-23 21:31:02 +02:00
let ext_modified = user_org . set_external_id ( Some ( user_data . external_id . clone ( ) ) ) ;
2023-09-02 23:57:43 +02:00
if revoked | | ext_modified {
user_org . save ( & mut conn ) . await ? ;
}
}
2023-06-02 22:28:30 +02:00
// If user is part of the organization, restore it
} else if let Some ( mut user_org ) =
2024-06-23 21:31:02 +02:00
UserOrganization ::find_by_email_and_org ( & user_data . email , & org_id , & mut conn ) . await
2023-06-02 22:28:30 +02:00
{
2023-09-02 23:57:43 +02:00
let restored = user_org . restore ( ) ;
2024-06-23 21:31:02 +02:00
let ext_modified = user_org . set_external_id ( Some ( user_data . external_id . clone ( ) ) ) ;
2023-09-02 23:57:43 +02:00
if restored | | ext_modified {
2023-06-02 22:28:30 +02:00
user_org . save ( & mut conn ) . await ? ;
}
} else {
// If user is not part of the organization
2024-06-23 21:31:02 +02:00
let user = match User ::find_by_mail ( & user_data . email , & mut conn ) . await {
2023-06-02 22:28:30 +02:00
Some ( user ) = > user , // exists in vaultwarden
None = > {
2023-09-02 23:57:43 +02:00
// User does not exist yet
2024-06-23 21:31:02 +02:00
let mut new_user = User ::new ( user_data . email . clone ( ) ) ;
2023-06-02 22:28:30 +02:00
new_user . save ( & mut conn ) . await ? ;
if ! CONFIG . mail_enabled ( ) {
let invitation = Invitation ::new ( & new_user . email ) ;
invitation . save ( & mut conn ) . await ? ;
}
new_user
}
} ;
2023-07-31 20:40:48 +02:00
let user_org_status = if CONFIG . mail_enabled ( ) | | user . password_hash . is_empty ( ) {
2023-06-02 22:28:30 +02:00
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 ( ) ) ;
2024-06-23 21:31:02 +02:00
new_org_user . set_external_id ( Some ( user_data . external_id . clone ( ) ) ) ;
2023-06-02 22:28:30 +02:00
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 ( & mut conn ) . await ? ;
if CONFIG . mail_enabled ( ) {
let ( org_name , org_email ) = match Organization ::find_by_uuid ( & org_id , & mut conn ) . await {
Some ( org ) = > ( org . name , org . billing_email ) ,
None = > err! ( " Error looking up organization " ) ,
} ;
mail ::send_invite (
2024-06-23 21:31:02 +02:00
& user_data . email ,
2023-06-02 22:28:30 +02:00
& user . uuid ,
Some ( org_id . clone ( ) ) ,
Some ( new_org_user . uuid ) ,
& org_name ,
Some ( org_email ) ,
)
. await ? ;
}
}
}
2023-06-09 22:50:44 +02:00
if CONFIG . org_groups_enabled ( ) {
2024-06-23 21:31:02 +02:00
for group_data in & data . groups {
let group_uuid = match Group ::find_by_external_id_and_org ( & group_data . external_id , & org_id , & mut conn ) . await
2024-05-25 15:20:36 +02:00
{
2023-06-09 22:50:44 +02:00
Some ( group ) = > group . uuid ,
None = > {
2024-06-23 21:31:02 +02:00
let mut group = Group ::new (
org_id . clone ( ) ,
group_data . name . clone ( ) ,
false ,
Some ( group_data . external_id . clone ( ) ) ,
) ;
2023-06-09 22:50:44 +02:00
group . save ( & mut conn ) . await ? ;
group . uuid
}
} ;
2023-06-02 22:28:30 +02:00
2023-06-09 22:50:44 +02:00
GroupUser ::delete_all_by_group ( & group_uuid , & mut conn ) . await ? ;
2023-06-02 22:28:30 +02:00
2024-06-23 21:31:02 +02:00
for ext_id in & group_data . member_external_ids {
2023-09-02 23:57:43 +02:00
if let Some ( user_org ) = UserOrganization ::find_by_external_id_and_org ( ext_id , & org_id , & mut conn ) . await
{
let mut group_user = GroupUser ::new ( group_uuid . clone ( ) , user_org . uuid . clone ( ) ) ;
group_user . save ( & mut conn ) . await ? ;
2023-06-02 22:28:30 +02:00
}
}
}
2023-06-09 22:50:44 +02:00
} else {
warn! ( " Group support is disabled, groups will not be imported! " ) ;
2023-06-02 22:28:30 +02:00
}
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
2024-06-23 21:31:02 +02:00
if data . overwrite_existing {
2023-06-09 22:50:44 +02:00
// Generate a HashSet to quickly verify if a member is listed or not.
2024-06-23 21:31:02 +02:00
let sync_members : HashSet < String > = data . members . into_iter ( ) . map ( | m | m . external_id ) . collect ( ) ;
2023-06-02 22:28:30 +02:00
for user_org in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await {
2023-09-02 23:57:43 +02:00
if let Some ( ref user_external_id ) = user_org . external_id {
if ! sync_members . contains ( user_external_id ) {
2023-06-02 22:28:30 +02:00
if user_org . atype = = UserOrgType ::Owner & & user_org . status = = UserOrgStatus ::Confirmed as i32 {
// Removing owner, check that there is at least one other confirmed owner
if UserOrganization ::count_confirmed_by_org_and_type ( & org_id , UserOrgType ::Owner , & mut conn )
. await
< = 1
{
warn! ( " Can't delete the last owner " ) ;
continue ;
}
}
user_org . delete ( & mut conn ) . await ? ;
}
}
}
}
Ok ( ( ) )
}
pub struct PublicToken ( String ) ;
#[ rocket::async_trait ]
impl < ' r > FromRequest < ' r > for PublicToken {
type Error = & 'static str ;
async fn from_request ( request : & ' r Request < '_ > ) -> request ::Outcome < Self , Self ::Error > {
let headers = request . headers ( ) ;
// Get access_token
let access_token : & str = match headers . get_one ( " Authorization " ) {
Some ( a ) = > match a . rsplit ( " Bearer " ) . next ( ) {
Some ( split ) = > split ,
None = > err_handler! ( " No access token provided " ) ,
} ,
None = > err_handler! ( " No access token provided " ) ,
} ;
// Check JWT token is valid and get device and user from it
let claims = match auth ::decode_api_org ( access_token ) {
Ok ( claims ) = > claims ,
Err ( _ ) = > err_handler! ( " Invalid claim " ) ,
} ;
// Check if time is between claims.nbf and claims.exp
2024-03-19 19:47:30 +01:00
let time_now = Utc ::now ( ) . timestamp ( ) ;
2023-06-02 22:28:30 +02:00
if time_now < claims . nbf {
err_handler! ( " Token issued in the future " ) ;
}
if time_now > claims . exp {
err_handler! ( " Token expired " ) ;
}
2024-05-19 20:33:31 +02:00
// Check if claims.iss is domain|claims.scope[0]
let complete_host = format! ( " {} | {} " , CONFIG . domain_origin ( ) , claims . scope [ 0 ] ) ;
2023-06-02 22:28:30 +02:00
if complete_host ! = claims . iss {
err_handler! ( " Token not issued by this server " ) ;
}
// Check if claims.sub is org_api_key.uuid
// Check if claims.client_sub is org_api_key.org_uuid
let conn = match DbConn ::from_request ( request ) . await {
Outcome ::Success ( conn ) = > conn ,
_ = > err_handler! ( " Error getting DB " ) ,
} ;
let org_uuid = match claims . client_id . strip_prefix ( " organization. " ) {
Some ( uuid ) = > uuid ,
None = > err_handler! ( " Malformed client_id " ) ,
} ;
let org_api_key = match OrganizationApiKey ::find_by_org_uuid ( org_uuid , & conn ) . await {
Some ( org_api_key ) = > org_api_key ,
None = > err_handler! ( " Invalid client_id " ) ,
} ;
if org_api_key . org_uuid ! = claims . client_sub {
err_handler! ( " Token not issued for this org " ) ;
}
if org_api_key . uuid ! = claims . sub {
err_handler! ( " Token not issued for this client " ) ;
}
Outcome ::Success ( PublicToken ( claims . client_sub ) )
}
}