diff --git a/Cargo.lock b/Cargo.lock index 311fa43b..c15428a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,6 +778,17 @@ dependencies = [ "url", ] +[[package]] +name = "diesel-derive-newtype" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5adf688c584fe33726ce0e2898f608a2a92578ac94a4a92fcecf73214fe0716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diesel_derives" version = "2.2.3" @@ -3960,6 +3971,7 @@ dependencies = [ "data-encoding", "data-url", "diesel", + "diesel-derive-newtype", "diesel_logger", "diesel_migrations", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index f739145b..12ab7229 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ serde_json = "1.0.133" diesel = { version = "2.2.6", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.2.0" diesel_logger = { version = "0.4.0", optional = true } +diesel-derive-newtype = "2.1.2" # Bundled/Static SQLite libsqlite3-sys = { version = "0.30.1", features = ["bundled"], optional = true } diff --git a/src/api/admin.rs b/src/api/admin.rs index ddb21f79..4cb5ffc0 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -488,7 +488,7 @@ async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> struct MembershipTypeData { user_type: NumberOrString, user_uuid: String, - org_uuid: String, + org_uuid: OrganizationId, } #[post("/users/org_type", data = "")] @@ -570,9 +570,9 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu Ok(Html(text)) } -#[post("/organizations//delete")] -async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { - let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?; +#[post("/organizations//delete")] +async fn delete_organization(org_uuid: OrganizationId, _token: AdminToken, mut conn: DbConn) -> EmptyResult { + let org = Organization::find_by_uuid(&org_uuid, &mut conn).await.map_res("Organization doesn't exist")?; org.delete(&mut conn).await } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 92d72d57..39654d79 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -459,7 +459,7 @@ struct UpdateEmergencyAccessData { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct UpdateResetPasswordData { - organization_id: String, + organization_id: OrganizationId, reset_password_key: String, } @@ -516,9 +516,10 @@ fn validate_keydata( } // Check that we're correctly rotating all the user's reset password keys - let existing_reset_password_ids = existing_memberships.iter().map(|m| m.org_uuid.as_str()).collect::>(); + let existing_reset_password_ids = + existing_memberships.iter().map(|m| &m.org_uuid).collect::>(); let provided_reset_password_ids = - data.reset_password_keys.iter().map(|rp| rp.organization_id.as_str()).collect::>(); + data.reset_password_keys.iter().map(|rp| &rp.organization_id).collect::>(); if !provided_reset_password_ids.is_superset(&existing_reset_password_ids) { err!("All existing reset password keys must be included in the rotation") } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 8e1c4f0e..8558fef7 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -224,7 +224,7 @@ pub struct CipherData { pub folder_id: Option, // TODO: Some of these might appear all the time, no need for Option #[serde(alias = "organizationID")] - pub organization_id: Option, + pub organization_id: Option, key: Option, @@ -1572,14 +1572,14 @@ async fn move_cipher_selected_put( } #[derive(FromForm)] -struct OrganizationId { +struct OrganizationIdData { #[field(name = "organizationId")] - org_id: String, + org_id: OrganizationId, } #[post("/ciphers/purge?", data = "")] async fn delete_all( - organization: Option, + organization: Option, data: Json, headers: Headers, mut conn: DbConn, @@ -1835,10 +1835,10 @@ pub struct CipherSyncData { pub cipher_folders: HashMap, pub cipher_favorites: HashSet, pub cipher_collections: HashMap>, - pub members: HashMap, + pub members: HashMap, pub user_collections: HashMap, pub user_collections_groups: HashMap, - pub user_group_full_access_for_organizations: HashSet, + pub user_group_full_access_for_organizations: HashSet, } #[derive(Eq, PartialEq)] @@ -1885,7 +1885,7 @@ impl CipherSyncData { } // Generate a HashMap with the Organization UUID as key and the Membership record - let members: HashMap = + let members: HashMap = Membership::find_by_user(user_uuid, conn).await.into_iter().map(|m| (m.org_uuid.clone(), m)).collect(); // Generate a HashMap with the User_Collections UUID as key and the CollectionUser record @@ -1907,7 +1907,7 @@ impl CipherSyncData { }; // Get all organizations that the given user has full access to via group assignment - let user_group_full_access_for_organizations: HashSet = if CONFIG.org_groups_enabled() { + let user_group_full_access_for_organizations: HashSet = if CONFIG.org_groups_enabled() { Group::get_orgs_by_user_with_full_access(user_uuid, conn).await.into_iter().collect() } else { HashSet::new() diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 49ff083f..a0172ebb 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -8,7 +8,7 @@ use crate::{ api::{EmptyResult, JsonResult}, auth::{AdminHeaders, Headers}, db::{ - models::{Cipher, Event, Membership}, + models::{Cipher, Event, Membership, OrganizationId}, DbConn, DbPool, }, util::parse_date, @@ -153,7 +153,7 @@ struct EventCollection { // Optional cipher_id: Option, - organization_id: Option, + organization_id: Option, } // Upstream: @@ -261,7 +261,7 @@ async fn _log_user_event( pub async fn log_event( event_type: i32, source_uuid: &str, - org_uuid: &str, + org_uuid: &OrganizationId, act_user_uuid: &str, device_type: i32, ip: &IpAddr, @@ -277,7 +277,7 @@ pub async fn log_event( async fn _log_event( event_type: i32, source_uuid: &str, - org_uuid: &str, + org_uuid: &OrganizationId, act_user_uuid: &str, device_type: i32, event_date: Option, @@ -313,7 +313,7 @@ async fn _log_event( _ => {} } - event.org_uuid = Some(String::from(org_uuid)); + event.org_uuid = Some(org_uuid.clone()); event.act_user_uuid = Some(String::from(act_user_uuid)); event.device_type = Some(device_type); event.ip_address = Some(ip.to_string()); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index b5ffe3ed..bcfd29a6 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -188,7 +188,7 @@ async fn create_organization(headers: Headers, data: Json, mut conn: Db #[delete("/organizations/", data = "")] async fn delete_organization( - org_id: &str, + org_id: OrganizationId, data: Json, headers: OwnerHeaders, mut conn: DbConn, @@ -197,7 +197,7 @@ async fn delete_organization( data.validate(&headers.user, true, &mut conn).await?; - match Organization::find_by_uuid(org_id, &mut conn).await { + match Organization::find_by_uuid(&org_id, &mut conn).await { None => err!("Organization not found"), Some(org) => org.delete(&mut conn).await, } @@ -205,7 +205,7 @@ async fn delete_organization( #[post("/organizations//delete", data = "")] async fn post_delete_organization( - org_id: &str, + org_id: OrganizationId, data: Json, headers: OwnerHeaders, conn: DbConn, @@ -214,12 +214,12 @@ async fn post_delete_organization( } #[post("/organizations//leave")] -async fn leave_organization(org_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult { - match Membership::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await { +async fn leave_organization(org_id: OrganizationId, headers: Headers, mut conn: DbConn) -> EmptyResult { + match Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await { None => err!("User not part of organization"), Some(member) => { if member.atype == MembershipType::Owner - && Membership::count_confirmed_by_org_and_type(org_id, MembershipType::Owner, &mut conn).await <= 1 + && Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &mut conn).await <= 1 { err!("The last owner can't leave") } @@ -227,7 +227,7 @@ async fn leave_organization(org_id: &str, headers: Headers, mut conn: DbConn) -> log_event( EventType::OrganizationUserRemoved as i32, &member.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -241,8 +241,8 @@ async fn leave_organization(org_id: &str, headers: Headers, mut conn: DbConn) -> } #[get("/organizations/")] -async fn get_organization(org_id: &str, _headers: OwnerHeaders, mut conn: DbConn) -> JsonResult { - match Organization::find_by_uuid(org_id, &mut conn).await { +async fn get_organization(org_id: OrganizationId, _headers: OwnerHeaders, mut conn: DbConn) -> JsonResult { + match Organization::find_by_uuid(&org_id, &mut conn).await { Some(organization) => Ok(Json(organization.to_json())), None => err!("Can't find organization details"), } @@ -250,7 +250,7 @@ async fn get_organization(org_id: &str, _headers: OwnerHeaders, mut conn: DbConn #[put("/organizations/", data = "")] async fn put_organization( - org_id: &str, + org_id: OrganizationId, headers: OwnerHeaders, data: Json, conn: DbConn, @@ -260,14 +260,14 @@ async fn put_organization( #[post("/organizations/", data = "")] async fn post_organization( - org_id: &str, + org_id: OrganizationId, headers: OwnerHeaders, data: Json, mut conn: DbConn, ) -> JsonResult { let data: OrganizationUpdateData = data.into_inner(); - let Some(mut org) = Organization::find_by_uuid(org_id, &mut conn).await else { + let Some(mut org) = Organization::find_by_uuid(&org_id, &mut conn).await else { err!("Can't find organization details") }; @@ -278,8 +278,8 @@ async fn post_organization( log_event( EventType::OrganizationUpdated as i32, - org_id, - org_id, + org_id.as_ref(), + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -305,30 +305,35 @@ async fn get_user_collections(headers: Headers, mut conn: DbConn) -> Json } #[get("/organizations//collections")] -async fn get_org_collections(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbConn) -> Json { +async fn get_org_collections(org_id: OrganizationId, _headers: ManagerHeadersLoose, mut conn: DbConn) -> Json { Json(json!({ - "data": _get_org_collections(org_id, &mut conn).await, + "data": _get_org_collections(&org_id, &mut conn).await, "object": "list", "continuationToken": null, })) } #[get("/organizations//collections/details")] -async fn get_org_collections_details(org_id: &str, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { +async fn get_org_collections_details( + org_id: OrganizationId, + headers: ManagerHeadersLoose, + mut conn: DbConn, +) -> JsonResult { let mut data = Vec::new(); - let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await else { + let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await else { err!("User is not part of organization") }; // get all collection memberships for the current organization - let coll_users = CollectionUser::find_by_organization(org_id, &mut conn).await; + let coll_users = CollectionUser::find_by_organization(&org_id, &mut conn).await; // check if current user has full access to the organization (either directly or via any group) let has_full_access_to_org = member.access_all - || (CONFIG.org_groups_enabled() && GroupUser::has_full_access_by_member(org_id, &member.uuid, &mut conn).await); + || (CONFIG.org_groups_enabled() + && GroupUser::has_full_access_by_member(&org_id, &member.uuid, &mut conn).await); - for col in Collection::find_by_organization(org_id, &mut conn).await { + for col in Collection::find_by_organization(&org_id, &mut conn).await { // check whether the current user has access to the given collection let assigned = has_full_access_to_org || CollectionUser::has_access_to_collection_by_user(&col.uuid, &member.user_uuid, &mut conn).await @@ -371,20 +376,20 @@ async fn get_org_collections_details(org_id: &str, headers: ManagerHeadersLoose, }))) } -async fn _get_org_collections(org_id: &str, conn: &mut DbConn) -> Value { +async fn _get_org_collections(org_id: &OrganizationId, conn: &mut DbConn) -> Value { Collection::find_by_organization(org_id, conn).await.iter().map(Collection::to_json).collect::() } #[post("/organizations//collections", data = "")] async fn post_organization_collections( - org_id: &str, + org_id: OrganizationId, headers: ManagerHeadersLoose, data: Json, mut conn: DbConn, ) -> JsonResult { let data: NewCollectionData = data.into_inner(); - let Some(org) = Organization::find_by_uuid(org_id, &mut conn).await else { + let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else { err!("Can't find organization details") }; @@ -394,7 +399,7 @@ async fn post_organization_collections( log_event( EventType::CollectionCreated as i32, &collection.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -409,7 +414,7 @@ async fn post_organization_collections( } for user in data.users { - let Some(member) = Membership::find_by_uuid_and_org(&user.id, org_id, &mut conn).await else { + let Some(member) = Membership::find_by_uuid_and_org(&user.id, &org_id, &mut conn).await else { err!("User is not part of organization") }; @@ -430,7 +435,7 @@ async fn post_organization_collections( #[put("/organizations//collections/", data = "")] async fn put_organization_collection_update( - org_id: &str, + org_id: OrganizationId, col_id: &str, headers: ManagerHeaders, data: Json, @@ -441,7 +446,7 @@ async fn put_organization_collection_update( #[post("/organizations//collections/", data = "")] async fn post_organization_collection_update( - org_id: &str, + org_id: OrganizationId, col_id: &str, headers: ManagerHeaders, data: Json, @@ -449,11 +454,11 @@ async fn post_organization_collection_update( ) -> JsonResult { let data: NewCollectionData = data.into_inner(); - if Organization::find_by_uuid(org_id, &mut conn).await.is_none() { + if Organization::find_by_uuid(&org_id, &mut conn).await.is_none() { err!("Can't find organization details") }; - let Some(mut collection) = Collection::find_by_uuid_and_org(col_id, org_id, &mut conn).await else { + let Some(mut collection) = Collection::find_by_uuid_and_org(col_id, &org_id, &mut conn).await else { err!("Collection not found") }; @@ -468,7 +473,7 @@ async fn post_organization_collection_update( log_event( EventType::CollectionUpdated as i32, &collection.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -487,7 +492,7 @@ async fn post_organization_collection_update( CollectionUser::delete_all_by_collection(col_id, &mut conn).await?; for user in data.users { - let Some(member) = Membership::find_by_uuid_and_org(&user.id, org_id, &mut conn).await else { + let Some(member) = Membership::find_by_uuid_and_org(&user.id, &org_id, &mut conn).await else { err!("User is not part of organization") }; @@ -503,17 +508,17 @@ async fn post_organization_collection_update( #[delete("/organizations//collections//user/")] async fn delete_organization_collection_user( - org_id: &str, + org_id: OrganizationId, col_id: &str, member_id: &str, _headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { - let Some(collection) = Collection::find_by_uuid_and_org(col_id, org_id, &mut conn).await else { + let Some(collection) = Collection::find_by_uuid_and_org(col_id, &org_id, &mut conn).await else { err!("Collection not found", "Collection does not exist or does not belong to this organization") }; - match Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await { + match Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await { None => err!("User not found in organization"), Some(member) => { match CollectionUser::find_by_collection_and_user(&collection.uuid, &member.user_uuid, &mut conn).await { @@ -526,7 +531,7 @@ async fn delete_organization_collection_user( #[post("/organizations//collections//delete-user/")] async fn post_organization_collection_delete_user( - org_id: &str, + org_id: OrganizationId, col_id: &str, member_id: &str, headers: AdminHeaders, @@ -536,7 +541,7 @@ async fn post_organization_collection_delete_user( } async fn _delete_organization_collection( - org_id: &str, + org_id: &OrganizationId, col_id: &str, headers: &ManagerHeaders, conn: &mut DbConn, @@ -559,12 +564,12 @@ async fn _delete_organization_collection( #[delete("/organizations//collections/")] async fn delete_organization_collection( - org_id: &str, + org_id: OrganizationId, col_id: &str, headers: ManagerHeaders, mut conn: DbConn, ) -> EmptyResult { - _delete_organization_collection(org_id, col_id, &headers, &mut conn).await + _delete_organization_collection(&org_id, col_id, &headers, &mut conn).await } #[derive(Deserialize, Debug)] @@ -573,17 +578,17 @@ struct DeleteCollectionData { #[allow(dead_code)] id: String, #[allow(dead_code)] - org_id: String, + org_id: OrganizationId, } #[post("/organizations//collections//delete")] async fn post_organization_collection_delete( - org_id: &str, + org_id: OrganizationId, col_id: &str, headers: ManagerHeaders, mut conn: DbConn, ) -> EmptyResult { - _delete_organization_collection(org_id, col_id, &headers, &mut conn).await + _delete_organization_collection(&org_id, col_id, &headers, &mut conn).await } #[derive(Deserialize, Debug)] @@ -594,7 +599,7 @@ struct BulkCollectionIds { #[delete("/organizations//collections", data = "")] async fn bulk_delete_organization_collections( - org_id: &str, + org_id: OrganizationId, headers: ManagerHeadersLoose, data: Json, mut conn: DbConn, @@ -606,14 +611,14 @@ async fn bulk_delete_organization_collections( let headers = ManagerHeaders::from_loose(headers, &collections, &mut conn).await?; for col_id in collections { - _delete_organization_collection(org_id, &col_id, &headers, &mut conn).await? + _delete_organization_collection(&org_id, &col_id, &headers, &mut conn).await? } Ok(()) } #[get("/organizations//collections//details")] async fn get_org_collection_detail( - org_id: &str, + org_id: OrganizationId, coll_id: &str, headers: ManagerHeaders, mut conn: DbConn, @@ -625,7 +630,7 @@ async fn get_org_collection_detail( err!("Collection is not owned by organization") } - let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await else { + let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await else { err!("User is not part of organization") }; @@ -666,16 +671,21 @@ async fn get_org_collection_detail( } #[get("/organizations//collections//users")] -async fn get_collection_users(org_id: &str, coll_id: &str, _headers: ManagerHeaders, mut conn: DbConn) -> JsonResult { +async fn get_collection_users( + org_id: OrganizationId, + coll_id: &str, + _headers: ManagerHeaders, + mut conn: DbConn, +) -> JsonResult { // Get org and collection, check that collection is from org - let Some(collection) = Collection::find_by_uuid_and_org(coll_id, org_id, &mut conn).await else { + let Some(collection) = Collection::find_by_uuid_and_org(coll_id, &org_id, &mut conn).await else { err!("Collection not found in Organization") }; let mut user_list = Vec::new(); for col_user in CollectionUser::find_by_collection(&collection.uuid, &mut conn).await { user_list.push( - Membership::find_by_user_and_org(&col_user.user_uuid, org_id, &mut conn) + Membership::find_by_user_and_org(&col_user.user_uuid, &org_id, &mut conn) .await .unwrap() .to_json_user_access_restrictions(&col_user), @@ -687,14 +697,14 @@ async fn get_collection_users(org_id: &str, coll_id: &str, _headers: ManagerHead #[put("/organizations//collections//users", data = "")] async fn put_collection_users( - org_id: &str, + org_id: OrganizationId, coll_id: &str, data: Json>, _headers: ManagerHeaders, mut conn: DbConn, ) -> EmptyResult { // Get org and collection, check that collection is from org - if Collection::find_by_uuid_and_org(coll_id, org_id, &mut conn).await.is_none() { + if Collection::find_by_uuid_and_org(coll_id, &org_id, &mut conn).await.is_none() { err!("Collection not found in Organization") } @@ -703,7 +713,7 @@ async fn put_collection_users( // And then add all the received ones (except if the user has access_all) for d in data.iter() { - let Some(user) = Membership::find_by_uuid_and_org(&d.id, org_id, &mut conn).await else { + let Some(user) = Membership::find_by_uuid_and_org(&d.id, &org_id, &mut conn).await else { err!("User is not part of organization") }; @@ -720,7 +730,7 @@ async fn put_collection_users( #[derive(FromForm)] struct OrgIdData { #[field(name = "organizationId")] - organization_id: String, + organization_id: OrganizationId, } #[get("/ciphers/organization-details?")] @@ -737,7 +747,7 @@ async fn get_org_details(data: OrgIdData, headers: Headers, mut conn: DbConn) -> }))) } -async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value { +async fn _get_org_details(org_id: &OrganizationId, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value { let ciphers = Cipher::find_by_org(org_id, conn).await; let cipher_sync_data = CipherSyncData::new(user_uuid, CipherSyncType::Organization, conn).await; @@ -760,12 +770,12 @@ struct GetOrgUserData { #[get("/organizations//users?")] async fn get_members( data: GetOrgUserData, - org_id: &str, + org_id: OrganizationId, _headers: ManagerHeadersLoose, mut conn: DbConn, ) -> Json { let mut users_json = Vec::new(); - for u in Membership::find_by_org(org_id, &mut conn).await { + for u in Membership::find_by_org(&org_id, &mut conn).await { users_json.push( u.to_json_user_details( data.include_collections.unwrap_or(false), @@ -784,10 +794,15 @@ async fn get_members( } #[post("/organizations//keys", data = "")] -async fn post_org_keys(org_id: &str, data: Json, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn post_org_keys( + org_id: OrganizationId, + data: Json, + _headers: AdminHeaders, + mut conn: DbConn, +) -> JsonResult { let data: OrgKeyData = data.into_inner(); - let mut org = match Organization::find_by_uuid(org_id, &mut conn).await { + let mut org = match Organization::find_by_uuid(&org_id, &mut conn).await { Some(organization) => { if organization.private_key.is_some() && organization.public_key.is_some() { err!("Organization Keys already exist") @@ -829,7 +844,12 @@ struct InviteData { } #[post("/organizations//users/invite", data = "")] -async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { +async fn send_invite( + org_id: OrganizationId, + data: Json, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { let data: InviteData = data.into_inner(); let new_type = match MembershipType::from_str(&data.r#type.into_string()) { @@ -863,7 +883,7 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders user } Some(user) => { - if Membership::find_by_user_and_org(&user.uuid, org_id, &mut conn).await.is_some() { + if Membership::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await.is_some() { err!(format!("User already in organization: {email}")) } else { // automatically accept existing users if mail is disabled @@ -875,7 +895,7 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders } }; - let mut new_user = Membership::new(user.uuid.clone(), String::from(org_id)); + let mut new_user = Membership::new(user.uuid.clone(), org_id.clone()); let access_all = data.access_all; new_user.access_all = access_all; new_user.atype = new_type; @@ -884,7 +904,7 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders // If no accessAll, add the collections received if !access_all { for col in data.collections.iter().flatten() { - match Collection::find_by_uuid_and_org(&col.id, org_id, &mut conn).await { + match Collection::find_by_uuid_and_org(&col.id, &org_id, &mut conn).await { None => err!("Collection not found in Organization"), Some(collection) => { CollectionUser::save( @@ -910,7 +930,7 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders log_event( EventType::OrganizationUserInvited as i32, &new_user.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -919,14 +939,14 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders .await; if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { + let org_name = match Organization::find_by_uuid(&org_id, &mut conn).await { Some(org) => org.name, None => err!("Error looking up organization"), }; mail::send_invite( &user, - Some(String::from(org_id)), + Some(org_id.clone()), Some(new_user.uuid), &org_name, Some(headers.user.email.clone()), @@ -940,7 +960,7 @@ async fn send_invite(org_id: &str, data: Json, headers: AdminHeaders #[post("/organizations//users/reinvite", data = "")] async fn bulk_reinvite_user( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -949,7 +969,7 @@ async fn bulk_reinvite_user( let mut bulk_response = Vec::new(); for member_id in data.ids { - let err_msg = match _reinvite_user(org_id, &member_id, &headers.user.email, &mut conn).await { + let err_msg = match _reinvite_user(&org_id, &member_id, &headers.user.email, &mut conn).await { Ok(_) => String::new(), Err(e) => format!("{e:?}"), }; @@ -971,11 +991,16 @@ async fn bulk_reinvite_user( } #[post("/organizations//users//reinvite")] -async fn reinvite_user(org_id: &str, member: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _reinvite_user(org_id, member, &headers.user.email, &mut conn).await +async fn reinvite_user(org_id: OrganizationId, member: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { + _reinvite_user(&org_id, member, &headers.user.email, &mut conn).await } -async fn _reinvite_user(org_id: &str, member: &str, invited_by_email: &str, conn: &mut DbConn) -> EmptyResult { +async fn _reinvite_user( + org_id: &OrganizationId, + member: &str, + invited_by_email: &str, + conn: &mut DbConn, +) -> EmptyResult { let Some(member) = Membership::find_by_uuid_and_org(member, org_id, conn).await else { err!("The user hasn't been invited to the organization.") }; @@ -1000,7 +1025,7 @@ async fn _reinvite_user(org_id: &str, member: &str, invited_by_email: &str, conn if CONFIG.mail_enabled() { mail::send_invite( &user, - Some(org_id.to_string()), + Some(org_id.clone()), Some(member.uuid), &org_name, Some(invited_by_email.to_string()), @@ -1027,7 +1052,12 @@ struct AcceptData { } #[post("/organizations//users//accept", data = "")] -async fn accept_invite(org_id: &str, member_id: &str, data: Json, mut conn: DbConn) -> EmptyResult { +async fn accept_invite( + org_id: OrganizationId, + member_id: &str, + data: Json, + mut conn: DbConn, +) -> EmptyResult { // The web-vault passes org_id and member_id in the URL, but we are just reading them from the JWT instead let data: AcceptData = data.into_inner(); let claims = decode_invite(&data.token)?; @@ -1059,7 +1089,7 @@ async fn accept_invite(org_id: &str, member_id: &str, data: Json, mu // This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_membership_type // It returns different error messages per function. if member.atype < MembershipType::Admin { - match OrgPolicy::is_user_allowed(&member.user_uuid, org_id, false, &mut conn).await { + match OrgPolicy::is_user_allowed(&member.user_uuid, &org_id, false, &mut conn).await { Ok(_) => {} Err(OrgPolicyErr::TwoFactorMissing) => { if CONFIG.email_2fa_auto_fallback() { @@ -1121,7 +1151,7 @@ struct BulkConfirmData { #[post("/organizations//users/confirm", data = "")] async fn bulk_confirm_invite( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -1135,7 +1165,7 @@ async fn bulk_confirm_invite( for invite in keys { let member_id = invite.id.unwrap_or_default(); let user_key = invite.key.unwrap_or_default(); - let err_msg = match _confirm_invite(org_id, &member_id, &user_key, &headers, &mut conn, &nt).await { + let err_msg = match _confirm_invite(&org_id, &member_id, &user_key, &headers, &mut conn, &nt).await { Ok(_) => String::new(), Err(e) => format!("{e:?}"), }; @@ -1161,7 +1191,7 @@ async fn bulk_confirm_invite( #[post("/organizations//users//confirm", data = "")] async fn confirm_invite( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: Json, headers: AdminHeaders, @@ -1170,11 +1200,11 @@ async fn confirm_invite( ) -> EmptyResult { let data = data.into_inner(); let user_key = data.key.unwrap_or_default(); - _confirm_invite(org_id, member_id, &user_key, &headers, &mut conn, &nt).await + _confirm_invite(&org_id, member_id, &user_key, &headers, &mut conn, &nt).await } async fn _confirm_invite( - org_id: &str, + org_id: &OrganizationId, member_id: &str, key: &str, headers: &AdminHeaders, @@ -1252,13 +1282,13 @@ async fn _confirm_invite( #[get("/organizations//users/?")] async fn get_user( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: GetOrgUserData, _headers: AdminHeaders, mut conn: DbConn, ) -> JsonResult { - let Some(user) = Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await else { + let Some(user) = Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await else { err!("The specified user isn't a member of the organization") }; @@ -1282,7 +1312,7 @@ struct EditUserData { #[put("/organizations//users/", data = "", rank = 1)] async fn put_membership( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: Json, headers: AdminHeaders, @@ -1293,7 +1323,7 @@ async fn put_membership( #[post("/organizations//users/", data = "", rank = 1)] async fn edit_user( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: Json, headers: AdminHeaders, @@ -1305,7 +1335,7 @@ async fn edit_user( err!("Invalid type") }; - let Some(mut member_to_edit) = Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await else { + let Some(mut member_to_edit) = Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await else { err!("The specified user isn't member of the organization") }; @@ -1325,7 +1355,7 @@ async fn edit_user( && member_to_edit.status == MembershipStatus::Confirmed as i32 { // Removing owner permission, check that there is at least one other confirmed owner - if Membership::count_confirmed_by_org_and_type(org_id, MembershipType::Owner, &mut conn).await <= 1 { + if Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &mut conn).await <= 1 { err!("Can't delete the last owner") } } @@ -1333,7 +1363,7 @@ async fn edit_user( // This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_membership_type // It returns different error messages per function. if new_type < MembershipType::Admin { - match OrgPolicy::is_user_allowed(&member_to_edit.user_uuid, org_id, true, &mut conn).await { + match OrgPolicy::is_user_allowed(&member_to_edit.user_uuid, &org_id, true, &mut conn).await { Ok(_) => {} Err(OrgPolicyErr::TwoFactorMissing) => { if CONFIG.email_2fa_auto_fallback() { @@ -1352,14 +1382,14 @@ async fn edit_user( member_to_edit.atype = new_type as i32; // Delete all the odd collections - for c in CollectionUser::find_by_organization_and_user_uuid(org_id, &member_to_edit.user_uuid, &mut conn).await { + for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &member_to_edit.user_uuid, &mut conn).await { c.delete(&mut conn).await?; } // If no accessAll, add the collections received if !data.access_all { for col in data.collections.iter().flatten() { - match Collection::find_by_uuid_and_org(&col.id, org_id, &mut conn).await { + match Collection::find_by_uuid_and_org(&col.id, &org_id, &mut conn).await { None => err!("Collection not found in Organization"), Some(collection) => { CollectionUser::save( @@ -1385,7 +1415,7 @@ async fn edit_user( log_event( EventType::OrganizationUserUpdated as i32, &member_to_edit.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1398,7 +1428,7 @@ async fn edit_user( #[delete("/organizations//users", data = "")] async fn bulk_delete_user( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -1408,7 +1438,7 @@ async fn bulk_delete_user( let mut bulk_response = Vec::new(); for member_id in data.ids { - let err_msg = match _delete_user(org_id, &member_id, &headers, &mut conn, &nt).await { + let err_msg = match _delete_user(&org_id, &member_id, &headers, &mut conn, &nt).await { Ok(_) => String::new(), Err(e) => format!("{e:?}"), }; @@ -1431,28 +1461,28 @@ async fn bulk_delete_user( #[delete("/organizations//users/")] async fn delete_user( - org_id: &str, + org_id: OrganizationId, member_id: &str, headers: AdminHeaders, mut conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - _delete_user(org_id, member_id, &headers, &mut conn, &nt).await + _delete_user(&org_id, member_id, &headers, &mut conn, &nt).await } #[post("/organizations//users//delete")] async fn post_delete_user( - org_id: &str, + org_id: OrganizationId, member_id: &str, headers: AdminHeaders, mut conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - _delete_user(org_id, member_id, &headers, &mut conn, &nt).await + _delete_user(&org_id, member_id, &headers, &mut conn, &nt).await } async fn _delete_user( - org_id: &str, + org_id: &OrganizationId, member_id: &str, headers: &AdminHeaders, conn: &mut DbConn, @@ -1494,7 +1524,7 @@ async fn _delete_user( #[post("/organizations//users/public-keys", data = "")] async fn bulk_public_keys( - org_id: &str, + org_id: OrganizationId, data: Json, _headers: AdminHeaders, mut conn: DbConn, @@ -1506,7 +1536,7 @@ async fn bulk_public_keys( // If the user does not exists, just ignore it, and do not return any information regarding that Membership UUID. // The web-vault will then ignore that user for the following steps. for member_id in data.ids { - match Membership::find_by_uuid_and_org(&member_id, org_id, &mut conn).await { + match Membership::find_by_uuid_and_org(&member_id, &org_id, &mut conn).await { Some(member) => match User::find_by_uuid(&member.user_uuid, &mut conn).await { Some(user) => bulk_response.push(json!( { @@ -1614,7 +1644,7 @@ async fn post_org_import( #[serde(rename_all = "camelCase")] #[allow(dead_code)] struct BulkCollectionsData { - organization_id: String, + organization_id: OrganizationId, cipher_ids: Vec, collection_ids: HashSet, remove_collections: bool, @@ -1671,8 +1701,8 @@ async fn post_bulk_collections(data: Json, headers: Headers } #[get("/organizations//policies")] -async fn list_policies(org_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> Json { - let policies = OrgPolicy::find_by_org(org_id, &mut conn).await; +async fn list_policies(org_id: OrganizationId, _headers: AdminHeaders, mut conn: DbConn) -> Json { + let policies = OrgPolicy::find_by_org(&org_id, &mut conn).await; let policies_json: Vec = policies.iter().map(OrgPolicy::to_json).collect(); Json(json!({ @@ -1683,11 +1713,11 @@ async fn list_policies(org_id: &str, _headers: AdminHeaders, mut conn: DbConn) - } #[get("/organizations//policies/token?")] -async fn list_policies_token(org_id: &str, token: &str, mut conn: DbConn) -> JsonResult { +async fn list_policies_token(org_id: OrganizationId, token: &str, mut conn: DbConn) -> JsonResult { // web-vault 2024.6.2 seems to send these values and cause logs to output errors // Catch this and prevent errors in the logs // TODO: CleanUp after 2024.6.x is not used anymore. - if org_id == "undefined" && token == "undefined" { + if org_id.as_ref() == "undefined" && token == "undefined" { return Ok(Json(json!({}))); } @@ -1702,7 +1732,7 @@ async fn list_policies_token(org_id: &str, token: &str, mut conn: DbConn) -> Jso } // TODO: We receive the invite token as ?token=<>, validate it contains the org id - let policies = OrgPolicy::find_by_org(org_id, &mut conn).await; + let policies = OrgPolicy::find_by_org(&org_id, &mut conn).await; let policies_json: Vec = policies.iter().map(OrgPolicy::to_json).collect(); Ok(Json(json!({ @@ -1713,14 +1743,14 @@ async fn list_policies_token(org_id: &str, token: &str, mut conn: DbConn) -> Jso } #[get("/organizations//policies/")] -async fn get_policy(org_id: &str, pol_type: i32, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn get_policy(org_id: OrganizationId, pol_type: i32, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { let Some(pol_type_enum) = OrgPolicyType::from_i32(pol_type) else { err!("Invalid or unsupported policy type") }; - let policy = match OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &mut conn).await { + let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &mut conn).await { Some(p) => p, - None => OrgPolicy::new(String::from(org_id), pol_type_enum, "null".to_string()), + None => OrgPolicy::new(org_id.clone(), pol_type_enum, "null".to_string()), }; Ok(Json(policy.to_json())) @@ -1736,7 +1766,7 @@ struct PolicyData { #[put("/organizations//policies/", data = "")] async fn put_policy( - org_id: &str, + org_id: OrganizationId, pol_type: i32, data: Json, headers: AdminHeaders, @@ -1756,7 +1786,7 @@ async fn put_policy( if CONFIG.enforce_single_org_with_reset_pw_policy() { if pol_type_enum == OrgPolicyType::ResetPassword && data.enabled { let single_org_policy_enabled = - match OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::SingleOrg, &mut conn).await { + match OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::SingleOrg, &mut conn).await { Some(p) => p.enabled, None => false, }; @@ -1769,7 +1799,7 @@ async fn put_policy( // Also prevent the Single Org Policy to be disabled if the Reset Password policy is enabled if pol_type_enum == OrgPolicyType::SingleOrg && !data.enabled { let reset_pw_policy_enabled = - match OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::ResetPassword, &mut conn).await { + match OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::ResetPassword, &mut conn).await { Some(p) => p.enabled, None => false, }; @@ -1783,7 +1813,7 @@ async fn put_policy( // When enabling the TwoFactorAuthentication policy, revoke all members that do not have 2FA if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { two_factor::enforce_2fa_policy_for_org( - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1794,7 +1824,7 @@ async fn put_policy( // When enabling the SingleOrg policy, remove this org's members that are members of other orgs if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled { - for member in Membership::find_by_org(org_id, &mut conn).await.into_iter() { + for member in Membership::find_by_org(&org_id, &mut conn).await.into_iter() { // Policy only applies to non-Owner/non-Admin members who have accepted joining the org // Exclude invited and revoked users when checking for this policy. // Those users will not be allowed to accept or be activated because of the policy checks done there. @@ -1813,7 +1843,7 @@ async fn put_policy( log_event( EventType::OrganizationUserRemoved as i32, &member.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1826,9 +1856,9 @@ async fn put_policy( } } - let mut policy = match OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &mut conn).await { + let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &mut conn).await { Some(p) => p, - None => OrgPolicy::new(String::from(org_id), pol_type_enum, "{}".to_string()), + None => OrgPolicy::new(org_id.clone(), pol_type_enum, "{}".to_string()), }; policy.enabled = data.enabled; @@ -1838,7 +1868,7 @@ async fn put_policy( log_event( EventType::PolicyUpdated as i32, &policy.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1851,7 +1881,7 @@ async fn put_policy( #[allow(unused_variables)] #[get("/organizations//tax")] -fn get_organization_tax(org_id: &str, _headers: Headers) -> Json { +fn get_organization_tax(org_id: OrganizationId, _headers: Headers) -> Json { // Prevent a 404 error, which also causes Javascript errors. // Upstream sends "Only allowed when not self hosted." As an error message. // If we do the same it will also output this to the log, which is overkill. @@ -1936,7 +1966,7 @@ struct OrgImportData { } #[post("/organizations//import", data = "")] -async fn import(org_id: &str, data: Json, headers: Headers, mut conn: DbConn) -> EmptyResult { +async fn import(org_id: OrganizationId, data: Json, headers: Headers, mut conn: DbConn) -> EmptyResult { let data = data.into_inner(); // TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way @@ -1945,7 +1975,7 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c // as opposed to upstream which only removes auto-imported users. // User needs to be admin or owner to use the Directory Connector - match Membership::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await { + match Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await { Some(member) if member.atype >= MembershipType::Admin => { /* Okay, nothing to do */ } Some(_) => err!("User has insufficient permissions to use Directory Connector"), None => err!("User not part of organization"), @@ -1954,11 +1984,11 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c for user_data in &data.users { if user_data.deleted { // If user is marked for deletion and it exists, delete it - if let Some(member) = Membership::find_by_email_and_org(&user_data.email, org_id, &mut conn).await { + if let Some(member) = Membership::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await { log_event( EventType::OrganizationUserRemoved as i32, &member.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1970,7 +2000,7 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c } // If user is not part of the organization, but it exists - } else if Membership::find_by_email_and_org(&user_data.email, org_id, &mut conn).await.is_none() { + } else if Membership::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await.is_none() { if let Some(user) = User::find_by_mail(&user_data.email, &mut conn).await { let member_status = if CONFIG.mail_enabled() { MembershipStatus::Invited as i32 @@ -1978,7 +2008,7 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c MembershipStatus::Accepted as i32 // Automatically mark user as accepted if no email invites }; - let mut new_member = Membership::new(user.uuid.clone(), String::from(org_id)); + let mut new_member = Membership::new(user.uuid.clone(), org_id.clone()); new_member.access_all = false; new_member.atype = MembershipType::User as i32; new_member.status = member_status; @@ -1988,7 +2018,7 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c log_event( EventType::OrganizationUserInvited as i32, &new_member.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -1997,14 +2027,14 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c .await; if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(org_id, &mut conn).await { + let org_name = match Organization::find_by_uuid(&org_id, &mut conn).await { Some(org) => org.name, None => err!("Error looking up organization"), }; mail::send_invite( &user, - Some(String::from(org_id)), + Some(org_id.clone()), Some(new_member.uuid), &org_name, Some(headers.user.email.clone()), @@ -2017,13 +2047,13 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c // 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) if data.overwrite_existing { - for member in Membership::find_by_org_and_type(org_id, MembershipType::User, &mut conn).await { + for member in Membership::find_by_org_and_type(&org_id, MembershipType::User, &mut conn).await { if let Some(user_email) = User::find_by_uuid(&member.user_uuid, &mut conn).await.map(|u| u.email) { if !data.users.iter().any(|u| u.email == user_email) { log_event( EventType::OrganizationUserRemoved as i32, &member.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2042,14 +2072,19 @@ async fn import(org_id: &str, data: Json, headers: Headers, mut c // Pre web-vault v2022.9.x endpoint #[put("/organizations//users//deactivate")] -async fn deactivate_membership(org_id: &str, member_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _revoke_membership(org_id, member_id, &headers, &mut conn).await +async fn deactivate_membership( + org_id: OrganizationId, + member_id: &str, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _revoke_membership(&org_id, member_id, &headers, &mut conn).await } // Pre web-vault v2022.9.x endpoint #[put("/organizations//users/deactivate", data = "")] async fn bulk_deactivate_membership( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, conn: DbConn, @@ -2058,8 +2093,13 @@ async fn bulk_deactivate_membership( } #[put("/organizations//users//revoke")] -async fn revoke_membership(org_id: &str, member_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _revoke_membership(org_id, member_id, &headers, &mut conn).await +async fn revoke_membership( + org_id: OrganizationId, + member_id: &str, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _revoke_membership(&org_id, member_id, &headers, &mut conn).await } #[derive(Deserialize, Debug)] @@ -2070,7 +2110,7 @@ struct OrgBulkRevokeData { #[put("/organizations//users/revoke", data = "")] async fn bulk_revoke_membership( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -2081,7 +2121,7 @@ async fn bulk_revoke_membership( match data.ids { Some(members) => { for member_id in members { - let err_msg = match _revoke_membership(org_id, &member_id, &headers, &mut conn).await { + let err_msg = match _revoke_membership(&org_id, &member_id, &headers, &mut conn).await { Ok(_) => String::new(), Err(e) => format!("{e:?}"), }; @@ -2105,7 +2145,12 @@ async fn bulk_revoke_membership( })) } -async fn _revoke_membership(org_id: &str, member_id: &str, headers: &AdminHeaders, conn: &mut DbConn) -> EmptyResult { +async fn _revoke_membership( + org_id: &OrganizationId, + member_id: &str, + headers: &AdminHeaders, + conn: &mut DbConn, +) -> EmptyResult { match Membership::find_by_uuid_and_org(member_id, org_id, conn).await { Some(mut member) if member.status > MembershipStatus::Revoked as i32 => { if member.user_uuid == headers.user.uuid { @@ -2142,14 +2187,19 @@ async fn _revoke_membership(org_id: &str, member_id: &str, headers: &AdminHeader // Pre web-vault v2022.9.x endpoint #[put("/organizations//users//activate")] -async fn activate_membership(org_id: &str, member_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _restore_membership(org_id, member_id, &headers, &mut conn).await +async fn activate_membership( + org_id: OrganizationId, + member_id: &str, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _restore_membership(&org_id, member_id, &headers, &mut conn).await } // Pre web-vault v2022.9.x endpoint #[put("/organizations//users/activate", data = "")] async fn bulk_activate_membership( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, conn: DbConn, @@ -2158,13 +2208,18 @@ async fn bulk_activate_membership( } #[put("/organizations//users//restore")] -async fn restore_membership(org_id: &str, member_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _restore_membership(org_id, member_id, &headers, &mut conn).await +async fn restore_membership( + org_id: OrganizationId, + member_id: &str, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _restore_membership(&org_id, member_id, &headers, &mut conn).await } #[put("/organizations//users/restore", data = "")] async fn bulk_restore_membership( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -2173,7 +2228,7 @@ async fn bulk_restore_membership( let mut bulk_response = Vec::new(); for member_id in data.ids { - let err_msg = match _restore_membership(org_id, &member_id, &headers, &mut conn).await { + let err_msg = match _restore_membership(&org_id, &member_id, &headers, &mut conn).await { Ok(_) => String::new(), Err(e) => format!("{e:?}"), }; @@ -2194,7 +2249,12 @@ async fn bulk_restore_membership( })) } -async fn _restore_membership(org_id: &str, member_id: &str, headers: &AdminHeaders, conn: &mut DbConn) -> EmptyResult { +async fn _restore_membership( + org_id: &OrganizationId, + member_id: &str, + headers: &AdminHeaders, + conn: &mut DbConn, +) -> EmptyResult { match Membership::find_by_uuid_and_org(member_id, org_id, conn).await { Some(mut member) if member.status < MembershipStatus::Accepted as i32 => { if member.user_uuid == headers.user.uuid { @@ -2243,10 +2303,10 @@ async fn _restore_membership(org_id: &str, member_id: &str, headers: &AdminHeade } #[get("/organizations//groups")] -async fn get_groups(org_id: &str, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { +async fn get_groups(org_id: OrganizationId, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { let groups: Vec = if CONFIG.org_groups_enabled() { // Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::() - let groups = Group::find_by_organization(org_id, &mut conn).await; + let groups = Group::find_by_organization(&org_id, &mut conn).await; let mut groups_json = Vec::with_capacity(groups.len()); for g in groups { @@ -2278,8 +2338,8 @@ struct GroupRequest { } impl GroupRequest { - pub fn to_group(&self, organizations_uuid: &str) -> Group { - Group::new(String::from(organizations_uuid), self.name.clone(), self.access_all, self.external_id.clone()) + pub fn to_group(&self, org_uuid: &OrganizationId) -> Group { + Group::new(org_uuid.clone(), self.name.clone(), self.access_all, self.external_id.clone()) } pub fn update_group(&self, mut group: Group) -> Group { @@ -2328,7 +2388,7 @@ impl SelectionReadOnly { #[post("/organizations//groups/", data = "")] async fn post_group( - org_id: &str, + org_id: OrganizationId, group_id: &str, data: Json, headers: AdminHeaders, @@ -2338,18 +2398,23 @@ async fn post_group( } #[post("/organizations//groups", data = "")] -async fn post_groups(org_id: &str, headers: AdminHeaders, data: Json, mut conn: DbConn) -> JsonResult { +async fn post_groups( + org_id: OrganizationId, + headers: AdminHeaders, + data: Json, + mut conn: DbConn, +) -> JsonResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } let group_request = data.into_inner(); - let group = group_request.to_group(org_id); + let group = group_request.to_group(&org_id); log_event( EventType::GroupCreated as i32, &group.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2362,7 +2427,7 @@ async fn post_groups(org_id: &str, headers: AdminHeaders, data: Json/groups/", data = "")] async fn put_group( - org_id: &str, + org_id: OrganizationId, group_id: &str, data: Json, headers: AdminHeaders, @@ -2372,7 +2437,7 @@ async fn put_group( err!("Group support is disabled"); } - let Some(group) = Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await else { + let Some(group) = Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await else { err!("Group not found", "Group uuid is invalid or does not belong to the organization") }; @@ -2385,7 +2450,7 @@ async fn put_group( log_event( EventType::GroupUpdated as i32, &updated_group.uuid, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2400,7 +2465,7 @@ async fn add_update_group( mut group: Group, collections: Vec, users: Vec, - org_id: &str, + org_id: OrganizationId, headers: &AdminHeaders, conn: &mut DbConn, ) -> JsonResult { @@ -2418,7 +2483,7 @@ async fn add_update_group( log_event( EventType::OrganizationUserUpdatedGroups as i32, &assigned_user_id, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2437,12 +2502,17 @@ async fn add_update_group( } #[get("/organizations//groups//details")] -async fn get_group_details(org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn get_group_details( + org_id: OrganizationId, + group_id: &str, + _headers: AdminHeaders, + mut conn: DbConn, +) -> JsonResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } - let Some(group) = Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await else { + let Some(group) = Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await else { err!("Group not found", "Group uuid is invalid or does not belong to the organization") }; @@ -2450,16 +2520,26 @@ async fn get_group_details(org_id: &str, group_id: &str, _headers: AdminHeaders, } #[post("/organizations//groups//delete")] -async fn post_delete_group(org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _delete_group(org_id, group_id, &headers, &mut conn).await +async fn post_delete_group( + org_id: OrganizationId, + group_id: &str, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _delete_group(&org_id, group_id, &headers, &mut conn).await } #[delete("/organizations//groups/")] -async fn delete_group(org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { - _delete_group(org_id, group_id, &headers, &mut conn).await +async fn delete_group(org_id: OrganizationId, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult { + _delete_group(&org_id, group_id, &headers, &mut conn).await } -async fn _delete_group(org_id: &str, group_id: &str, headers: &AdminHeaders, conn: &mut DbConn) -> EmptyResult { +async fn _delete_group( + org_id: &OrganizationId, + group_id: &str, + headers: &AdminHeaders, + conn: &mut DbConn, +) -> EmptyResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } @@ -2484,7 +2564,7 @@ async fn _delete_group(org_id: &str, group_id: &str, headers: &AdminHeaders, con #[delete("/organizations//groups", data = "")] async fn bulk_delete_groups( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -2496,18 +2576,18 @@ async fn bulk_delete_groups( let data: OrgBulkIds = data.into_inner(); for group_id in data.ids { - _delete_group(org_id, &group_id, &headers, &mut conn).await? + _delete_group(&org_id, &group_id, &headers, &mut conn).await? } Ok(()) } #[get("/organizations//groups/")] -async fn get_group(org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn get_group(org_id: OrganizationId, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } - let Some(group) = Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await else { + let Some(group) = Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await else { err!("Group not found", "Group uuid is invalid or does not belong to the organization") }; @@ -2515,12 +2595,17 @@ async fn get_group(org_id: &str, group_id: &str, _headers: AdminHeaders, mut con } #[get("/organizations//groups//users")] -async fn get_group_users(org_id: &str, group_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn get_group_users( + org_id: OrganizationId, + group_id: &str, + _headers: AdminHeaders, + mut conn: DbConn, +) -> JsonResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } - if Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await.is_none() { + if Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await.is_none() { err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") }; @@ -2535,7 +2620,7 @@ async fn get_group_users(org_id: &str, group_id: &str, _headers: AdminHeaders, m #[put("/organizations//groups//users", data = "")] async fn put_group_users( - org_id: &str, + org_id: OrganizationId, group_id: &str, headers: AdminHeaders, data: Json>, @@ -2545,7 +2630,7 @@ async fn put_group_users( err!("Group support is disabled"); } - if Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await.is_none() { + if Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await.is_none() { err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") }; @@ -2559,7 +2644,7 @@ async fn put_group_users( log_event( EventType::OrganizationUserUpdatedGroups as i32, &assigned_user_id, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2572,12 +2657,17 @@ async fn put_group_users( } #[get("/organizations//users//groups")] -async fn get_user_groups(org_id: &str, user_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { +async fn get_user_groups( + org_id: OrganizationId, + user_id: &str, + _headers: AdminHeaders, + mut conn: DbConn, +) -> JsonResult { if !CONFIG.org_groups_enabled() { err!("Group support is disabled"); } - if Membership::find_by_uuid_and_org(user_id, org_id, &mut conn).await.is_none() { + if Membership::find_by_uuid_and_org(user_id, &org_id, &mut conn).await.is_none() { err!("User could not be found!") }; @@ -2595,7 +2685,7 @@ struct OrganizationUserUpdateGroupsRequest { #[post("/organizations//users//groups", data = "")] async fn post_user_groups( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: Json, headers: AdminHeaders, @@ -2606,7 +2696,7 @@ async fn post_user_groups( #[put("/organizations//users//groups", data = "")] async fn put_user_groups( - org_id: &str, + org_id: OrganizationId, member_id: &str, data: Json, headers: AdminHeaders, @@ -2616,7 +2706,7 @@ async fn put_user_groups( err!("Group support is disabled"); } - if Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await.is_none() { + if Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await.is_none() { err!("User could not be found or does not belong to the organization."); } @@ -2631,7 +2721,7 @@ async fn put_user_groups( log_event( EventType::OrganizationUserUpdatedGroups as i32, member_id, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2644,7 +2734,7 @@ async fn put_user_groups( #[post("/organizations//groups//delete-user/")] async fn post_delete_group_user( - org_id: &str, + org_id: OrganizationId, group_id: &str, member_id: &str, headers: AdminHeaders, @@ -2655,7 +2745,7 @@ async fn post_delete_group_user( #[delete("/organizations//groups//users/")] async fn delete_group_user( - org_id: &str, + org_id: OrganizationId, group_id: &str, member_id: &str, headers: AdminHeaders, @@ -2665,18 +2755,18 @@ async fn delete_group_user( err!("Group support is disabled"); } - if Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await.is_none() { + if Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await.is_none() { err!("User could not be found or does not belong to the organization."); } - if Group::find_by_uuid_and_org(group_id, org_id, &mut conn).await.is_none() { + if Group::find_by_uuid_and_org(group_id, &org_id, &mut conn).await.is_none() { err!("Group could not be found or does not belong to the organization."); } log_event( EventType::OrganizationUserUpdatedGroups as i32, member_id, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2706,8 +2796,8 @@ struct OrganizationUserResetPasswordRequest { // But the clients do not seem to use this at all // Just add it here in case they will #[get("/organizations//public-key")] -async fn get_organization_public_key(org_id: &str, _headers: Headers, mut conn: DbConn) -> JsonResult { - let Some(org) = Organization::find_by_uuid(org_id, &mut conn).await else { +async fn get_organization_public_key(org_id: OrganizationId, _headers: Headers, mut conn: DbConn) -> JsonResult { + let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else { err!("Organization not found") }; @@ -2720,20 +2810,20 @@ async fn get_organization_public_key(org_id: &str, _headers: Headers, mut conn: // Obsolete - Renamed to public-key (2023.8), left for backwards compatibility with older clients // https://github.com/bitwarden/server/blob/25dc0c9178e3e3584074bbef0d4be827b7c89415/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L463-L468 #[get("/organizations//keys")] -async fn get_organization_keys(org_id: &str, headers: Headers, conn: DbConn) -> JsonResult { +async fn get_organization_keys(org_id: OrganizationId, headers: Headers, conn: DbConn) -> JsonResult { get_organization_public_key(org_id, headers, conn).await } #[put("/organizations//users//reset-password", data = "")] async fn put_reset_password( - org_id: &str, + org_id: OrganizationId, member_id: &str, headers: AdminHeaders, data: Json, mut conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - let Some(org) = Organization::find_by_uuid(org_id, &mut conn).await else { + let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else { err!("Required organization not found") }; @@ -2745,7 +2835,7 @@ async fn put_reset_password( err!("User not found") }; - check_reset_password_applicable_and_permissions(org_id, member_id, &headers, &mut conn).await?; + check_reset_password_applicable_and_permissions(&org_id, member_id, &headers, &mut conn).await?; if member.reset_password_key.is_none() { err!("Password reset not or not correctly enrolled"); @@ -2771,7 +2861,7 @@ async fn put_reset_password( log_event( EventType::OrganizationUserAdminResetPassword as i32, member_id, - org_id, + &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, @@ -2784,16 +2874,16 @@ async fn put_reset_password( #[get("/organizations//users//reset-password-details")] async fn get_reset_password_details( - org_id: &str, + org_id: OrganizationId, member_id: &str, headers: AdminHeaders, mut conn: DbConn, ) -> JsonResult { - let Some(org) = Organization::find_by_uuid(org_id, &mut conn).await else { + let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else { err!("Required organization not found") }; - let Some(member) = Membership::find_by_uuid_and_org(member_id, org_id, &mut conn).await else { + let Some(member) = Membership::find_by_uuid_and_org(member_id, &org_id, &mut conn).await else { err!("User to reset isn't member of required organization") }; @@ -2801,7 +2891,7 @@ async fn get_reset_password_details( err!("User not found") }; - check_reset_password_applicable_and_permissions(org_id, member_id, &headers, &mut conn).await?; + check_reset_password_applicable_and_permissions(&org_id, member_id, &headers, &mut conn).await?; // https://github.com/bitwarden/server/blob/3b50ccb9f804efaacdc46bed5b60e5b28eddefcf/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs#L111 Ok(Json(json!({ @@ -2817,7 +2907,7 @@ async fn get_reset_password_details( } async fn check_reset_password_applicable_and_permissions( - org_id: &str, + org_id: &OrganizationId, member_id: &str, headers: &AdminHeaders, conn: &mut DbConn, @@ -2836,7 +2926,7 @@ async fn check_reset_password_applicable_and_permissions( } } -async fn check_reset_password_applicable(org_id: &str, conn: &mut DbConn) -> EmptyResult { +async fn check_reset_password_applicable(org_id: &OrganizationId, conn: &mut DbConn) -> EmptyResult { if !CONFIG.mail_enabled() { err!("Password reset is not supported on an email-disabled instance."); } @@ -2854,22 +2944,22 @@ async fn check_reset_password_applicable(org_id: &str, conn: &mut DbConn) -> Emp #[put("/organizations//users//reset-password-enrollment", data = "")] async fn put_reset_password_enrollment( - org_id: &str, + org_id: OrganizationId, member_id: &str, headers: Headers, data: Json, mut conn: DbConn, ) -> EmptyResult { - let Some(mut member) = Membership::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await else { + let Some(mut member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await else { err!("User to enroll isn't member of required organization") }; - check_reset_password_applicable(org_id, &mut conn).await?; + check_reset_password_applicable(&org_id, &mut conn).await?; let reset_request = data.into_inner(); if reset_request.reset_password_key.is_none() - && OrgPolicy::org_is_reset_password_auto_enroll(org_id, &mut conn).await + && OrgPolicy::org_is_reset_password_auto_enroll(&org_id, &mut conn).await { err!("Reset password can't be withdrawed due to an enterprise policy"); } @@ -2892,7 +2982,7 @@ async fn put_reset_password_enrollment( EventType::OrganizationUserResetPasswordWithdraw as i32 }; - log_event(log_id, member_id, org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; + log_event(log_id, member_id, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; Ok(()) } @@ -2906,7 +2996,7 @@ async fn put_reset_password_enrollment( // Else the export will be just an empty JSON file. #[get("/organizations//export")] async fn get_org_export( - org_id: &str, + org_id: OrganizationId, headers: AdminHeaders, client_version: Option, mut conn: DbConn, @@ -2928,12 +3018,12 @@ async fn get_org_export( // Backwards compatible pre v2023.1.0 response Json(json!({ "collections": { - "data": convert_json_key_lcase_first(_get_org_collections(org_id, &mut conn).await), + "data": convert_json_key_lcase_first(_get_org_collections(&org_id, &mut conn).await), "object": "list", "continuationToken": null, }, "ciphers": { - "data": convert_json_key_lcase_first(_get_org_details(org_id, &headers.host, &headers.user.uuid, &mut conn).await), + "data": convert_json_key_lcase_first(_get_org_details(&org_id, &headers.host, &headers.user.uuid, &mut conn).await), "object": "list", "continuationToken": null, } @@ -2941,14 +3031,14 @@ async fn get_org_export( } else { // v2023.1.0 and newer response Json(json!({ - "collections": convert_json_key_lcase_first(_get_org_collections(org_id, &mut conn).await), - "ciphers": convert_json_key_lcase_first(_get_org_details(org_id, &headers.host, &headers.user.uuid, &mut conn).await), + "collections": convert_json_key_lcase_first(_get_org_collections(&org_id, &mut conn).await), + "ciphers": convert_json_key_lcase_first(_get_org_details(&org_id, &headers.host, &headers.user.uuid, &mut conn).await), })) } } async fn _api_key( - org_id: &str, + org_id: &OrganizationId, data: Json, rotate: bool, headers: AdminHeaders, @@ -2971,7 +3061,7 @@ async fn _api_key( } None => { let api_key = crate::crypto::generate_api_key(); - let new_org_api_key = OrganizationApiKey::new(String::from(org_id), api_key); + let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key); new_org_api_key.save(&conn).await.expect("Error creating organization API Key"); new_org_api_key } @@ -2985,16 +3075,21 @@ async fn _api_key( } #[post("/organizations//api-key", data = "")] -async fn api_key(org_id: &str, data: Json, headers: AdminHeaders, conn: DbConn) -> JsonResult { - _api_key(org_id, data, false, headers, conn).await +async fn api_key( + org_id: OrganizationId, + data: Json, + headers: AdminHeaders, + conn: DbConn, +) -> JsonResult { + _api_key(&org_id, data, false, headers, conn).await } #[post("/organizations//rotate-api-key", data = "")] async fn rotate_api_key( - org_id: &str, + org_id: OrganizationId, data: Json, headers: AdminHeaders, conn: DbConn, ) -> JsonResult { - _api_key(org_id, data, true, headers, conn).await + _api_key(&org_id, data, true, headers, conn).await } diff --git a/src/api/core/public.rs b/src/api/core/public.rs index 1480cef0..7478833f 100644 --- a/src/api/core/public.rs +++ b/src/api/core/public.rs @@ -179,7 +179,7 @@ async fn ldap_import(data: Json, token: PublicToken, mut conn: Db Ok(()) } -pub struct PublicToken(String); +pub struct PublicToken(OrganizationId); #[rocket::async_trait] impl<'r> FromRequest<'r> for PublicToken { @@ -222,7 +222,8 @@ impl<'r> FromRequest<'r> for PublicToken { let Some(org_uuid) = claims.client_id.strip_prefix("organization.") else { err_handler!("Malformed client_id") }; - let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(org_uuid, &conn).await else { + let org_uuid: OrganizationId = org_uuid.to_string().into(); + let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(&org_uuid, &conn).await else { err_handler!("Invalid client_id") }; if org_api_key.org_uuid != claims.client_sub { diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 756ae836..c2facd62 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -208,7 +208,7 @@ pub async fn enforce_2fa_policy( } pub async fn enforce_2fa_policy_for_org( - org_uuid: &str, + org_uuid: &OrganizationId, act_uuid: &str, device_type: i32, ip: &std::net::IpAddr, diff --git a/src/api/identity.rs b/src/api/identity.rs index 1d111987..a32433f0 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -472,7 +472,8 @@ async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: & let Some(org_uuid) = client_id.strip_prefix("organization.") else { err!("Malformed client_id", format!("IP: {}.", ip.ip)) }; - let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(org_uuid, conn).await else { + let org_uuid: OrganizationId = org_uuid.to_string().into(); + let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(&org_uuid, conn).await else { err!("Invalid client_id", format!("IP: {}.", ip.ip)) }; diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 8c925e37..dc650039 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -422,7 +422,7 @@ impl WebSocketUsers { if *NOTIFICATIONS_DISABLED { return; } - let org_uuid = convert_option(cipher.organization_uuid.clone()); + let org_uuid = convert_option(cipher.organization_uuid.as_deref()); // Depending if there are collections provided or not, we need to have different values for the following variables. // The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change. let (user_uuid, collection_uuids, revision_date) = if let Some(collection_uuids) = collection_uuids { diff --git a/src/auth.rs b/src/auth.rs index fda4cb02..a2cbc9ee 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,6 +14,7 @@ use std::{ net::IpAddr, }; +use crate::db::models::OrganizationId; use crate::{error::Error, CONFIG}; const JWT_ALGORITHM: Algorithm = Algorithm::RS256; @@ -190,7 +191,7 @@ pub struct InviteJwtClaims { pub sub: String, pub email: String, - pub org_id: Option, + pub org_id: Option, pub member_id: Option, pub invited_by_email: Option, } @@ -198,7 +199,7 @@ pub struct InviteJwtClaims { pub fn generate_invite_claims( uuid: String, email: String, - org_id: Option, + org_id: Option, member_id: Option, invited_by_email: Option, ) -> InviteJwtClaims { @@ -266,18 +267,18 @@ pub struct OrgApiKeyLoginJwtClaims { pub sub: String, pub client_id: String, - pub client_sub: String, + pub client_sub: OrganizationId, pub scope: Vec, } -pub fn generate_organization_api_key_login_claims(uuid: String, org_id: String) -> OrgApiKeyLoginJwtClaims { +pub fn generate_organization_api_key_login_claims(uuid: String, org_id: OrganizationId) -> OrgApiKeyLoginJwtClaims { let time_now = Utc::now(); OrgApiKeyLoginJwtClaims { nbf: time_now.timestamp(), exp: (time_now + TimeDelta::try_hours(1).unwrap()).timestamp(), iss: JWT_ORG_API_KEY_ISSUER.to_string(), sub: uuid, - client_id: format!("organization.{org_id}"), + client_id: format!("organization.{}", org_id), client_sub: org_id, scope: vec!["api.organization".into()], } @@ -549,17 +550,17 @@ impl<'r> FromRequest<'r> for OrgHeaders { // org_id is usually the second path param ("/organizations/"), // but there are cases where it is a query value. // First check the path, if this is not a valid uuid, try the query values. - let url_org_id: Option<&str> = { + let url_org_id: Option = { let mut url_org_id = None; if let Some(Ok(org_id)) = request.param::<&str>(1) { if uuid::Uuid::parse_str(org_id).is_ok() { - url_org_id = Some(org_id); + url_org_id = Some(org_id.to_string().into()); } } if let Some(Ok(org_id)) = request.query_value::<&str>("organizationId") { if uuid::Uuid::parse_str(org_id).is_ok() { - url_org_id = Some(org_id); + url_org_id = Some(org_id.to_string().into()); } } @@ -574,7 +575,7 @@ impl<'r> FromRequest<'r> for OrgHeaders { }; let user = headers.user; - let membership = match Membership::find_by_user_and_org(&user.uuid, org_id, &mut conn).await { + let membership = match Membership::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await { Some(member) => { if member.status == MembershipStatus::Confirmed as i32 { member diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 65855cc0..266fd192 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -3,6 +3,7 @@ use std::io::ErrorKind; use bigdecimal::{BigDecimal, ToPrimitive}; use serde_json::Value; +use super::OrganizationId; use crate::CONFIG; db_object! { @@ -172,7 +173,7 @@ impl Attachment { }} } - pub async fn size_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn size_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 { db_run! { conn: { let result: Option = attachments::table .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) @@ -189,7 +190,7 @@ impl Attachment { }} } - pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 { db_run! { conn: { attachments::table .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) @@ -203,7 +204,11 @@ impl Attachment { // This will return all attachments linked to the user or org // There is no filtering done here if the user actually has access! // It is used to speed up the sync process, and the matching is done in a different part. - pub async fn find_all_by_user_and_orgs(user_uuid: &str, org_uuids: &Vec, conn: &mut DbConn) -> Vec { + pub async fn find_all_by_user_and_orgs( + user_uuid: &str, + org_uuids: &Vec, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { attachments::table .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index 3aca20cb..ed931edd 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -1,3 +1,4 @@ +use super::OrganizationId; use crate::crypto::ct_eq; use chrono::{NaiveDateTime, Utc}; @@ -9,7 +10,7 @@ db_object! { pub struct AuthRequest { pub uuid: String, pub user_uuid: String, - pub organization_uuid: Option, + pub organization_uuid: Option, pub request_device_identifier: String, pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index c4bd05db..76cd0a77 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -4,7 +4,8 @@ use chrono::{NaiveDateTime, TimeDelta, Utc}; use serde_json::Value; use super::{ - Attachment, CollectionCipher, Favorite, FolderCipher, Group, Membership, MembershipStatus, MembershipType, User, + Attachment, CollectionCipher, Favorite, FolderCipher, Group, Membership, MembershipStatus, MembershipType, + OrganizationId, User, }; use crate::api::core::{CipherData, CipherSyncData, CipherSyncType}; @@ -22,7 +23,7 @@ db_object! { pub updated_at: NaiveDateTime, pub user_uuid: Option, - pub organization_uuid: Option, + pub organization_uuid: Option, pub key: Option, diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index d8aceba3..055ecb2c 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -1,6 +1,6 @@ use serde_json::Value; -use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, User}; +use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User}; use crate::CONFIG; db_object! { @@ -9,7 +9,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct Collection { pub uuid: String, - pub org_uuid: String, + pub org_uuid: OrganizationId, pub name: String, pub external_id: Option, } @@ -35,7 +35,7 @@ db_object! { /// Local methods impl Collection { - pub fn new(org_uuid: String, name: String, external_id: Option) -> Self { + pub fn new(org_uuid: OrganizationId, name: String, external_id: Option) -> Self { let mut new_model = Self { uuid: crate::util::get_uuid(), org_uuid, @@ -185,7 +185,7 @@ impl Collection { }} } - pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult { for collection in Self::find_by_organization(org_uuid, conn).await { collection.delete(conn).await?; } @@ -279,15 +279,19 @@ impl Collection { } } - pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_organization_and_user_uuid( + org_uuid: &OrganizationId, + user_uuid: &str, + conn: &mut DbConn, + ) -> Vec { Self::find_by_user_uuid(user_uuid.to_owned(), conn) .await .into_iter() - .filter(|c| c.org_uuid == org_uuid) + .filter(|c| &c.org_uuid == org_uuid) .collect() } - pub async fn find_by_organization(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { collections::table .filter(collections::org_uuid.eq(org_uuid)) @@ -297,7 +301,7 @@ impl Collection { }} } - pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 { db_run! { conn: { collections::table .filter(collections::org_uuid.eq(org_uuid)) @@ -308,7 +312,7 @@ impl Collection { }} } - pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { db_run! { conn: { collections::table .filter(collections::uuid.eq(uuid)) @@ -498,7 +502,11 @@ impl Collection { /// Database methods impl CollectionUser { - pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_organization_and_user_uuid( + org_uuid: &OrganizationId, + user_uuid: &str, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { users_collections::table .filter(users_collections::user_uuid.eq(user_uuid)) @@ -511,7 +519,7 @@ impl CollectionUser { }} } - pub async fn find_by_organization(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_collections::table .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) @@ -661,7 +669,11 @@ impl CollectionUser { }} } - pub async fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_user_and_org( + user_uuid: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> EmptyResult { let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await; db_run! { conn: { diff --git a/src/db/models/event.rs b/src/db/models/event.rs index 0f9e0e10..3a225803 100644 --- a/src/db/models/event.rs +++ b/src/db/models/event.rs @@ -1,6 +1,7 @@ use crate::db::DbConn; use serde_json::Value; +use super::OrganizationId; use crate::{api::EmptyResult, error::MapResult, CONFIG}; use chrono::{NaiveDateTime, TimeDelta, Utc}; @@ -18,7 +19,7 @@ db_object! { pub uuid: String, pub event_type: i32, // EventType pub user_uuid: Option, - pub org_uuid: Option, + pub org_uuid: Option, pub cipher_uuid: Option, pub collection_uuid: Option, pub group_uuid: Option, diff --git a/src/db/models/group.rs b/src/db/models/group.rs index 06ae4246..aeb8502a 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -1,4 +1,4 @@ -use super::{Membership, User}; +use super::{Membership, OrganizationId, User}; use crate::api::EmptyResult; use crate::db::DbConn; use crate::error::MapResult; @@ -11,7 +11,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct Group { pub uuid: String, - pub organizations_uuid: String, + pub organizations_uuid: OrganizationId, pub name: String, pub access_all: bool, pub external_id: Option, @@ -40,7 +40,12 @@ db_object! { /// Local methods impl Group { - pub fn new(organizations_uuid: String, name: String, access_all: bool, external_id: Option) -> Self { + pub fn new( + organizations_uuid: OrganizationId, + name: String, + access_all: bool, + external_id: Option, + ) -> Self { let now = Utc::now().naive_utc(); let mut new_model = Self { @@ -163,27 +168,27 @@ impl Group { } } - pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult { for group in Self::find_by_organization(org_uuid, conn).await { group.delete(conn).await?; } Ok(()) } - pub async fn find_by_organization(organizations_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { groups::table - .filter(groups::organizations_uuid.eq(organizations_uuid)) + .filter(groups::organizations_uuid.eq(org_uuid)) .load::(conn) .expect("Error loading groups") .from_db() }} } - pub async fn count_by_org(organizations_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 { db_run! { conn: { groups::table - .filter(groups::organizations_uuid.eq(organizations_uuid)) + .filter(groups::organizations_uuid.eq(org_uuid)) .count() .first::(conn) .ok() @@ -191,7 +196,7 @@ impl Group { }} } - pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { db_run! { conn: { groups::table .filter(groups::uuid.eq(uuid)) @@ -202,7 +207,11 @@ impl Group { }} } - pub async fn find_by_external_id_and_org(external_id: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_external_id_and_org( + external_id: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { groups::table .filter(groups::external_id.eq(external_id)) @@ -213,7 +222,7 @@ impl Group { }} } //Returns all organizations the user has full access to - pub async fn get_orgs_by_user_with_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn get_orgs_by_user_with_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec { db_run! { conn: { groups_users::table .inner_join(users_organizations::table.on( @@ -226,12 +235,12 @@ impl Group { .filter(groups::access_all.eq(true)) .select(groups::organizations_uuid) .distinct() - .load::(conn) + .load::(conn) .expect("Error loading organization group full access information for user") }} } - pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> bool { db_run! { conn: { groups::table .inner_join(groups_users::table.on( @@ -504,7 +513,7 @@ impl GroupUser { }} } - pub async fn has_full_access_by_member(org_uuid: &str, member_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn has_full_access_by_member(org_uuid: &OrganizationId, member_uuid: &str, conn: &mut DbConn) -> bool { db_run! { conn: { groups_users::table .inner_join(groups::table.on( diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index b42ed605..69b7460e 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -27,7 +27,9 @@ pub use self::favorite::Favorite; pub use self::folder::{Folder, FolderCipher}; pub use self::group::{CollectionGroup, Group, GroupUser}; pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType}; -pub use self::organization::{Membership, MembershipStatus, MembershipType, Organization, OrganizationApiKey}; +pub use self::organization::{ + Membership, MembershipStatus, MembershipType, Organization, OrganizationApiKey, OrganizationId, +}; pub use self::send::{Send, SendType}; pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor_duo_context::TwoFactorDuoContext; diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index d5cb372c..f0a99d24 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -5,7 +5,7 @@ use crate::api::EmptyResult; use crate::db::DbConn; use crate::error::MapResult; -use super::{Membership, MembershipStatus, MembershipType, TwoFactor}; +use super::{Membership, MembershipStatus, MembershipType, OrganizationId, TwoFactor}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -13,7 +13,7 @@ db_object! { #[diesel(primary_key(uuid))] pub struct OrgPolicy { pub uuid: String, - pub org_uuid: String, + pub org_uuid: OrganizationId, pub atype: i32, pub enabled: bool, pub data: String, @@ -62,7 +62,7 @@ pub enum OrgPolicyErr { /// Local methods impl OrgPolicy { - pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self { + pub fn new(org_uuid: OrganizationId, atype: OrgPolicyType, data: String) -> Self { Self { uuid: crate::util::get_uuid(), org_uuid, @@ -142,7 +142,7 @@ impl OrgPolicy { }} } - pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { org_policies::table .filter(org_policies::org_uuid.eq(org_uuid)) @@ -170,7 +170,11 @@ impl OrgPolicy { }} } - pub async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Option { + pub async fn find_by_org_and_type( + org_uuid: &OrganizationId, + policy_type: OrgPolicyType, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { org_policies::table .filter(org_policies::org_uuid.eq(org_uuid)) @@ -181,7 +185,7 @@ impl OrgPolicy { }} } - pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid))) .execute(conn) @@ -246,14 +250,14 @@ impl OrgPolicy { pub async fn is_applicable_to_user( user_uuid: &str, policy_type: OrgPolicyType, - exclude_org_uuid: Option<&str>, + exclude_org_uuid: Option<&OrganizationId>, conn: &mut DbConn, ) -> bool { for policy in OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await { // Check if we need to skip this organization. - if exclude_org_uuid.is_some() && exclude_org_uuid.unwrap() == policy.org_uuid { + if exclude_org_uuid.is_some() && *exclude_org_uuid.unwrap() == policy.org_uuid { continue; } @@ -268,7 +272,7 @@ impl OrgPolicy { pub async fn is_user_allowed( user_uuid: &str, - org_uuid: &str, + org_uuid: &OrganizationId, exclude_current_org: bool, conn: &mut DbConn, ) -> OrgPolicyResult { @@ -296,7 +300,7 @@ impl OrgPolicy { Ok(()) } - pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &mut DbConn) -> bool { match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { Some(policy) => match serde_json::from_str::(&policy.data) { Ok(opts) => { diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 7eacf573..73a86b7f 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -1,9 +1,13 @@ use chrono::{NaiveDateTime, Utc}; use num_traits::FromPrimitive; +use rocket::request::FromParam; use serde_json::Value; use std::{ + borrow::Borrow, cmp::Ordering, collections::{HashMap, HashSet}, + fmt::{Display, Formatter}, + ops::Deref, }; use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User}; @@ -15,7 +19,7 @@ db_object! { #[diesel(table_name = organizations)] #[diesel(primary_key(uuid))] pub struct Organization { - pub uuid: String, + pub uuid: OrganizationId, pub name: String, pub billing_email: String, pub private_key: Option, @@ -28,7 +32,7 @@ db_object! { pub struct Membership { pub uuid: String, pub user_uuid: String, - pub org_uuid: String, + pub org_uuid: OrganizationId, pub access_all: bool, pub akey: String, @@ -43,7 +47,7 @@ db_object! { #[diesel(primary_key(uuid, org_uuid))] pub struct OrganizationApiKey { pub uuid: String, - pub org_uuid: String, + pub org_uuid: OrganizationId, pub atype: i32, pub api_key: String, pub revision_date: NaiveDateTime, @@ -147,7 +151,7 @@ impl PartialOrd for i32 { impl Organization { pub fn new(name: String, billing_email: String, private_key: Option, public_key: Option) -> Self { Self { - uuid: crate::util::get_uuid(), + uuid: OrganizationId(crate::util::get_uuid()), name, billing_email, private_key, @@ -200,7 +204,7 @@ impl Organization { static ACTIVATE_REVOKE_DIFF: i32 = 128; impl Membership { - pub fn new(user_uuid: String, org_uuid: String) -> Self { + pub fn new(user_uuid: String, org_uuid: OrganizationId) -> Self { Self { uuid: crate::util::get_uuid(), @@ -255,7 +259,7 @@ impl Membership { } impl OrganizationApiKey { - pub fn new(org_uuid: String, api_key: String) -> Self { + pub fn new(org_uuid: OrganizationId, api_key: String) -> Self { Self { uuid: crate::util::get_uuid(), @@ -336,7 +340,7 @@ impl Organization { }} } - pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid(uuid: &OrganizationId, conn: &mut DbConn) -> Option { db_run! { conn: { organizations::table .filter(organizations::uuid.eq(uuid)) @@ -655,7 +659,7 @@ impl Membership { }} } - pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult { for member in Self::find_by_org(org_uuid, conn).await { member.delete(conn).await?; } @@ -669,9 +673,13 @@ impl Membership { Ok(()) } - pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_email_and_org( + email: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { if let Some(user) = User::find_by_mail(email, conn).await { - if let Some(member) = Membership::find_by_user_and_org(&user.uuid, org_id, conn).await { + if let Some(member) = Membership::find_by_user_and_org(&user.uuid, org_uuid, conn).await { return Some(member); } } @@ -700,7 +708,7 @@ impl Membership { }} } - pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::uuid.eq(uuid)) @@ -750,7 +758,7 @@ impl Membership { }} } - pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -759,7 +767,7 @@ impl Membership { }} } - pub async fn find_confirmed_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_confirmed_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -769,7 +777,7 @@ impl Membership { }} } - pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { + pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -780,7 +788,11 @@ impl Membership { }} } - pub async fn find_by_org_and_type(org_uuid: &str, atype: MembershipType, conn: &mut DbConn) -> Vec { + pub async fn find_by_org_and_type( + org_uuid: &OrganizationId, + atype: MembershipType, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -790,7 +802,11 @@ impl Membership { }} } - pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: MembershipType, conn: &mut DbConn) -> i64 { + pub async fn count_confirmed_by_org_and_type( + org_uuid: &OrganizationId, + atype: MembershipType, + conn: &mut DbConn, + ) -> i64 { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -802,7 +818,7 @@ impl Membership { }} } - pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -812,7 +828,11 @@ impl Membership { }} } - pub async fn find_confirmed_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_confirmed_by_user_and_org( + user_uuid: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) @@ -834,12 +854,12 @@ impl Membership { }} } - pub async fn get_orgs_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn get_orgs_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::user_uuid.eq(user_uuid)) .select(users_organizations::org_uuid) - .load::(conn) + .load::(conn) .unwrap_or_default() }} } @@ -863,7 +883,7 @@ impl Membership { }} } - pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -886,7 +906,11 @@ impl Membership { }} } - pub async fn find_by_cipher_and_org_with_group(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_cipher_and_org_with_group( + cipher_uuid: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -924,7 +948,11 @@ impl Membership { }} } - pub async fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_collection_and_org( + collection_uuid: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Vec { db_run! { conn: { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) @@ -941,7 +969,11 @@ impl Membership { }} } - pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_external_id_and_org( + ext_id: &str, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { db_run! {conn: { users_organizations::table .filter( @@ -987,7 +1019,7 @@ impl OrganizationApiKey { } } - pub async fn find_by_org_uuid(org_uuid: &str, conn: &DbConn) -> Option { + pub async fn find_by_org_uuid(org_uuid: &OrganizationId, conn: &DbConn) -> Option { db_run! { conn: { organization_api_key::table .filter(organization_api_key::org_uuid.eq(org_uuid)) @@ -996,7 +1028,7 @@ impl OrganizationApiKey { }} } - pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult { db_run! { conn: { diesel::delete(organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid))) .execute(conn) @@ -1005,6 +1037,57 @@ impl OrganizationApiKey { } } +#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct OrganizationId(String); + +impl AsRef for OrganizationId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Deref for OrganizationId { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Borrow for OrganizationId { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Display for OrganizationId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for OrganizationId { + fn from(raw: String) -> Self { + Self(raw) + } +} + +impl<'r> FromParam<'r> for OrganizationId { + type Error = (); + + #[inline(always)] + fn from_param(param: &'r str) -> Result { + if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) { + Ok(OrganizationId(param.to_string())) + } else { + Err(()) + } + } +} + +#[derive(DieselNewType, Clone, Debug, Hash, PartialEq, Eq, Serialize)] +pub struct MembershipId(String); + #[cfg(test)] mod tests { use super::*; diff --git a/src/db/models/send.rs b/src/db/models/send.rs index fb95f86b..9e49ed5f 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -3,7 +3,7 @@ use serde_json::Value; use crate::util::LowerCase; -use super::User; +use super::{OrganizationId, User}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -14,8 +14,7 @@ db_object! { pub uuid: String, pub user_uuid: Option, - pub organization_uuid: Option, - + pub organization_uuid: Option, pub name: String, pub notes: Option, @@ -332,7 +331,7 @@ impl Send { Some(total) } - pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec { db_run! {conn: { sends::table .filter(sends::organization_uuid.eq(org_uuid)) diff --git a/src/mail.rs b/src/mail.rs index a8e250c8..e1c8c6da 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -17,7 +17,7 @@ use crate::{ encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims, generate_verify_email_claims, }, - db::models::{Device, DeviceType, User}, + db::models::{Device, DeviceType, OrganizationId, User}, error::Error, CONFIG, }; @@ -259,7 +259,7 @@ pub async fn send_single_org_removed_from_org(address: &str, org_name: &str) -> pub async fn send_invite( user: &User, - org_id: Option, + org_id: Option, member_id: Option, org_name: &str, invited_by_email: Option, diff --git a/src/main.rs b/src/main.rs index 3a151cdf..530c7b2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,8 @@ extern crate log; extern crate diesel; #[macro_use] extern crate diesel_migrations; +#[macro_use] +extern crate diesel_derive_newtype; use std::{ collections::HashMap,