vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

organizations.rs (66359B)


      1 use crate::{
      2     api::{
      3         core::{CipherSyncData, CipherSyncType},
      4         EmptyResult, JsonResult, PasswordOrOtpData,
      5     },
      6     auth::{self, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
      7     db::{
      8         models::{
      9             Cipher, Collection, CollectionCipher, CollectionUser, OrgPolicy, OrgPolicyErr,
     10             OrgPolicyType, Organization, TwoFactorType, User, UserOrgStatus, UserOrgType,
     11             UserOrganization,
     12         },
     13         DbConn,
     14     },
     15     error::Error,
     16     util::{self, NumberOrString},
     17 };
     18 use core::convert;
     19 use rocket::{http::Status, serde::json::Json, Route};
     20 use serde_json::Value;
     21 use std::collections::{HashMap, HashSet};
     22 
     23 pub fn routes() -> Vec<Route> {
     24     routes![
     25         accept_invite,
     26         activate_organization_user,
     27         api_key,
     28         bulk_activate_organization_user,
     29         bulk_confirm_invite,
     30         bulk_deactivate_organization_user,
     31         bulk_delete_groups,
     32         bulk_delete_organization_collections,
     33         bulk_delete_user,
     34         bulk_public_keys,
     35         bulk_reinvite_user,
     36         bulk_restore_organization_user,
     37         bulk_revoke_organization_user,
     38         confirm_invite,
     39         create_organization,
     40         deactivate_organization_user,
     41         delete_group,
     42         delete_group_user,
     43         delete_organization,
     44         delete_organization_collection,
     45         delete_organization_collection_user,
     46         delete_user,
     47         edit_user,
     48         get_collection_users,
     49         get_group,
     50         get_group_details,
     51         get_group_users,
     52         get_groups,
     53         get_org_collection_detail,
     54         get_org_collections,
     55         get_org_collections_details,
     56         get_org_details,
     57         get_org_export,
     58         get_org_users,
     59         get_organization,
     60         get_organization_public_key,
     61         get_organization_tax,
     62         get_plans,
     63         get_plans_all,
     64         get_plans_tax_rates,
     65         get_policy,
     66         get_reset_password_details,
     67         get_user,
     68         get_user_collections,
     69         get_user_groups,
     70         import,
     71         leave_organization,
     72         list_policies,
     73         list_policies_token,
     74         post_bulk_collections,
     75         post_delete_group,
     76         post_delete_group_user,
     77         post_delete_organization,
     78         post_delete_user,
     79         post_group,
     80         post_groups,
     81         post_org_import,
     82         post_org_keys,
     83         post_organization,
     84         post_organization_collection_delete,
     85         post_organization_collection_delete_user,
     86         post_organization_collection_update,
     87         post_organization_collections,
     88         post_user_groups,
     89         put_collection_users,
     90         put_group,
     91         put_group_users,
     92         put_organization,
     93         put_organization_collection_update,
     94         put_organization_user,
     95         put_policy,
     96         put_reset_password,
     97         put_reset_password_enrollment,
     98         put_user_groups,
     99         reinvite_user,
    100         restore_organization_user,
    101         revoke_organization_user,
    102         rotate_api_key,
    103         send_invite,
    104     ]
    105 }
    106 
    107 #[derive(Deserialize)]
    108 #[serde(rename_all = "camelCase")]
    109 struct OrgData {
    110     #[allow(dead_code)]
    111     billing_email: String,
    112     #[allow(dead_code)]
    113     collection_name: String,
    114     #[allow(dead_code)]
    115     key: String,
    116     #[allow(dead_code)]
    117     name: String,
    118     #[allow(dead_code)]
    119     keys: Option<OrgKeyData>,
    120     #[allow(dead_code)]
    121     plan_type: NumberOrString, // Ignored, always use the same plan
    122 }
    123 
    124 #[derive(Deserialize)]
    125 #[serde(rename_all = "camelCase")]
    126 struct OrganizationUpdateData {
    127     billing_email: String,
    128     name: String,
    129 }
    130 
    131 #[derive(Deserialize)]
    132 #[serde(rename_all = "camelCase")]
    133 struct NewCollectionData {
    134     name: String,
    135     #[allow(dead_code)]
    136     groups: Vec<NewCollectionObjectData>,
    137     users: Vec<NewCollectionObjectData>,
    138     external_id: Option<String>,
    139 }
    140 
    141 #[derive(Deserialize)]
    142 #[serde(rename_all = "camelCase")]
    143 struct NewCollectionObjectData {
    144     hide_passwords: bool,
    145     id: String,
    146     read_only: bool,
    147 }
    148 
    149 #[derive(Deserialize)]
    150 #[serde(rename_all = "camelCase")]
    151 struct OrgKeyData {
    152     encrypted_private_key: String,
    153     public_key: String,
    154 }
    155 
    156 #[derive(Deserialize)]
    157 #[serde(rename_all = "camelCase")]
    158 struct OrgBulkIds {
    159     ids: Vec<String>,
    160 }
    161 
    162 const ORG_CREATION_NOT_ALLOWED_MSG: &str = "Organization creation is not allowed";
    163 #[allow(unused_variables, clippy::needless_pass_by_value)]
    164 #[post("/organizations", data = "<data>")]
    165 fn create_organization(_headers: Headers, data: Json<OrgData>) -> Error {
    166     Error::new(ORG_CREATION_NOT_ALLOWED_MSG, ORG_CREATION_NOT_ALLOWED_MSG)
    167 }
    168 
    169 #[delete("/organizations/<org_id>", data = "<data>")]
    170 async fn delete_organization(
    171     org_id: &str,
    172     data: Json<PasswordOrOtpData>,
    173     headers: OwnerHeaders,
    174     conn: DbConn,
    175 ) -> EmptyResult {
    176     let data: PasswordOrOtpData = data.into_inner();
    177     data.validate(&headers.user)?;
    178     match Organization::find_by_uuid(org_id, &conn).await {
    179         None => err!("Organization not found"),
    180         Some(org) => org.delete(&conn).await,
    181     }
    182 }
    183 
    184 #[post("/organizations/<org_id>/delete", data = "<data>")]
    185 async fn post_delete_organization(
    186     org_id: &str,
    187     data: Json<PasswordOrOtpData>,
    188     headers: OwnerHeaders,
    189     conn: DbConn,
    190 ) -> EmptyResult {
    191     delete_organization(org_id, data, headers, conn).await
    192 }
    193 
    194 #[post("/organizations/<org_id>/leave")]
    195 async fn leave_organization(org_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
    196     match UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &conn).await {
    197         None => err!("User not part of organization"),
    198         Some(user_org) => {
    199             if user_org.atype == UserOrgType::Owner
    200                 && UserOrganization::count_confirmed_by_org_and_type(
    201                     org_id,
    202                     UserOrgType::Owner,
    203                     &conn,
    204                 )
    205                 .await
    206                     <= 1
    207             {
    208                 err!("The last owner can't leave")
    209             }
    210             user_org.delete(&conn).await
    211         }
    212     }
    213 }
    214 
    215 #[get("/organizations/<org_id>")]
    216 async fn get_organization(org_id: &str, _headers: OwnerHeaders, conn: DbConn) -> JsonResult {
    217     match Organization::find_by_uuid(org_id, &conn).await {
    218         Some(organization) => Ok(Json(organization.to_json())),
    219         None => err!("Can't find organization details"),
    220     }
    221 }
    222 
    223 #[put("/organizations/<org_id>", data = "<data>")]
    224 async fn put_organization(
    225     org_id: &str,
    226     headers: OwnerHeaders,
    227     data: Json<OrganizationUpdateData>,
    228     conn: DbConn,
    229 ) -> JsonResult {
    230     post_organization(org_id, headers, data, conn).await
    231 }
    232 
    233 #[post("/organizations/<org_id>", data = "<data>")]
    234 async fn post_organization(
    235     org_id: &str,
    236     _headers: OwnerHeaders,
    237     data: Json<OrganizationUpdateData>,
    238     conn: DbConn,
    239 ) -> JsonResult {
    240     let data: OrganizationUpdateData = data.into_inner();
    241     let Some(mut org) = Organization::find_by_uuid(org_id, &conn).await else {
    242         err!("Can't find organization details")
    243     };
    244     org.name = data.name;
    245     org.billing_email = data.billing_email;
    246     org.save(&conn).await?;
    247     Ok(Json(org.to_json()))
    248 }
    249 
    250 // GET /api/collections?writeOnly=false
    251 #[get("/collections")]
    252 async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
    253     Json(json!({
    254         "data":
    255             Collection::find_by_user_uuid(headers.user.uuid.clone(), &conn).await
    256             .iter()
    257             .map(Collection::to_json)
    258             .collect::<Value>(),
    259         "object": "list",
    260         "continuationToken": null,
    261     }))
    262 }
    263 
    264 #[get("/organizations/<org_id>/collections")]
    265 async fn get_org_collections(
    266     org_id: &str,
    267     _headers: ManagerHeadersLoose,
    268     conn: DbConn,
    269 ) -> Json<Value> {
    270     Json(json!({
    271         "data": _get_org_collections(org_id, &conn).await,
    272         "object": "list",
    273         "continuationToken": null,
    274     }))
    275 }
    276 
    277 #[get("/organizations/<org_id>/collections/details")]
    278 async fn get_org_collections_details(
    279     org_id: &str,
    280     headers: ManagerHeadersLoose,
    281     conn: DbConn,
    282 ) -> JsonResult {
    283     let mut data = Vec::new();
    284     let Some(user_org) =
    285         UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &conn).await
    286     else {
    287         err!("User is not part of organization")
    288     };
    289     let coll_users = CollectionUser::find_by_organization(org_id, &conn).await;
    290     let has_full_access_to_org = user_org.access_all;
    291     for col in Collection::find_by_organization(org_id, &conn).await {
    292         let assigned = has_full_access_to_org;
    293         let users: Vec<Value> = coll_users
    294             .iter()
    295             .filter(|collection_user| collection_user.collection_uuid == col.uuid)
    296             .map(|collection_user| {
    297                 SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json()
    298             })
    299             .collect();
    300         let groups = Vec::<Value>::new();
    301         let mut json_object = col.to_json();
    302         json_object["assigned"] = json!(assigned);
    303         json_object["users"] = json!(users);
    304         json_object["groups"] = json!(groups);
    305         json_object["object"] = json!("collectionAccessDetails");
    306         data.push(json_object);
    307     }
    308     Ok(Json(json!({
    309         "data": data,
    310         "object": "list",
    311         "continuationToken": null,
    312     })))
    313 }
    314 
    315 async fn _get_org_collections(org_id: &str, conn: &DbConn) -> Value {
    316     Collection::find_by_organization(org_id, conn)
    317         .await
    318         .iter()
    319         .map(Collection::to_json)
    320         .collect::<Value>()
    321 }
    322 
    323 #[post("/organizations/<org_id>/collections", data = "<data>")]
    324 async fn post_organization_collections(
    325     org_id: &str,
    326     headers: ManagerHeadersLoose,
    327     data: Json<NewCollectionData>,
    328     conn: DbConn,
    329 ) -> JsonResult {
    330     let data: NewCollectionData = data.into_inner();
    331     let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
    332         err!("Can't find organization details")
    333     };
    334     let collection = Collection::new(org.uuid, data.name, data.external_id);
    335     collection.save(&conn).await?;
    336     for user in data.users {
    337         let Some(org_user) = UserOrganization::find_by_uuid(&user.id, &conn).await else {
    338             err!("User is not part of organization")
    339         };
    340         if org_user.access_all {
    341             continue;
    342         }
    343         CollectionUser::save(
    344             &org_user.user_uuid,
    345             &collection.uuid,
    346             user.read_only,
    347             user.hide_passwords,
    348             &conn,
    349         )
    350         .await?;
    351     }
    352     if headers.org_user.atype == UserOrgType::Manager && !headers.org_user.access_all {
    353         CollectionUser::save(
    354             &headers.org_user.user_uuid,
    355             &collection.uuid,
    356             false,
    357             false,
    358             &conn,
    359         )
    360         .await?;
    361     }
    362     Ok(Json(collection.to_json()))
    363 }
    364 
    365 #[put("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
    366 async fn put_organization_collection_update(
    367     org_id: &str,
    368     col_id: &str,
    369     headers: ManagerHeaders,
    370     data: Json<NewCollectionData>,
    371     conn: DbConn,
    372 ) -> JsonResult {
    373     post_organization_collection_update(org_id, col_id, headers, data, conn).await
    374 }
    375 
    376 #[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
    377 async fn post_organization_collection_update(
    378     org_id: &str,
    379     col_id: &str,
    380     headers: ManagerHeaders,
    381     data: Json<NewCollectionData>,
    382     conn: DbConn,
    383 ) -> JsonResult {
    384     let data: NewCollectionData = data.into_inner();
    385     let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
    386         err!("Can't find organization details")
    387     };
    388     let Some(mut collection) = Collection::find_by_uuid(col_id, &conn).await else {
    389         err!("Collection not found")
    390     };
    391     if collection.org_uuid != org.uuid {
    392         err!("Collection is not owned by organization");
    393     }
    394     collection.name = data.name;
    395     collection.external_id = match data.external_id {
    396         Some(external_id) if !external_id.trim().is_empty() => Some(external_id),
    397         _ => None,
    398     };
    399     collection.save(&conn).await?;
    400     CollectionUser::delete_all_by_collection(col_id, &conn).await?;
    401     for user in data.users {
    402         let Some(org_user) = UserOrganization::find_by_uuid(&user.id, &conn).await else {
    403             err!("User is not part of organization")
    404         };
    405         if org_user.access_all {
    406             continue;
    407         }
    408         CollectionUser::save(
    409             &org_user.user_uuid,
    410             col_id,
    411             user.read_only,
    412             user.hide_passwords,
    413             &conn,
    414         )
    415         .await?;
    416     }
    417     Ok(Json(
    418         collection
    419             .to_json_details(&headers.user.uuid, None, &conn)
    420             .await,
    421     ))
    422 }
    423 
    424 #[delete("/organizations/<org_id>/collections/<col_id>/user/<org_user_id>")]
    425 async fn delete_organization_collection_user(
    426     org_id: &str,
    427     col_id: &str,
    428     org_user_id: &str,
    429     _headers: AdminHeaders,
    430     conn: DbConn,
    431 ) -> EmptyResult {
    432     let collection = match Collection::find_by_uuid(col_id, &conn).await {
    433         None => err!("Collection not found"),
    434         Some(collection) => {
    435             if collection.org_uuid == org_id {
    436                 collection
    437             } else {
    438                 err!("Collection and Organization id do not match")
    439             }
    440         }
    441     };
    442     match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &conn).await {
    443         None => err!("User not found in organization"),
    444         Some(user_org) => {
    445             match CollectionUser::find_by_collection_and_user(
    446                 &collection.uuid,
    447                 &user_org.user_uuid,
    448                 &conn,
    449             )
    450             .await
    451             {
    452                 None => err!("User not assigned to collection"),
    453                 Some(col_user) => col_user.delete(&conn).await,
    454             }
    455         }
    456     }
    457 }
    458 
    459 #[post("/organizations/<org_id>/collections/<col_id>/delete-user/<org_user_id>")]
    460 async fn post_organization_collection_delete_user(
    461     org_id: &str,
    462     col_id: &str,
    463     org_user_id: &str,
    464     headers: AdminHeaders,
    465     conn: DbConn,
    466 ) -> EmptyResult {
    467     delete_organization_collection_user(org_id, col_id, org_user_id, headers, conn).await
    468 }
    469 
    470 async fn _delete_organization_collection(
    471     org_id: &str,
    472     col_id: &str,
    473     _headers: &ManagerHeaders,
    474     conn: &DbConn,
    475 ) -> EmptyResult {
    476     match Collection::find_by_uuid(col_id, conn).await {
    477         None => err!("Collection not found"),
    478         Some(collection) => {
    479             if collection.org_uuid == org_id {
    480                 collection.delete(conn).await
    481             } else {
    482                 err!("Collection and Organization id do not match")
    483             }
    484         }
    485     }
    486 }
    487 
    488 #[delete("/organizations/<org_id>/collections/<col_id>")]
    489 async fn delete_organization_collection(
    490     org_id: &str,
    491     col_id: &str,
    492     headers: ManagerHeaders,
    493     conn: DbConn,
    494 ) -> EmptyResult {
    495     _delete_organization_collection(org_id, col_id, &headers, &conn).await
    496 }
    497 
    498 #[derive(Deserialize)]
    499 #[serde(rename_all = "camelCase")]
    500 struct DeleteCollectionData {
    501     #[allow(dead_code)]
    502     id: String,
    503     #[allow(dead_code)]
    504     org_id: String,
    505 }
    506 
    507 #[post(
    508     "/organizations/<org_id>/collections/<col_id>/delete",
    509     data = "<_data>"
    510 )]
    511 async fn post_organization_collection_delete(
    512     org_id: &str,
    513     col_id: &str,
    514     headers: ManagerHeaders,
    515     _data: Json<DeleteCollectionData>,
    516     conn: DbConn,
    517 ) -> EmptyResult {
    518     _delete_organization_collection(org_id, col_id, &headers, &conn).await
    519 }
    520 
    521 #[derive(Deserialize)]
    522 #[serde(rename_all = "camelCase")]
    523 struct BulkCollectionIds {
    524     ids: Vec<String>,
    525     organization_id: String,
    526 }
    527 
    528 #[delete("/organizations/<org_id>/collections", data = "<data>")]
    529 async fn bulk_delete_organization_collections(
    530     org_id: &str,
    531     headers: ManagerHeadersLoose,
    532     data: Json<BulkCollectionIds>,
    533     conn: DbConn,
    534 ) -> EmptyResult {
    535     let data: BulkCollectionIds = data.into_inner();
    536     if org_id != data.organization_id {
    537         err!("OrganizationId mismatch");
    538     }
    539     let collections = data.ids;
    540     let headers = ManagerHeaders::from_loose(headers, &collections, &conn).await?;
    541     for col_id in collections {
    542         _delete_organization_collection(org_id, &col_id, &headers, &conn).await?;
    543     }
    544     Ok(())
    545 }
    546 
    547 #[get("/organizations/<org_id>/collections/<coll_id>/details")]
    548 async fn get_org_collection_detail(
    549     org_id: &str,
    550     coll_id: &str,
    551     headers: ManagerHeaders,
    552     conn: DbConn,
    553 ) -> JsonResult {
    554     match Collection::find_by_uuid_and_user(coll_id, headers.user.uuid.clone(), &conn).await {
    555         None => err!("Collection not found"),
    556         Some(collection) => {
    557             if collection.org_uuid != org_id {
    558                 err!("Collection is not owned by organization")
    559             }
    560             let Some(user_org) =
    561                 UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &conn).await
    562             else {
    563                 err!("User is not part of organization")
    564             };
    565             let groups: Vec<Value> = Vec::new();
    566             let users: Vec<Value> =
    567                 CollectionUser::find_by_collection_swap_user_uuid_with_org_user_uuid(
    568                     &collection.uuid,
    569                     &conn,
    570                 )
    571                 .await
    572                 .iter()
    573                 .map(|collection_user| {
    574                     SelectionReadOnly::to_collection_user_details_read_only(collection_user)
    575                         .to_json()
    576                 })
    577                 .collect();
    578             let assigned =
    579                 Collection::can_access_collection(&user_org, &collection.uuid, &conn).await;
    580             let mut json_object = collection.to_json();
    581             json_object["assigned"] = json!(assigned);
    582             json_object["users"] = json!(users);
    583             json_object["groups"] = json!(groups);
    584             json_object["object"] = json!("collectionAccessDetails");
    585             Ok(Json(json_object))
    586         }
    587     }
    588 }
    589 
    590 #[get("/organizations/<org_id>/collections/<coll_id>/users")]
    591 async fn get_collection_users(
    592     org_id: &str,
    593     coll_id: &str,
    594     _headers: ManagerHeaders,
    595     conn: DbConn,
    596 ) -> JsonResult {
    597     // Get org and collection, check that collection is from org
    598     let collection = match Collection::find_by_uuid_and_org(coll_id, org_id, &conn).await {
    599         None => err!("Collection not found in Organization"),
    600         Some(collection) => collection,
    601     };
    602     let mut user_list = Vec::new();
    603     for col_user in CollectionUser::find_by_collection(&collection.uuid, &conn).await {
    604         user_list.push(
    605             UserOrganization::find_by_user_and_org(&col_user.user_uuid, org_id, &conn)
    606                 .await
    607                 .unwrap()
    608                 .to_json_user_access_restrictions(&col_user),
    609         );
    610     }
    611     Ok(Json(json!(user_list)))
    612 }
    613 
    614 #[put("/organizations/<org_id>/collections/<coll_id>/users", data = "<data>")]
    615 async fn put_collection_users(
    616     org_id: &str,
    617     coll_id: &str,
    618     data: Json<Vec<CollectionData>>,
    619     _headers: ManagerHeaders,
    620     conn: DbConn,
    621 ) -> EmptyResult {
    622     // Get org and collection, check that collection is from org
    623     if Collection::find_by_uuid_and_org(coll_id, org_id, &conn)
    624         .await
    625         .is_none()
    626     {
    627         err!("Collection not found in Organization")
    628     }
    629     // Delete all the user-collections
    630     CollectionUser::delete_all_by_collection(coll_id, &conn).await?;
    631     // And then add all the received ones (except if the user has access_all)
    632     for d in data.iter() {
    633         let Some(user) = UserOrganization::find_by_uuid(&d.id, &conn).await else {
    634             err!("User is not part of organization")
    635         };
    636         if user.access_all {
    637             continue;
    638         }
    639         CollectionUser::save(
    640             &user.user_uuid,
    641             coll_id,
    642             d.read_only,
    643             d.hide_passwords,
    644             &conn,
    645         )
    646         .await?;
    647     }
    648     Ok(())
    649 }
    650 
    651 #[derive(FromForm)]
    652 struct OrgIdData {
    653     #[field(name = "organizationId")]
    654     organization_id: String,
    655 }
    656 
    657 #[get("/ciphers/organization-details?<data..>")]
    658 async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult {
    659     if UserOrganization::find_confirmed_by_user_and_org(
    660         &headers.user.uuid,
    661         &data.organization_id,
    662         &conn,
    663     )
    664     .await
    665     .is_none()
    666     {
    667         err_code!("Resource not found.", Status::NotFound.code);
    668     }
    669 
    670     Ok(Json(json!({
    671         "data": _get_org_details(&data.organization_id, &headers.user.uuid, &conn).await,
    672         "object": "list",
    673         "continuationToken": null,
    674     })))
    675 }
    676 
    677 async fn _get_org_details(org_id: &str, user_uuid: &str, conn: &DbConn) -> Value {
    678     let ciphers = Cipher::find_by_org(org_id, conn).await;
    679     let cipher_sync_data = CipherSyncData::new(user_uuid, CipherSyncType::Organization, conn).await;
    680     let mut ciphers_json = Vec::with_capacity(ciphers.len());
    681     for c in ciphers {
    682         ciphers_json.push(
    683             c.to_json(
    684                 user_uuid,
    685                 Some(&cipher_sync_data),
    686                 CipherSyncType::Organization,
    687                 conn,
    688             )
    689             .await,
    690         );
    691     }
    692     json!(ciphers_json)
    693 }
    694 
    695 #[derive(FromForm)]
    696 struct GetOrgUserData {
    697     #[field(name = "includeCollections")]
    698     include_collections: Option<bool>,
    699     #[field(name = "includeGroups")]
    700     include_groups: Option<bool>,
    701 }
    702 
    703 #[get("/organizations/<org_id>/users?<data..>")]
    704 async fn get_org_users(
    705     data: GetOrgUserData,
    706     org_id: &str,
    707     _headers: ManagerHeadersLoose,
    708     conn: DbConn,
    709 ) -> Json<Value> {
    710     let mut users_json = Vec::new();
    711     for u in UserOrganization::find_by_org(org_id, &conn).await {
    712         users_json.push(
    713             u.to_json_user_details(data.include_collections.unwrap_or(false), &conn)
    714                 .await,
    715         );
    716     }
    717     Json(json!({
    718         "data": users_json,
    719         "object": "list",
    720         "continuationToken": null,
    721     }))
    722 }
    723 
    724 #[post("/organizations/<org_id>/keys", data = "<data>")]
    725 async fn post_org_keys(
    726     org_id: &str,
    727     data: Json<OrgKeyData>,
    728     _headers: AdminHeaders,
    729     conn: DbConn,
    730 ) -> JsonResult {
    731     let data: OrgKeyData = data.into_inner();
    732     let mut org = match Organization::find_by_uuid(org_id, &conn).await {
    733         Some(organization) => {
    734             if organization.private_key.is_some() && organization.public_key.is_some() {
    735                 err!("Organization Keys already exist")
    736             }
    737             organization
    738         }
    739         None => err!("Can't find organization details"),
    740     };
    741     org.private_key = Some(data.encrypted_private_key);
    742     org.public_key = Some(data.public_key);
    743     org.save(&conn).await?;
    744     Ok(Json(json!({
    745         "object": "organizationKeys",
    746         "publicKey": org.public_key,
    747         "privateKey": org.private_key,
    748     })))
    749 }
    750 
    751 #[derive(Deserialize)]
    752 #[serde(rename_all = "camelCase")]
    753 struct CollectionData {
    754     id: String,
    755     read_only: bool,
    756     hide_passwords: bool,
    757 }
    758 
    759 #[derive(Deserialize)]
    760 #[serde(rename_all = "camelCase")]
    761 struct InviteData {
    762     #[allow(dead_code)]
    763     emails: Vec<String>,
    764     #[allow(dead_code)]
    765     groups: Vec<String>,
    766     #[allow(dead_code)]
    767     r#type: NumberOrString,
    768     #[allow(dead_code)]
    769     collections: Option<Vec<CollectionData>>,
    770     #[allow(dead_code)]
    771     #[serde(default)]
    772     access_all: bool,
    773 }
    774 const INVITATIONS_NOT_ALLOWED_MSG: &str = "Invitations are not allowed.";
    775 #[allow(unused_variables, clippy::needless_pass_by_value)]
    776 #[post("/organizations/<org_id>/users/invite", data = "<data>")]
    777 fn send_invite(org_id: &str, data: Json<InviteData>, _headers: AdminHeaders) -> Error {
    778     Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
    779 }
    780 #[allow(unused_variables, clippy::needless_pass_by_value)]
    781 #[post("/organizations/<org_id>/users/reinvite", data = "<data>")]
    782 fn bulk_reinvite_user(org_id: &str, data: Json<OrgBulkIds>, _headers: AdminHeaders) -> Json<Value> {
    783     Json(json!({
    784         "data": Vec::<Value>::new(),
    785         "object": "list",
    786         "continuationToken": null
    787     }))
    788 }
    789 
    790 #[allow(unused_variables, clippy::needless_pass_by_value)]
    791 #[post("/organizations/<org_id>/users/<user_org>/reinvite")]
    792 fn reinvite_user(org_id: &str, user_org: &str, _headers: AdminHeaders) -> Error {
    793     Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
    794 }
    795 
    796 #[derive(Deserialize)]
    797 #[serde(rename_all = "camelCase")]
    798 struct AcceptData {
    799     #[allow(dead_code)]
    800     token: String,
    801     #[allow(dead_code)]
    802     reset_password_key: Option<String>,
    803 }
    804 
    805 #[allow(unused_variables, clippy::needless_pass_by_value)]
    806 #[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
    807 fn accept_invite(org_id: &str, _org_user_id: &str, data: Json<AcceptData>) -> Error {
    808     Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
    809 }
    810 
    811 #[allow(unused_variables, clippy::needless_pass_by_value)]
    812 #[post("/organizations/<org_id>/users/confirm", data = "<data>")]
    813 fn bulk_confirm_invite(org_id: &str, data: Json<Value>, _headers: AdminHeaders) -> Json<Value> {
    814     Json(json!({
    815         "data": Vec::<Value>::new(),
    816         "object": "list",
    817         "continuationToken": null
    818     }))
    819 }
    820 
    821 #[allow(unused_variables, clippy::needless_pass_by_value)]
    822 #[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")]
    823 fn confirm_invite(
    824     org_id: &str,
    825     org_user_id: &str,
    826     data: Json<Value>,
    827     _headers: AdminHeaders,
    828 ) -> Error {
    829     Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
    830 }
    831 
    832 #[get("/organizations/<org_id>/users/<org_user_id>?<data..>")]
    833 async fn get_user(
    834     org_id: &str,
    835     org_user_id: &str,
    836     data: GetOrgUserData,
    837     _headers: AdminHeaders,
    838     conn: DbConn,
    839 ) -> JsonResult {
    840     let Some(user) = UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &conn).await
    841     else {
    842         err!("The specified user isn't a member of the organization")
    843     };
    844     // In this case, when groups are requested we also need to include collections.
    845     // Else these will not be shown in the interface, and could lead to missing collections when saved.
    846     let include_groups = data.include_groups.unwrap_or(false);
    847     Ok(Json(
    848         user.to_json_user_details(data.include_collections.unwrap_or(include_groups), &conn)
    849             .await,
    850     ))
    851 }
    852 
    853 #[derive(Deserialize)]
    854 #[serde(rename_all = "camelCase")]
    855 struct EditUserData {
    856     r#type: NumberOrString,
    857     collections: Option<Vec<CollectionData>>,
    858     #[allow(dead_code)]
    859     groups: Option<Vec<String>>,
    860     access_all: bool,
    861 }
    862 
    863 #[put(
    864     "/organizations/<org_id>/users/<org_user_id>",
    865     data = "<data>",
    866     rank = 1
    867 )]
    868 async fn put_organization_user(
    869     org_id: &str,
    870     org_user_id: &str,
    871     data: Json<EditUserData>,
    872     headers: AdminHeaders,
    873     conn: DbConn,
    874 ) -> EmptyResult {
    875     edit_user(org_id, org_user_id, data, headers, conn).await
    876 }
    877 
    878 #[post(
    879     "/organizations/<org_id>/users/<org_user_id>",
    880     data = "<data>",
    881     rank = 1
    882 )]
    883 async fn edit_user(
    884     org_id: &str,
    885     org_user_id: &str,
    886     data: Json<EditUserData>,
    887     headers: AdminHeaders,
    888     conn: DbConn,
    889 ) -> EmptyResult {
    890     let data: EditUserData = data.into_inner();
    891     let Some(new_type) = UserOrgType::from_str(&data.r#type.into_string()) else {
    892         err!("Invalid type")
    893     };
    894     let Some(mut user_to_edit) =
    895         UserOrganization::find_by_uuid_and_org(org_user_id, org_id, &conn).await
    896     else {
    897         err!("The specified user isn't member of the organization")
    898     };
    899     if new_type != user_to_edit.atype
    900         && (user_to_edit.atype >= UserOrgType::Admin || new_type >= UserOrgType::Admin)
    901         && headers.org_user_type != UserOrgType::Owner
    902     {
    903         err!("Only Owners can grant and remove Admin or Owner privileges")
    904     }
    905     if user_to_edit.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
    906         err!("Only Owners can edit Owner users")
    907     }
    908     if user_to_edit.atype == UserOrgType::Owner
    909         && new_type != UserOrgType::Owner
    910         && user_to_edit.status == i32::from(UserOrgStatus::Confirmed)
    911     {
    912         // Removing owner permission, check that there is at least one other confirmed owner
    913         if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, &conn)
    914             .await
    915             <= 1
    916         {
    917             err!("Can't delete the last owner")
    918         }
    919     }
    920     // This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
    921     // It returns different error messages per function.
    922     if new_type < UserOrgType::Admin {
    923         match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, org_id, true, &conn).await {
    924             Ok(()) => {}
    925             Err(OrgPolicyErr::TwoFactorMissing) => {
    926                 err!("You cannot modify this user to this type because it has no two-step login method activated");
    927             }
    928             Err(OrgPolicyErr::SingleOrgEnforced) => {
    929                 err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
    930             }
    931         }
    932     }
    933     user_to_edit.access_all = data.access_all;
    934     user_to_edit.atype = i32::from(new_type);
    935     // Delete all the odd collections
    936     for c in
    937         CollectionUser::find_by_organization_and_user_uuid(org_id, &user_to_edit.user_uuid, &conn)
    938             .await
    939     {
    940         c.delete(&conn).await?;
    941     }
    942     // If no accessAll, add the collections received
    943     if !data.access_all {
    944         for col in data.collections.iter().flatten() {
    945             match Collection::find_by_uuid_and_org(&col.id, org_id, &conn).await {
    946                 None => err!("Collection not found in Organization"),
    947                 Some(collection) => {
    948                     CollectionUser::save(
    949                         &user_to_edit.user_uuid,
    950                         &collection.uuid,
    951                         col.read_only,
    952                         col.hide_passwords,
    953                         &conn,
    954                     )
    955                     .await?;
    956                 }
    957             }
    958         }
    959     }
    960     user_to_edit.save(&conn).await
    961 }
    962 
    963 #[delete("/organizations/<org_id>/users", data = "<data>")]
    964 async fn bulk_delete_user(
    965     org_id: &str,
    966     data: Json<OrgBulkIds>,
    967     headers: AdminHeaders,
    968     conn: DbConn,
    969 ) -> Json<Value> {
    970     let data: OrgBulkIds = data.into_inner();
    971     let mut bulk_response = Vec::new();
    972     for org_user_id in data.ids {
    973         let err_msg = match _delete_user(org_id, &org_user_id, &headers, &conn).await {
    974             Ok(()) => String::new(),
    975             Err(e) => format!("{e:?}"),
    976         };
    977         bulk_response.push(json!(
    978             {
    979                 "object": "organizationBulkConfirmResponseModel",
    980                 "id": org_user_id,
    981                 "error": err_msg
    982             }
    983         ));
    984     }
    985     Json(json!({
    986         "data": bulk_response,
    987         "object": "list",
    988         "continuationToken": null
    989     }))
    990 }
    991 
    992 #[delete("/organizations/<org_id>/users/<org_user_id>")]
    993 async fn delete_user(
    994     org_id: &str,
    995     org_user_id: &str,
    996     headers: AdminHeaders,
    997     conn: DbConn,
    998 ) -> EmptyResult {
    999     _delete_user(org_id, org_user_id, &headers, &conn).await
   1000 }
   1001 
   1002 #[post("/organizations/<org_id>/users/<org_user_id>/delete")]
   1003 async fn post_delete_user(
   1004     org_id: &str,
   1005     org_user_id: &str,
   1006     headers: AdminHeaders,
   1007     conn: DbConn,
   1008 ) -> EmptyResult {
   1009     _delete_user(org_id, org_user_id, &headers, &conn).await
   1010 }
   1011 
   1012 async fn _delete_user(
   1013     org_id: &str,
   1014     org_user_id: &str,
   1015     headers: &AdminHeaders,
   1016     conn: &DbConn,
   1017 ) -> EmptyResult {
   1018     let Some(user_to_delete) =
   1019         UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await
   1020     else {
   1021         err!("User to delete isn't member of the organization")
   1022     };
   1023     if user_to_delete.atype != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
   1024         err!("Only Owners can delete Admins or Owners")
   1025     }
   1026     if user_to_delete.atype == UserOrgType::Owner
   1027         && user_to_delete.status == i32::from(UserOrgStatus::Confirmed)
   1028     {
   1029         // Removing owner, check that there is at least one other confirmed owner
   1030         if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await
   1031             <= 1
   1032         {
   1033             err!("Can't delete the last owner")
   1034         }
   1035     }
   1036     user_to_delete.delete(conn).await
   1037 }
   1038 
   1039 #[post("/organizations/<org_id>/users/public-keys", data = "<data>")]
   1040 async fn bulk_public_keys(
   1041     org_id: &str,
   1042     data: Json<OrgBulkIds>,
   1043     _headers: AdminHeaders,
   1044     conn: DbConn,
   1045 ) -> Json<Value> {
   1046     let data: OrgBulkIds = data.into_inner();
   1047     let mut bulk_response = Vec::new();
   1048     // Check all received UserOrg UUID's and find the matching User to retrieve the public-key.
   1049     // If the user does not exists, just ignore it, and do not return any information regarding that UserOrg UUID.
   1050     // The web-vault will then ignore that user for the following steps.
   1051     for user_org_id in data.ids {
   1052         if let Some(user_org) =
   1053             UserOrganization::find_by_uuid_and_org(&user_org_id, org_id, &conn).await
   1054         {
   1055             if let Some(user) = User::find_by_uuid(&user_org.user_uuid, &conn).await {
   1056                 bulk_response.push(json!(
   1057                     {
   1058                         "object": "organizationUserPublicKeyResponseModel",
   1059                         "id": user_org_id,
   1060                         "userId": user.uuid,
   1061                         "key": user.public_key
   1062                     }
   1063                 ));
   1064             } else {
   1065                 debug!("User doesn't exist");
   1066             }
   1067         } else {
   1068             debug!("UserOrg doesn't exist");
   1069         }
   1070     }
   1071     Json(json!({
   1072         "data": bulk_response,
   1073         "object": "list",
   1074         "continuationToken": null
   1075     }))
   1076 }
   1077 
   1078 use super::ciphers::update_cipher_from_data;
   1079 use super::ciphers::CipherData;
   1080 
   1081 #[derive(Deserialize)]
   1082 #[serde(rename_all = "camelCase")]
   1083 struct ImportData {
   1084     ciphers: Vec<CipherData>,
   1085     collections: Vec<NewCollectionData>,
   1086     collection_relationships: Vec<RelationsData>,
   1087 }
   1088 
   1089 #[derive(Deserialize)]
   1090 #[serde(rename_all = "camelCase")]
   1091 struct RelationsData {
   1092     // Cipher index
   1093     key: usize,
   1094     // Collection index
   1095     value: usize,
   1096 }
   1097 
   1098 #[post("/ciphers/import-organization?<query..>", data = "<data>")]
   1099 async fn post_org_import(
   1100     query: OrgIdData,
   1101     data: Json<ImportData>,
   1102     headers: AdminHeaders,
   1103     conn: DbConn,
   1104 ) -> EmptyResult {
   1105     let data: ImportData = data.into_inner();
   1106     let org_id = query.organization_id;
   1107     // Validate the import before continuing
   1108     // Bitwarden does not process the import if there is one item invalid.
   1109     // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
   1110     // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
   1111     Cipher::validate_cipher_data(&data.ciphers)?;
   1112     let mut collections = Vec::new();
   1113     for coll in data.collections {
   1114         let collection = Collection::new(org_id.clone(), coll.name, coll.external_id);
   1115         if collection.save(&conn).await.is_err() {
   1116             collections.push(Err(Error::new(
   1117                 "Failed to create Collection",
   1118                 "Failed to create Collection",
   1119             )));
   1120         } else {
   1121             collections.push(Ok(collection));
   1122         }
   1123     }
   1124     // Read the relations between collections and ciphers
   1125     let mut relations = Vec::new();
   1126     for relation in data.collection_relationships {
   1127         relations.push((relation.key, relation.value));
   1128     }
   1129     let headers: Headers = headers.into();
   1130     let mut ciphers = Vec::new();
   1131     for cipher_data in data.ciphers {
   1132         let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
   1133         update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, true)
   1134             .await
   1135             .ok();
   1136         ciphers.push(cipher);
   1137     }
   1138     // Assign the collections
   1139     for (cipher_index, coll_index) in relations {
   1140         let cipher_id = &ciphers[cipher_index].uuid;
   1141         let coll = &collections[coll_index];
   1142         let coll_id = match *coll {
   1143             Ok(ref coll) => coll.uuid.as_str(),
   1144             Err(_) => err!("Failed to assign to collection"),
   1145         };
   1146 
   1147         CollectionCipher::save(cipher_id, coll_id, &conn).await?;
   1148     }
   1149     let mut user = headers.user;
   1150     user.update_revision(&conn).await
   1151 }
   1152 
   1153 #[derive(Deserialize)]
   1154 #[serde(rename_all = "camelCase")]
   1155 struct BulkCollectionsData {
   1156     organization_id: String,
   1157     cipher_ids: Vec<String>,
   1158     collection_ids: HashSet<String>,
   1159     remove_collections: bool,
   1160 }
   1161 
   1162 // This endpoint is only reachable via the organization view, therefor this endpoint is located here
   1163 // Also Bitwarden does not send out Notifications for these changes, it only does this for individual cipher collection updates
   1164 #[allow(clippy::iter_over_hash_type)]
   1165 #[post("/ciphers/bulk-collections", data = "<data>")]
   1166 async fn post_bulk_collections(
   1167     data: Json<BulkCollectionsData>,
   1168     headers: Headers,
   1169     conn: DbConn,
   1170 ) -> EmptyResult {
   1171     let data: BulkCollectionsData = data.into_inner();
   1172 
   1173     // This feature does not seem to be active on all the clients
   1174     // To prevent future issues, add a check to block a call when this is set to true
   1175     if data.remove_collections {
   1176         err!("Bulk removing of collections is not yet implemented")
   1177     }
   1178 
   1179     // Get all the collection available to the user in one query
   1180     // Also filter based upon the provided collections
   1181     let user_collections: HashMap<String, Collection> =
   1182         Collection::find_by_organization_and_user_uuid(
   1183             &data.organization_id,
   1184             &headers.user.uuid,
   1185             &conn,
   1186         )
   1187         .await
   1188         .into_iter()
   1189         .filter_map(|c| {
   1190             if data.collection_ids.contains(&c.uuid) {
   1191                 Some((c.uuid.clone(), c))
   1192             } else {
   1193                 None
   1194             }
   1195         })
   1196         .collect();
   1197 
   1198     // Verify if all the collections requested exists and are writeable for the user, else abort
   1199     for collection_uuid in &data.collection_ids {
   1200         match user_collections.get(collection_uuid) {
   1201             Some(collection)
   1202                 if collection
   1203                     .is_writable_by_user(&headers.user.uuid, &conn)
   1204                     .await => {}
   1205             _ => err_code!(
   1206                 "Resource not found",
   1207                 "User does not have access to a collection",
   1208                 404
   1209             ),
   1210         }
   1211     }
   1212 
   1213     for cipher_id in &data.cipher_ids {
   1214         // Only act on existing cipher uuid's
   1215         // Do not abort the operation just ignore it, it could be a cipher was just deleted for example
   1216         if let Some(cipher) =
   1217             Cipher::find_by_uuid_and_org(cipher_id, &data.organization_id, &conn).await
   1218         {
   1219             if cipher
   1220                 .is_write_accessible_to_user(&headers.user.uuid, &conn)
   1221                 .await
   1222             {
   1223                 for collection in &data.collection_ids {
   1224                     CollectionCipher::save(&cipher.uuid, collection, &conn).await?;
   1225                 }
   1226             }
   1227         };
   1228     }
   1229 
   1230     Ok(())
   1231 }
   1232 
   1233 #[get("/organizations/<org_id>/policies")]
   1234 async fn list_policies(org_id: &str, _headers: AdminHeaders, conn: DbConn) -> Json<Value> {
   1235     let policies = OrgPolicy::find_by_org(org_id, &conn).await;
   1236     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
   1237     Json(json!({
   1238         "data": policies_json,
   1239         "object": "list",
   1240         "continuationToken": null
   1241     }))
   1242 }
   1243 
   1244 #[get("/organizations/<org_id>/policies/token?<token>")]
   1245 async fn list_policies_token(org_id: &str, token: &str, conn: DbConn) -> JsonResult {
   1246     if org_id == "undefined" && token == "undefined" {
   1247         return Ok(Json(json!({})));
   1248     }
   1249     let invite = auth::decode_invite(token)?;
   1250     let Some(invite_org_id) = invite.org_id else {
   1251         err!("Invalid token")
   1252     };
   1253     if invite_org_id != org_id {
   1254         err!("Token doesn't match request organization");
   1255     }
   1256     // TODO: We receive the invite token as ?token=<>, validate it contains the org id
   1257     let policies = OrgPolicy::find_by_org(org_id, &conn).await;
   1258     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
   1259     Ok(Json(json!({
   1260         "data": policies_json,
   1261         "object": "list",
   1262         "continuationToken": null
   1263     })))
   1264 }
   1265 
   1266 #[get("/organizations/<org_id>/policies/<pol_type>")]
   1267 async fn get_policy(
   1268     org_id: &str,
   1269     pol_type: i32,
   1270     _headers: AdminHeaders,
   1271     conn: DbConn,
   1272 ) -> JsonResult {
   1273     let Ok(pol_type_enum) = OrgPolicyType::try_from(pol_type) else {
   1274         err!("Invalid or unsupported policy type")
   1275     };
   1276     let policy = (OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &conn).await).map_or_else(
   1277         || OrgPolicy::new(String::from(org_id), pol_type_enum, "null".to_owned()),
   1278         convert::identity,
   1279     );
   1280     Ok(Json(policy.to_json()))
   1281 }
   1282 
   1283 #[derive(Deserialize)]
   1284 struct PolicyData {
   1285     enabled: bool,
   1286     #[serde(rename = "type")]
   1287     _type: i32,
   1288     data: Option<Value>,
   1289 }
   1290 
   1291 #[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")]
   1292 async fn put_policy(
   1293     org_id: &str,
   1294     pol_type: i32,
   1295     data: Json<PolicyData>,
   1296     _headers: AdminHeaders,
   1297     conn: DbConn,
   1298 ) -> JsonResult {
   1299     let data: PolicyData = data.into_inner();
   1300     let Ok(pol_type_enum) = OrgPolicyType::try_from(pol_type) else {
   1301         err!("Invalid or unsupported policy type")
   1302     };
   1303     // When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
   1304     if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled {
   1305         for member in UserOrganization::find_by_org(org_id, &conn).await {
   1306             // Policy only applies to non-Owner/non-Admin members who have accepted joining the org
   1307             // Invited users still need to accept the invite and will get an error when they try to accept the invite.
   1308             if member.atype < UserOrgType::Admin
   1309                 && member.status != i32::from(UserOrgStatus::Invited)
   1310                 && !TwoFactorType::has_twofactor(&member.user_uuid, &conn).await?
   1311             {
   1312                 member.delete(&conn).await?;
   1313             }
   1314         }
   1315     }
   1316     // When enabling the SingleOrg policy, remove this org's members that are members of other orgs
   1317     if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
   1318         for member in UserOrganization::find_by_org(org_id, &conn).await {
   1319             // Policy only applies to non-Owner/non-Admin members who have accepted joining the org
   1320             // Exclude invited and revoked users when checking for this policy.
   1321             // Those users will not be allowed to accept or be activated because of the policy checks done there.
   1322             // We check if the count is larger then 1, because it includes this organization also.
   1323             if member.atype < UserOrgType::Admin
   1324                 && member.status != i32::from(UserOrgStatus::Invited)
   1325                 && UserOrganization::count_accepted_and_confirmed_by_user(&member.user_uuid, &conn)
   1326                     .await
   1327                     > 1
   1328             {
   1329                 member.delete(&conn).await?;
   1330             }
   1331         }
   1332     }
   1333     let mut policy = (OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &conn).await)
   1334         .map_or_else(
   1335             || OrgPolicy::new(String::from(org_id), pol_type_enum, "{}".to_owned()),
   1336             |p| p,
   1337         );
   1338     policy.enabled = data.enabled;
   1339     policy.data = serde_json::to_string(&data.data)?;
   1340     policy.save(&conn).await?;
   1341     Ok(Json(policy.to_json()))
   1342 }
   1343 
   1344 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1345 #[get("/organizations/<org_id>/tax")]
   1346 fn get_organization_tax(org_id: &str, _headers: Headers) -> Json<Value> {
   1347     // Prevent a 404 error, which also causes Javascript errors.
   1348     // Upstream sends "Only allowed when not self hosted." As an error message.
   1349     // If we do the same it will also output this to the log, which is overkill.
   1350     // An empty list/data also works fine.
   1351     Json(_empty_data_json())
   1352 }
   1353 
   1354 #[get("/plans")]
   1355 fn get_plans() -> Json<Value> {
   1356     // Respond with a minimal json just enough to allow the creation of an new organization.
   1357     Json(json!({
   1358         "object": "list",
   1359         "data": [{
   1360             "object": "plan",
   1361             "type": 0i32,
   1362             "product": 0i32,
   1363             "name": "Free",
   1364             "nameLocalizationKey": "planNameFree",
   1365             "bitwardenProduct": 0i32,
   1366             "maxUsers": 0i32,
   1367             "descriptionLocalizationKey": "planDescFree"
   1368         },{
   1369             "object": "plan",
   1370             "type": 0i32,
   1371             "product": 1i32,
   1372             "name": "Free",
   1373             "nameLocalizationKey": "planNameFree",
   1374             "bitwardenProduct": 1i32,
   1375             "maxUsers": 0i32,
   1376             "descriptionLocalizationKey": "planDescFree"
   1377         }],
   1378         "ContinuationToken": null
   1379     }))
   1380 }
   1381 
   1382 #[get("/plans/all")]
   1383 fn get_plans_all() -> Json<Value> {
   1384     get_plans()
   1385 }
   1386 
   1387 #[allow(clippy::needless_pass_by_value)]
   1388 #[get("/plans/sales-tax-rates")]
   1389 fn get_plans_tax_rates(_headers: Headers) -> Json<Value> {
   1390     // Prevent a 404 error, which also causes Javascript errors.
   1391     Json(_empty_data_json())
   1392 }
   1393 
   1394 fn _empty_data_json() -> Value {
   1395     json!({
   1396         "object": "list",
   1397         "data": [],
   1398         "continuationToken": null
   1399     })
   1400 }
   1401 
   1402 #[derive(Deserialize)]
   1403 #[serde(rename_all = "camelCase")]
   1404 struct OrgImportGroupData {
   1405     #[allow(dead_code)]
   1406     name: String, // "GroupName"
   1407     #[allow(dead_code)]
   1408     external_id: String, // "cn=GroupName,ou=Groups,dc=example,dc=com"
   1409     #[allow(dead_code)]
   1410     users: Vec<String>, // ["uid=user,ou=People,dc=example,dc=com"]
   1411 }
   1412 
   1413 #[derive(Deserialize)]
   1414 #[serde(rename_all = "camelCase")]
   1415 struct OrgImportUserData {
   1416     email: String, // "user@maildomain.net"
   1417     #[allow(dead_code)]
   1418     external_id: String, // "uid=user,ou=People,dc=example,dc=com"
   1419     deleted: bool,
   1420 }
   1421 
   1422 #[derive(Deserialize)]
   1423 #[serde(rename_all = "camelCase")]
   1424 struct OrgImportData {
   1425     #[allow(dead_code)]
   1426     groups: Vec<OrgImportGroupData>,
   1427     overwrite_existing: bool,
   1428     users: Vec<OrgImportUserData>,
   1429 }
   1430 
   1431 #[post("/organizations/<org_id>/import", data = "<data>")]
   1432 async fn import(
   1433     org_id: &str,
   1434     data: Json<OrgImportData>,
   1435     headers: Headers,
   1436     conn: DbConn,
   1437 ) -> EmptyResult {
   1438     let data = data.into_inner();
   1439     // TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way
   1440     // to differentiate between auto-imported users and manually added ones.
   1441     // This means that this endpoint can end up removing users that were added manually by an admin,
   1442     // as opposed to upstream which only removes auto-imported users.
   1443     // User needs to be admin or owner to use the Directory Connector
   1444     match UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &conn).await {
   1445         Some(user_org) if user_org.atype >= UserOrgType::Admin => { /* Okay, nothing to do */ }
   1446         Some(_) => err!("User has insufficient permissions to use Directory Connector"),
   1447         None => err!("User not part of organization"),
   1448     };
   1449     for user_data in &data.users {
   1450         if user_data.deleted {
   1451             // If user is marked for deletion and it exists, delete it
   1452             if let Some(user_org) =
   1453                 UserOrganization::find_by_email_and_org(&user_data.email, org_id, &conn).await
   1454             {
   1455                 user_org.delete(&conn).await?;
   1456             }
   1457 
   1458         // If user is not part of the organization, but it exists
   1459         } else if UserOrganization::find_by_email_and_org(&user_data.email, org_id, &conn)
   1460             .await
   1461             .is_none()
   1462         {
   1463             if let Some(user) = User::find_by_mail(&user_data.email, &conn).await {
   1464                 let user_org_status = i32::from(UserOrgStatus::Accepted);
   1465                 let mut new_org_user =
   1466                     UserOrganization::new(user.uuid.clone(), String::from(org_id));
   1467                 new_org_user.access_all = false;
   1468                 new_org_user.atype = i32::from(UserOrgType::User);
   1469                 new_org_user.status = user_org_status;
   1470                 new_org_user.save(&conn).await?;
   1471             }
   1472         } else {
   1473             // We don't care.
   1474         }
   1475     }
   1476     // 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)
   1477     if data.overwrite_existing {
   1478         for user_org in
   1479             UserOrganization::find_by_org_and_type(org_id, UserOrgType::User, &conn).await
   1480         {
   1481             if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn)
   1482                 .await
   1483                 .map(|u| u.email)
   1484             {
   1485                 if !data.users.iter().any(|u| u.email == user_email) {
   1486                     user_org.delete(&conn).await?;
   1487                 }
   1488             }
   1489         }
   1490     }
   1491     Ok(())
   1492 }
   1493 
   1494 // Pre web-vault v2022.9.x endpoint
   1495 #[put("/organizations/<org_id>/users/<org_user_id>/deactivate")]
   1496 async fn deactivate_organization_user(
   1497     org_id: &str,
   1498     org_user_id: &str,
   1499     headers: AdminHeaders,
   1500     conn: DbConn,
   1501 ) -> EmptyResult {
   1502     _revoke_organization_user(org_id, org_user_id, &headers, &conn).await
   1503 }
   1504 
   1505 // Pre web-vault v2022.9.x endpoint
   1506 #[put("/organizations/<org_id>/users/deactivate", data = "<data>")]
   1507 async fn bulk_deactivate_organization_user(
   1508     org_id: &str,
   1509     data: Json<Value>,
   1510     headers: AdminHeaders,
   1511     conn: DbConn,
   1512 ) -> Json<Value> {
   1513     bulk_revoke_organization_user(org_id, data, headers, conn).await
   1514 }
   1515 
   1516 #[put("/organizations/<org_id>/users/<org_user_id>/revoke")]
   1517 async fn revoke_organization_user(
   1518     org_id: &str,
   1519     org_user_id: &str,
   1520     headers: AdminHeaders,
   1521     conn: DbConn,
   1522 ) -> EmptyResult {
   1523     _revoke_organization_user(org_id, org_user_id, &headers, &conn).await
   1524 }
   1525 
   1526 #[put("/organizations/<org_id>/users/revoke", data = "<data>")]
   1527 async fn bulk_revoke_organization_user(
   1528     org_id: &str,
   1529     data: Json<Value>,
   1530     headers: AdminHeaders,
   1531     conn: DbConn,
   1532 ) -> Json<Value> {
   1533     let data = data.into_inner();
   1534     let mut bulk_response = Vec::new();
   1535     match data["Ids"].as_array() {
   1536         Some(org_users) => {
   1537             for org_user_id in org_users {
   1538                 let org_user_id = org_user_id.as_str().unwrap_or_default();
   1539                 let err_msg =
   1540                     match _revoke_organization_user(org_id, org_user_id, &headers, &conn).await {
   1541                         Ok(()) => String::new(),
   1542                         Err(e) => format!("{e:?}"),
   1543                     };
   1544                 bulk_response.push(json!(
   1545                     {
   1546                         "object": "organizationUserBulkResponseModel",
   1547                         "id": org_user_id,
   1548                         "error": err_msg
   1549                     }
   1550                 ));
   1551             }
   1552         }
   1553         None => panic!("No users to revoke"),
   1554     }
   1555     Json(json!({
   1556         "data": bulk_response,
   1557         "object": "list",
   1558         "continuationToken": null
   1559     }))
   1560 }
   1561 
   1562 async fn _revoke_organization_user(
   1563     org_id: &str,
   1564     org_user_id: &str,
   1565     headers: &AdminHeaders,
   1566     conn: &DbConn,
   1567 ) -> EmptyResult {
   1568     match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
   1569         Some(mut user_org) if user_org.status > i32::from(UserOrgStatus::Revoked) => {
   1570             if user_org.user_uuid == headers.user.uuid {
   1571                 err!("You cannot revoke yourself")
   1572             }
   1573             if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
   1574                 err!("Only owners can revoke other owners")
   1575             }
   1576             if user_org.atype == UserOrgType::Owner
   1577                 && UserOrganization::count_confirmed_by_org_and_type(
   1578                     org_id,
   1579                     UserOrgType::Owner,
   1580                     conn,
   1581                 )
   1582                 .await
   1583                     <= 1
   1584             {
   1585                 err!("Organization must have at least one confirmed owner")
   1586             }
   1587             user_org.revoke();
   1588             user_org.save(conn).await?;
   1589         }
   1590         Some(_) => err!("User is already revoked"),
   1591         None => err!("User not found in organization"),
   1592     }
   1593     Ok(())
   1594 }
   1595 
   1596 // Pre web-vault v2022.9.x endpoint
   1597 #[put("/organizations/<org_id>/users/<org_user_id>/activate")]
   1598 async fn activate_organization_user(
   1599     org_id: &str,
   1600     org_user_id: &str,
   1601     headers: AdminHeaders,
   1602     conn: DbConn,
   1603 ) -> EmptyResult {
   1604     _restore_organization_user(org_id, org_user_id, &headers, &conn).await
   1605 }
   1606 
   1607 // Pre web-vault v2022.9.x endpoint
   1608 #[put("/organizations/<org_id>/users/activate", data = "<data>")]
   1609 async fn bulk_activate_organization_user(
   1610     org_id: &str,
   1611     data: Json<OrgBulkIds>,
   1612     headers: AdminHeaders,
   1613     conn: DbConn,
   1614 ) -> Json<Value> {
   1615     bulk_restore_organization_user(org_id, data, headers, conn).await
   1616 }
   1617 
   1618 #[put("/organizations/<org_id>/users/<org_user_id>/restore")]
   1619 async fn restore_organization_user(
   1620     org_id: &str,
   1621     org_user_id: &str,
   1622     headers: AdminHeaders,
   1623     conn: DbConn,
   1624 ) -> EmptyResult {
   1625     _restore_organization_user(org_id, org_user_id, &headers, &conn).await
   1626 }
   1627 
   1628 #[put("/organizations/<org_id>/users/restore", data = "<data>")]
   1629 async fn bulk_restore_organization_user(
   1630     org_id: &str,
   1631     data: Json<OrgBulkIds>,
   1632     headers: AdminHeaders,
   1633     conn: DbConn,
   1634 ) -> Json<Value> {
   1635     let data = data.into_inner();
   1636     let mut bulk_response = Vec::new();
   1637     for org_user_id in data.ids {
   1638         let err_msg = match _restore_organization_user(org_id, &org_user_id, &headers, &conn).await
   1639         {
   1640             Ok(()) => String::new(),
   1641             Err(e) => format!("{e:?}"),
   1642         };
   1643         bulk_response.push(json!(
   1644             {
   1645                 "object": "OrganizationUserBulkResponseModel",
   1646                 "id": org_user_id,
   1647                 "error": err_msg
   1648             }
   1649         ));
   1650     }
   1651     Json(json!({
   1652         "data": bulk_response,
   1653         "object": "list",
   1654         "continuationToken": null
   1655     }))
   1656 }
   1657 
   1658 async fn _restore_organization_user(
   1659     org_id: &str,
   1660     org_user_id: &str,
   1661     headers: &AdminHeaders,
   1662     conn: &DbConn,
   1663 ) -> EmptyResult {
   1664     match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
   1665         Some(mut user_org) if user_org.status < i32::from(UserOrgStatus::Accepted) => {
   1666             if user_org.user_uuid == headers.user.uuid {
   1667                 err!("You cannot restore yourself")
   1668             }
   1669             if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
   1670                 err!("Only owners can restore other owners")
   1671             }
   1672             // This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
   1673             // It returns different error messages per function.
   1674             if user_org.atype < UserOrgType::Admin {
   1675                 match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
   1676                     Ok(()) => {}
   1677                     Err(OrgPolicyErr::TwoFactorMissing) => {
   1678                         err!("You cannot restore this user because it has no two-step login method activated");
   1679                     }
   1680                     Err(OrgPolicyErr::SingleOrgEnforced) => {
   1681                         err!("You cannot restore this user because it is a member of an organization which forbids it");
   1682                     }
   1683                 }
   1684             }
   1685             user_org.restore();
   1686             user_org.save(conn).await?;
   1687         }
   1688         Some(_) => err!("User is already active"),
   1689         None => err!("User not found in organization"),
   1690     }
   1691     Ok(())
   1692 }
   1693 
   1694 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1695 #[get("/organizations/<org_id>/groups")]
   1696 fn get_groups(org_id: &str, _headers: ManagerHeadersLoose) -> Json<Value> {
   1697     Json(json!({
   1698         "data": Vec::<Value>::new(),
   1699         "object": "list",
   1700         "continuationToken": null,
   1701     }))
   1702 }
   1703 
   1704 #[derive(Deserialize)]
   1705 #[serde(rename_all = "camelCase")]
   1706 struct GroupRequest;
   1707 #[derive(Serialize)]
   1708 #[serde(rename_all = "camelCase")]
   1709 struct SelectionReadOnly {
   1710     id: String,
   1711     read_only: bool,
   1712     hide_passwords: bool,
   1713 }
   1714 
   1715 impl SelectionReadOnly {
   1716     fn to_collection_user_details_read_only(collection_user: &CollectionUser) -> Self {
   1717         Self {
   1718             id: collection_user.user_uuid.clone(),
   1719             read_only: collection_user.read_only,
   1720             hide_passwords: collection_user.hide_passwords,
   1721         }
   1722     }
   1723     fn to_json(&self) -> Value {
   1724         json!(self)
   1725     }
   1726 }
   1727 
   1728 const GROUPS_DISABLED_MSG: &str = "Groups are disabled.";
   1729 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1730 #[post("/organizations/<org_id>/groups/<group_id>", data = "<data>")]
   1731 fn post_group(
   1732     org_id: &str,
   1733     group_id: &str,
   1734     data: Json<GroupRequest>,
   1735     _headers: AdminHeaders,
   1736 ) -> Error {
   1737     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1738 }
   1739 
   1740 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1741 #[post("/organizations/<org_id>/groups", data = "<data>")]
   1742 fn post_groups(org_id: &str, _headers: AdminHeaders, data: Json<GroupRequest>) -> Error {
   1743     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1744 }
   1745 
   1746 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1747 #[put("/organizations/<org_id>/groups/<group_id>", data = "<data>")]
   1748 fn put_group(
   1749     org_id: &str,
   1750     group_id: &str,
   1751     data: Json<GroupRequest>,
   1752     _headers: AdminHeaders,
   1753 ) -> Error {
   1754     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1755 }
   1756 
   1757 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1758 #[get("/organizations/<_org_id>/groups/<group_id>/details")]
   1759 fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
   1760     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1761 }
   1762 
   1763 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1764 #[post("/organizations/<org_id>/groups/<group_id>/delete")]
   1765 fn post_delete_group(org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
   1766     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1767 }
   1768 
   1769 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1770 #[delete("/organizations/<org_id>/groups/<group_id>")]
   1771 fn delete_group(org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
   1772     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1773 }
   1774 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1775 #[delete("/organizations/<org_id>/groups", data = "<data>")]
   1776 fn bulk_delete_groups(org_id: &str, data: Json<OrgBulkIds>, _headers: AdminHeaders) -> Error {
   1777     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1778 }
   1779 
   1780 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1781 #[get("/organizations/<_org_id>/groups/<group_id>")]
   1782 fn get_group(_org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
   1783     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1784 }
   1785 
   1786 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1787 #[get("/organizations/<_org_id>/groups/<group_id>/users")]
   1788 fn get_group_users(_org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
   1789     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1790 }
   1791 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1792 #[put("/organizations/<org_id>/groups/<group_id>/users", data = "<data>")]
   1793 fn put_group_users(
   1794     org_id: &str,
   1795     group_id: &str,
   1796     _headers: AdminHeaders,
   1797     data: Json<Vec<String>>,
   1798 ) -> Error {
   1799     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1800 }
   1801 
   1802 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1803 #[get("/organizations/<_org_id>/users/<user_id>/groups")]
   1804 fn get_user_groups(_org_id: &str, user_id: &str, _headers: AdminHeaders) -> Error {
   1805     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1806 }
   1807 
   1808 #[derive(Deserialize)]
   1809 #[serde(rename_all = "camelCase")]
   1810 struct OrganizationUserUpdateGroupsRequest;
   1811 
   1812 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1813 #[post("/organizations/<org_id>/users/<org_user_id>/groups", data = "<data>")]
   1814 fn post_user_groups(
   1815     org_id: &str,
   1816     org_user_id: &str,
   1817     data: Json<OrganizationUserUpdateGroupsRequest>,
   1818     _headers: AdminHeaders,
   1819 ) -> Error {
   1820     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1821 }
   1822 
   1823 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1824 #[put("/organizations/<org_id>/users/<org_user_id>/groups", data = "<data>")]
   1825 fn put_user_groups(
   1826     org_id: &str,
   1827     org_user_id: &str,
   1828     data: Json<OrganizationUserUpdateGroupsRequest>,
   1829     _headers: AdminHeaders,
   1830 ) -> Error {
   1831     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1832 }
   1833 
   1834 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1835 #[post("/organizations/<org_id>/groups/<group_id>/delete-user/<org_user_id>")]
   1836 fn post_delete_group_user(
   1837     org_id: &str,
   1838     group_id: &str,
   1839     org_user_id: &str,
   1840     _headers: AdminHeaders,
   1841 ) -> Error {
   1842     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1843 }
   1844 
   1845 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1846 #[delete("/organizations/<org_id>/groups/<group_id>/users/<org_user_id>")]
   1847 fn delete_group_user(
   1848     org_id: &str,
   1849     group_id: &str,
   1850     org_user_id: &str,
   1851     _headers: AdminHeaders,
   1852 ) -> Error {
   1853     Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
   1854 }
   1855 
   1856 #[derive(Deserialize)]
   1857 #[serde(rename_all = "camelCase")]
   1858 struct OrganizationUserResetPasswordEnrollmentRequest {
   1859     #[allow(dead_code)]
   1860     reset_password_key: Option<String>,
   1861     #[allow(dead_code)]
   1862     master_password_hash: Option<String>,
   1863     #[allow(dead_code)]
   1864     otp: Option<String>,
   1865 }
   1866 
   1867 #[derive(Deserialize)]
   1868 #[serde(rename_all = "camelCase")]
   1869 struct OrganizationUserResetPasswordRequest {
   1870     #[allow(dead_code)]
   1871     new_master_password_hash: String,
   1872     #[allow(dead_code)]
   1873     key: String,
   1874 }
   1875 #[get("/organizations/<org_id>/public-key")]
   1876 async fn get_organization_public_key(org_id: &str, _headers: Headers, conn: DbConn) -> JsonResult {
   1877     let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
   1878         err!("Organization not found")
   1879     };
   1880 
   1881     Ok(Json(json!({
   1882         "object": "organizationPublicKey",
   1883         "publicKey": org.public_key,
   1884     })))
   1885 }
   1886 const PASS_RESET_MSG: &str = "Password reset is not supported on an email-disabled instance.";
   1887 #[allow(unused_variables)]
   1888 #[put(
   1889     "/organizations/<org_id>/users/<org_user_id>/reset-password",
   1890     data = "<data>"
   1891 )]
   1892 async fn put_reset_password(
   1893     org_id: &str,
   1894     org_user_id: &str,
   1895     _headers: AdminHeaders,
   1896     data: Json<OrganizationUserResetPasswordRequest>,
   1897     conn: DbConn,
   1898 ) -> EmptyResult {
   1899     let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
   1900         err!("Required organization not found")
   1901     };
   1902     let Some(org_user) =
   1903         UserOrganization::find_by_uuid_and_org(org_user_id, &org.uuid, &conn).await
   1904     else {
   1905         err!("User to reset isn't member of required organization")
   1906     };
   1907     match User::find_by_uuid(&org_user.user_uuid, &conn).await {
   1908         Some(_) => err!(PASS_RESET_MSG),
   1909         None => err!("User not found"),
   1910     }
   1911 }
   1912 
   1913 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1914 #[get("/organizations/<org_id>/users/<org_user_id>/reset-password-details")]
   1915 fn get_reset_password_details(org_id: &str, org_user_id: &str, _headers: AdminHeaders) -> Error {
   1916     Error::new(PASS_RESET_MSG, PASS_RESET_MSG)
   1917 }
   1918 
   1919 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1920 #[put(
   1921     "/organizations/<org_id>/users/<org_user_id>/reset-password-enrollment",
   1922     data = "<data>"
   1923 )]
   1924 fn put_reset_password_enrollment(
   1925     org_id: &str,
   1926     org_user_id: &str,
   1927     _headers: Headers,
   1928     data: Json<OrganizationUserResetPasswordEnrollmentRequest>,
   1929 ) -> Error {
   1930     Error::new(PASS_RESET_MSG, PASS_RESET_MSG)
   1931 }
   1932 
   1933 // This is a new function active since the v2022.9.x clients.
   1934 // It combines the previous two calls done before.
   1935 // We call those two functions here and combine them ourselves.
   1936 //
   1937 // NOTE: It seems clients can't handle uppercase-first keys!!
   1938 //       We need to convert all keys so they have the first character to be a lowercase.
   1939 //       Else the export will be just an empty JSON file.
   1940 #[get("/organizations/<org_id>/export")]
   1941 async fn get_org_export(org_id: &str, headers: AdminHeaders, conn: DbConn) -> Json<Value> {
   1942     use semver::{Version, VersionReq};
   1943     // Since version v2023.1.0 the format of the export is different.
   1944     // Also, this endpoint was created since v2022.9.0.
   1945     // Therefore, we will check for any version smaller then v2023.1.0 and return a different response.
   1946     // If we can't determine the version, we will use the latest default v2023.1.0 and higher.
   1947     // https://github.com/bitwarden/server/blob/9ca93381ce416454734418c3a9f99ab49747f1b6/src/Api/Controllers/OrganizationExportController.cs#L44
   1948     let use_list_response_model = headers.client_version.map_or(false, |client_version| {
   1949         let ver_match = VersionReq::parse("<2023.1.0").unwrap();
   1950         let client_version = Version::parse(&client_version).unwrap();
   1951         ver_match.matches(&client_version)
   1952     });
   1953     // Also both main keys here need to be lowercase, else the export will fail.
   1954     if use_list_response_model {
   1955         // Backwards compatible pre v2023.1.0 response
   1956         Json(json!({
   1957             "collections": {
   1958                 "data": util::convert_json_key_lcase_first(_get_org_collections(org_id, &conn).await),
   1959                 "object": "list",
   1960                 "continuationToken": null,
   1961             },
   1962             "ciphers": {
   1963                 "data": util::convert_json_key_lcase_first(_get_org_details(org_id, &headers.user.uuid, &conn).await),
   1964                 "object": "list",
   1965                 "continuationToken": null,
   1966             }
   1967         }))
   1968     } else {
   1969         // v2023.1.0 and newer response
   1970         Json(json!({
   1971             "collections": util::convert_json_key_lcase_first(_get_org_collections(org_id, &conn).await),
   1972             "ciphers": util::convert_json_key_lcase_first(_get_org_details(org_id, &headers.user.uuid, &conn).await),
   1973         }))
   1974     }
   1975 }
   1976 const API_DISABLED_MSG: &str = "API access is disabled.";
   1977 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1978 #[post("/organizations/<org_id>/api-key", data = "<data>")]
   1979 fn api_key(org_id: &str, data: Json<PasswordOrOtpData>, _headers: AdminHeaders) -> Error {
   1980     Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
   1981 }
   1982 #[allow(unused_variables, clippy::needless_pass_by_value)]
   1983 #[post("/organizations/<org_id>/rotate-api-key", data = "<data>")]
   1984 fn rotate_api_key(org_id: &str, data: Json<PasswordOrOtpData>, _headers: AdminHeaders) -> Error {
   1985     Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
   1986 }