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 }