@ -118,12 +118,13 @@ struct OrganizationUpdateData {
#[ allow(non_snake_case) ]
#[ allow(non_snake_case) ]
struct NewCollectionData {
struct NewCollectionData {
Name : String ,
Name : String ,
Groups : Vec < NewCollectionGroupData > ,
Groups : Vec < NewCollectionObjectData > ,
Users : Vec < NewCollectionObjectData > ,
}
}
#[ derive(Deserialize) ]
#[ derive(Deserialize) ]
#[ allow(non_snake_case) ]
#[ allow(non_snake_case) ]
struct NewCollection Group Data {
struct NewCollection Object Data {
HidePasswords : bool ,
HidePasswords : bool ,
Id : String ,
Id : String ,
ReadOnly : bool ,
ReadOnly : bool ,
@ -311,29 +312,62 @@ async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, mut
}
}
#[ get( " /organizations/<org_id>/collections/details " ) ]
#[ get( " /organizations/<org_id>/collections/details " ) ]
async fn get_org_collections_details ( org_id : String , _ headers: ManagerHeadersLoose , mut conn : DbConn ) -> Json < Value > {
async fn get_org_collections_details ( org_id : String , headers: ManagerHeadersLoose , mut conn : DbConn ) -> Json Result {
let mut data = Vec ::new ( ) ;
let mut data = Vec ::new ( ) ;
let user_org = match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
Some ( u ) = > u ,
None = > err ! ( "User is not part of organization" ) ,
} ;
let coll_users = CollectionUser ::find_by_organization ( & org_id , & mut conn ) . await ;
for col in Collection ::find_by_organization ( & org_id , & mut conn ) . await {
for col in Collection ::find_by_organization ( & org_id , & mut conn ) . await {
let groups : Vec < Value > = CollectionGroup ::find_by_collection ( & col . uuid , & mut conn )
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
CollectionGroup ::find_by_collection ( & col . uuid , & mut conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | {
. map ( | collection_group | {
SelectionReadOnly ::to_collection_group_details_read_only ( collection_group ) . to_json ( )
SelectionReadOnly ::to_collection_group_details_read_only ( collection_group ) . to_json ( )
} )
} )
. collect ( )
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups.
Vec ::with_capacity ( 0 )
} ;
let mut assigned = false ;
let users : Vec < Value > = coll_users
. iter ( )
. filter ( | collection_user | collection_user . collection_uuid = = col . uuid )
. map ( | collection_user | {
// Remember `user_uuid` is swapped here with the `user_org.uuid` with a join during the `CollectionUser::find_by_organization` call.
// We check here if the current user is assigned to this collection or not.
if collection_user . user_uuid = = user_org . uuid {
assigned = true ;
}
SelectionReadOnly ::to_collection_user_details_read_only ( collection_user ) . to_json ( )
} )
. collect ( ) ;
. collect ( ) ;
if user_org . access_all {
assigned = true ;
}
let mut json_object = col . to_json ( ) ;
let mut json_object = col . to_json ( ) ;
json_object [ "Assigned" ] = json ! ( assigned ) ;
json_object [ "Users" ] = json ! ( users ) ;
json_object [ "Groups" ] = json ! ( groups ) ;
json_object [ "Groups" ] = json ! ( groups ) ;
json_object [ "Object" ] = json ! ( "collectionGroupDetails" ) ;
json_object [ "Object" ] = json ! ( "collection Access Details") ;
data . push ( json_object )
data . push ( json_object )
}
}
Json ( json ! ( {
Ok ( Json ( json ! ( {
"Data" : data ,
"Data" : data ,
"Object" : "list" ,
"Object" : "list" ,
"ContinuationToken" : null ,
"ContinuationToken" : null ,
} ) )
} ) ) )
}
}
async fn _get_org_collections ( org_id : & str , conn : & mut DbConn ) -> Value {
async fn _get_org_collections ( org_id : & str , conn : & mut DbConn ) -> Value {
@ -355,12 +389,6 @@ async fn post_organization_collections(
None = > err ! ( "Can't find organization details" ) ,
None = > err ! ( "Can't find organization details" ) ,
} ;
} ;
// 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 , & mut conn ) . await {
Some ( u ) = > u ,
None = > err ! ( "User is not part of organization" ) ,
} ;
let collection = Collection ::new ( org . uuid , data . Name ) ;
let collection = Collection ::new ( org . uuid , data . Name ) ;
collection . save ( & mut conn ) . await ? ;
collection . save ( & mut conn ) . await ? ;
@ -381,11 +409,18 @@ async fn post_organization_collections(
. await ? ;
. await ? ;
}
}
// If the user doesn't have access to all collections, only in case of a Manger,
for user in data . Users {
// then we need to save the creating user uuid (Manager) to the users_collection table.
let org_user = match UserOrganization ::find_by_uuid ( & user . Id , & mut conn ) . await {
// Else the user will not have access to his own created collection.
Some ( u ) = > u ,
if ! user_org . access_all {
None = > err ! ( "User is not part of organization" ) ,
CollectionUser ::save ( & headers . user . uuid , & collection . uuid , false , false , & mut conn ) . await ? ;
} ;
if org_user . access_all {
continue ;
}
CollectionUser ::save ( & org_user . user_uuid , & collection . uuid , user . ReadOnly , user . HidePasswords , & mut conn )
. await ? ;
}
}
Ok ( Json ( collection . to_json ( ) ) )
Ok ( Json ( collection . to_json ( ) ) )
@ -448,6 +483,21 @@ async fn post_organization_collection_update(
CollectionGroup ::new ( col_id . clone ( ) , group . Id , group . ReadOnly , group . HidePasswords ) . save ( & mut conn ) . await ? ;
CollectionGroup ::new ( col_id . clone ( ) , group . Id , group . ReadOnly , group . HidePasswords ) . save ( & mut conn ) . await ? ;
}
}
CollectionUser ::delete_all_by_collection ( & col_id , & mut conn ) . await ? ;
for user in data . Users {
let org_user = match UserOrganization ::find_by_uuid ( & user . Id , & mut conn ) . await {
Some ( u ) = > u ,
None = > err ! ( "User is not part of organization" ) ,
} ;
if org_user . access_all {
continue ;
}
CollectionUser ::save ( & org_user . user_uuid , & col_id , user . ReadOnly , user . HidePasswords , & mut conn ) . await ? ;
}
Ok ( Json ( collection . to_json ( ) ) )
Ok ( Json ( collection . to_json ( ) ) )
}
}
@ -555,17 +605,49 @@ async fn get_org_collection_detail(
err ! ( "Collection is not owned by organization" )
err ! ( "Collection is not owned by organization" )
}
}
let groups : Vec < Value > = CollectionGroup ::find_by_collection ( & collection . uuid , & mut conn )
let user_org = match UserOrganization ::find_by_user_and_org ( & headers . user . uuid , & org_id , & mut conn ) . await {
Some ( u ) = > u ,
None = > err ! ( "User is not part of organization" ) ,
} ;
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
CollectionGroup ::find_by_collection ( & collection . uuid , & mut conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | {
. map ( | collection_group | {
SelectionReadOnly ::to_collection_group_details_read_only ( collection_group ) . to_json ( )
SelectionReadOnly ::to_collection_group_details_read_only ( collection_group ) . to_json ( )
} )
} )
. collect ( )
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups.
Vec ::with_capacity ( 0 )
} ;
let mut assigned = false ;
let users : Vec < Value > =
CollectionUser ::find_by_collection_swap_user_uuid_with_org_user_uuid ( & collection . uuid , & mut conn )
. await
. iter ( )
. map ( | collection_user | {
// Remember `user_uuid` is swapped here with the `user_org.uuid` with a join during the `find_by_collection_swap_user_uuid_with_org_user_uuid` call.
// We check here if the current user is assigned to this collection or not.
if collection_user . user_uuid = = user_org . uuid {
assigned = true ;
}
SelectionReadOnly ::to_collection_user_details_read_only ( collection_user ) . to_json ( )
} )
. collect ( ) ;
. collect ( ) ;
if user_org . access_all {
assigned = true ;
}
let mut json_object = collection . to_json ( ) ;
let mut json_object = collection . to_json ( ) ;
json_object [ "Assigned" ] = json ! ( assigned ) ;
json_object [ "Users" ] = json ! ( users ) ;
json_object [ "Groups" ] = json ! ( groups ) ;
json_object [ "Groups" ] = json ! ( groups ) ;
json_object [ "Object" ] = json ! ( "collectionGroupDetails" ) ;
json_object [ "Object" ] = json ! ( "collection Access Details") ;
Ok ( Json ( json_object ) )
Ok ( Json ( json_object ) )
}
}
@ -652,16 +734,39 @@ async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &mut
let mut ciphers_json = Vec ::with_capacity ( ciphers . len ( ) ) ;
let mut ciphers_json = Vec ::with_capacity ( ciphers . len ( ) ) ;
for c in ciphers {
for c in ciphers {
ciphers_json . push ( c . to_json ( host , user_uuid , Some ( & cipher_sync_data ) , conn ) . await ) ;
ciphers_json
. push ( c . to_json ( host , user_uuid , Some ( & cipher_sync_data ) , CipherSyncType ::Organization , conn ) . await ) ;
}
}
json ! ( ciphers_json )
json ! ( ciphers_json )
}
}
#[ get( " /organizations/<org_id>/users " ) ]
#[ derive(FromForm) ]
async fn get_org_users ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> Json < Value > {
struct GetOrgUserData {
#[ field(name = " includeCollections " ) ]
include_collections : Option < bool > ,
#[ field(name = " includeGroups " ) ]
include_groups : Option < bool > ,
}
// includeCollections
// includeGroups
#[ get( " /organizations/<org_id>/users?<data..> " ) ]
async fn get_org_users (
data : GetOrgUserData ,
org_id : String ,
_headers : ManagerHeadersLoose ,
mut conn : DbConn ,
) -> Json < Value > {
let mut users_json = Vec ::new ( ) ;
let mut users_json = Vec ::new ( ) ;
for u in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await {
for u in UserOrganization ::find_by_org ( & org_id , & mut conn ) . await {
users_json . push ( u . to_json_user_details ( & mut conn ) . await ) ;
users_json . push (
u . to_json_user_details (
data . include_collections . unwrap_or ( false ) ,
data . include_groups . unwrap_or ( false ) ,
& mut conn ,
)
. await ,
) ;
}
}
Json ( json ! ( {
Json ( json ! ( {
@ -2056,12 +2161,18 @@ async fn _restore_organization_user(
#[ get( " /organizations/<org_id>/groups " ) ]
#[ get( " /organizations/<org_id>/groups " ) ]
async fn get_groups ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
async fn get_groups ( org_id : String , _headers : ManagerHeadersLoose , mut conn : DbConn ) -> JsonResult {
let groups = if CONFIG . org_groups_enabled ( ) {
let groups : Vec < Value > = if CONFIG . org_groups_enabled ( ) {
Group ::find_by_organization ( & org_id , & mut conn ) . await . iter ( ) . map ( Group ::to_json ) . collect ::< Value > ( )
// Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
let groups = Group ::find_by_organization ( & org_id , & mut conn ) . await ;
let mut groups_json = Vec ::with_capacity ( groups . len ( ) ) ;
for g in groups {
groups_json . push ( g . to_json_details ( & mut conn ) . await )
}
groups_json
} else {
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups.
// so just act as if there are no groups.
Value ::Array ( Vec ::new ( ) )
Vec ::with_capacity ( 0 )
} ;
} ;
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -2078,6 +2189,7 @@ struct GroupRequest {
AccessAll : Option < bool > ,
AccessAll : Option < bool > ,
ExternalId : Option < String > ,
ExternalId : Option < String > ,
Collections : Vec < SelectionReadOnly > ,
Collections : Vec < SelectionReadOnly > ,
Users : Vec < String > ,
}
}
impl GroupRequest {
impl GroupRequest {
@ -2120,19 +2232,19 @@ impl SelectionReadOnly {
CollectionGroup ::new ( self . Id . clone ( ) , groups_uuid , self . ReadOnly , self . HidePasswords )
CollectionGroup ::new ( self . Id . clone ( ) , groups_uuid , self . ReadOnly , self . HidePasswords )
}
}
pub fn to_ group_details_read_only( collection_group : & CollectionGroup ) -> SelectionReadOnly {
pub fn to_ collection_ group_details_read_only( collection_group : & CollectionGroup ) -> SelectionReadOnly {
SelectionReadOnly {
SelectionReadOnly {
Id : collection_group . collection s_uuid. clone ( ) ,
Id : collection_group . group s_uuid. clone ( ) ,
ReadOnly : collection_group . read_only ,
ReadOnly : collection_group . read_only ,
HidePasswords : collection_group . hide_passwords ,
HidePasswords : collection_group . hide_passwords ,
}
}
}
}
pub fn to_collection_ group_details_read_only( collection_group : & CollectionGroup ) -> SelectionReadOnly {
pub fn to_collection_ user_details_read_only( collection_user : & CollectionUser ) -> SelectionReadOnly {
SelectionReadOnly {
SelectionReadOnly {
Id : collection_ group. groups _uuid. clone ( ) ,
Id : collection_ user. user _uuid. clone ( ) ,
ReadOnly : collection_ group . read_only ,
ReadOnly : collection_ user . read_only ,
HidePasswords : collection_ group . hide_passwords ,
HidePasswords : collection_ user . hide_passwords ,
}
}
}
}
@ -2171,7 +2283,7 @@ async fn post_groups(
log_event (
log_event (
EventType ::GroupCreated as i32 ,
EventType ::GroupCreated as i32 ,
& group . uuid ,
& group . uuid ,
org_id ,
org_id .clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
headers . device . atype ,
& ip . ip ,
& ip . ip ,
@ -2179,7 +2291,7 @@ async fn post_groups(
)
)
. await ;
. await ;
add_update_group ( group , group_request . Collections , & mut conn ) . await
add_update_group ( group , group_request . Collections , group_request . Users , & org_id , & headers , & ip , & mut conn ) . await
}
}
#[ put( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
#[ put( " /organizations/<org_id>/groups/<group_id> " , data = " <data> " ) ]
@ -2204,11 +2316,12 @@ async fn put_group(
let updated_group = group_request . update_group ( group ) ? ;
let updated_group = group_request . update_group ( group ) ? ;
CollectionGroup ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
CollectionGroup ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
GroupUser ::delete_all_by_group ( & group_id , & mut conn ) . await ? ;
log_event (
log_event (
EventType ::GroupUpdated as i32 ,
EventType ::GroupUpdated as i32 ,
& updated_group . uuid ,
& updated_group . uuid ,
org_id ,
org_id .clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
headers . device . atype ,
& ip . ip ,
& ip . ip ,
@ -2216,18 +2329,42 @@ async fn put_group(
)
)
. await ;
. await ;
add_update_group ( updated_group , group_request . Collections , & mut conn ) . await
add_update_group ( updated_group , group_request . Collections , group_request . Users , & org_id , & headers , & ip , & mut conn )
. await
}
}
async fn add_update_group ( mut group : Group , collections : Vec < SelectionReadOnly > , conn : & mut DbConn ) -> JsonResult {
async fn add_update_group (
mut group : Group ,
collections : Vec < SelectionReadOnly > ,
users : Vec < String > ,
org_id : & str ,
headers : & AdminHeaders ,
ip : & ClientIp ,
conn : & mut DbConn ,
) -> JsonResult {
group . save ( conn ) . await ? ;
group . save ( conn ) . await ? ;
for selection_read_only_request in collections {
for selection_read_only_request in collections {
let mut collection_group = selection_read_only_request . to_collection_group ( group . uuid . clone ( ) ) ;
let mut collection_group = selection_read_only_request . to_collection_group ( group . uuid . clone ( ) ) ;
collection_group . save ( conn ) . await ? ;
collection_group . save ( conn ) . await ? ;
}
}
for assigned_user_id in users {
let mut user_entry = GroupUser ::new ( group . uuid . clone ( ) , assigned_user_id . clone ( ) ) ;
user_entry . save ( conn ) . await ? ;
log_event (
EventType ::OrganizationUserUpdatedGroups as i32 ,
& assigned_user_id ,
String ::from ( org_id ) ,
headers . user . uuid . clone ( ) ,
headers . device . atype ,
& ip . ip ,
conn ,
)
. await ;
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"Id" : group . uuid ,
"Id" : group . uuid ,
"OrganizationId" : group . organizations_uuid ,
"OrganizationId" : group . organizations_uuid ,
@ -2248,20 +2385,7 @@ async fn get_group_details(_org_id: String, group_id: String, _headers: AdminHea
_ = > err ! ( "Group could not be found!" ) ,
_ = > err ! ( "Group could not be found!" ) ,
} ;
} ;
let collections_groups = CollectionGroup ::find_by_group ( & group_id , & mut conn )
Ok ( Json ( group . to_json_details ( & mut conn ) . await ) )
. 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 " ) ]
#[ post( " /organizations/<org_id>/groups/<group_id>/delete " ) ]