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 (66507B)


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