diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 39654d79..26986c64 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -79,7 +79,7 @@ pub struct RegisterData { name: Option, token: Option, #[allow(dead_code)] - organization_user_id: Option, + organization_user_id: Option, } #[derive(Debug, Deserialize)] @@ -106,7 +106,7 @@ fn enforce_password_hint_setting(password_hint: &Option) -> EmptyResult } Ok(()) } -async fn is_email_2fa_required(member_uuid: Option, conn: &mut DbConn) -> bool { +async fn is_email_2fa_required(member_uuid: Option, conn: &mut DbConn) -> bool { if !CONFIG._enable_email_2fa() { return false; } diff --git a/src/api/core/events.rs b/src/api/core/events.rs index a0172ebb..9bd1c806 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, OrganizationId}, + models::{Cipher, Event, Membership, MembershipId, OrganizationId}, DbConn, DbPool, }, util::parse_date, @@ -93,7 +93,7 @@ async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, #[get("/organizations//users//events?")] async fn get_user_events( org_id: &str, - member_id: &str, + member_id: MembershipId, data: EventRange, _headers: AdminHeaders, mut conn: DbConn, @@ -110,7 +110,7 @@ async fn get_user_events( parse_date(&data.end) }; - Event::find_by_org_and_member(org_id, member_id, &start_date, &end_date, &mut conn) + Event::find_by_org_and_member(org_id, &member_id, &start_date, &end_date, &mut conn) .await .iter() .map(|e| e.to_json()) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index bcfd29a6..ffefcd76 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -125,7 +125,7 @@ struct OrganizationUpdateData { struct NewCollectionData { name: String, groups: Vec, - users: Vec, + users: Vec, id: Option, external_id: Option, } @@ -138,6 +138,14 @@ struct NewCollectionObjectData { read_only: bool, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct NewCollectionMemberData { + hide_passwords: bool, + id: MembershipId, + read_only: bool, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct OrgKeyData { @@ -151,6 +159,12 @@ struct OrgBulkIds { ids: Vec, } +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct BulkMembershipIds { + ids: Vec, +} + #[post("/organizations", data = "")] async fn create_organization(headers: Headers, data: Json, mut conn: DbConn) -> JsonResult { if !CONFIG.is_org_creation_allowed(&headers.user.email) { @@ -510,7 +524,7 @@ async fn post_organization_collection_update( async fn delete_organization_collection_user( org_id: OrganizationId, col_id: &str, - member_id: &str, + member_id: MembershipId, _headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { @@ -518,7 +532,7 @@ async fn delete_organization_collection_user( 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 { @@ -533,7 +547,7 @@ async fn delete_organization_collection_user( async fn post_organization_collection_delete_user( org_id: OrganizationId, col_id: &str, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, conn: DbConn, ) -> EmptyResult { @@ -827,7 +841,7 @@ async fn post_org_keys( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CollectionData { - id: String, + id: MembershipId, read_only: bool, hide_passwords: bool, } @@ -895,11 +909,11 @@ async fn send_invite( } }; - let mut new_user = Membership::new(user.uuid.clone(), org_id.clone()); + let mut new_member = 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; - new_user.status = member_status; + new_member.access_all = access_all; + new_member.atype = new_type; + new_member.status = member_status; // If no accessAll, add the collections received if !access_all { @@ -920,16 +934,16 @@ async fn send_invite( } } - new_user.save(&mut conn).await?; + new_member.save(&mut conn).await?; for group in data.groups.iter() { - let mut group_entry = GroupUser::new(String::from(group), user.uuid.clone()); + let mut group_entry = GroupUser::new(String::from(group), new_member.uuid.clone()); group_entry.save(&mut conn).await?; } log_event( EventType::OrganizationUserInvited as i32, - &new_user.uuid, + &new_member.uuid, &org_id, &headers.user.uuid, headers.device.atype, @@ -947,7 +961,7 @@ async fn send_invite( mail::send_invite( &user, Some(org_id.clone()), - Some(new_user.uuid), + Some(new_member.uuid), &org_name, Some(headers.user.email.clone()), ) @@ -961,11 +975,11 @@ async fn send_invite( #[post("/organizations//users/reinvite", data = "")] async fn bulk_reinvite_user( org_id: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, mut conn: DbConn, ) -> Json { - let data: OrgBulkIds = data.into_inner(); + let data: BulkMembershipIds = data.into_inner(); let mut bulk_response = Vec::new(); for member_id in data.ids { @@ -990,18 +1004,23 @@ async fn bulk_reinvite_user( })) } -#[post("/organizations//users//reinvite")] -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 +#[post("/organizations//users//reinvite")] +async fn reinvite_user( + org_id: OrganizationId, + member_id: MembershipId, + headers: AdminHeaders, + mut conn: DbConn, +) -> EmptyResult { + _reinvite_user(&org_id, &member_id, &headers.user.email, &mut conn).await } async fn _reinvite_user( org_id: &OrganizationId, - member: &str, + member_id: &MembershipId, invited_by_email: &str, conn: &mut DbConn, ) -> EmptyResult { - let Some(member) = Membership::find_by_uuid_and_org(member, org_id, conn).await else { + let Some(member) = Membership::find_by_uuid_and_org(member_id, org_id, conn).await else { err!("The user hasn't been invited to the organization.") }; @@ -1054,7 +1073,7 @@ struct AcceptData { #[post("/organizations//users//accept", data = "")] async fn accept_invite( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, mut conn: DbConn, ) -> EmptyResult { @@ -1064,7 +1083,7 @@ async fn accept_invite( // If a claim does not have a member_id or it does not match the one in from the URI, something is wrong. match &claims.member_id { - Some(ou_id) if ou_id.eq(member_id) => {} + Some(ou_id) if ou_id.eq(&member_id) => {} _ => err!("Error accepting the invitation", "Claim does not match the member_id"), } @@ -1139,7 +1158,7 @@ async fn accept_invite( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ConfirmData { - id: Option, + id: Option, key: Option, } @@ -1163,7 +1182,7 @@ async fn bulk_confirm_invite( match data.keys { Some(keys) => { for invite in keys { - let member_id = invite.id.unwrap_or_default(); + let member_id = invite.id.unwrap(); 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 { Ok(_) => String::new(), @@ -1192,7 +1211,7 @@ async fn bulk_confirm_invite( #[post("/organizations//users//confirm", data = "")] async fn confirm_invite( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -1200,12 +1219,12 @@ 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: &OrganizationId, - member_id: &str, + member_id: &MembershipId, key: &str, headers: &AdminHeaders, conn: &mut DbConn, @@ -1283,12 +1302,12 @@ async fn _confirm_invite( #[get("/organizations//users/?")] async fn get_user( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, 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") }; @@ -1313,7 +1332,7 @@ struct EditUserData { #[put("/organizations//users/", data = "", rank = 1)] async fn put_membership( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, headers: AdminHeaders, conn: DbConn, @@ -1324,7 +1343,7 @@ async fn put_membership( #[post("/organizations//users/", data = "", rank = 1)] async fn edit_user( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -1335,7 +1354,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") }; @@ -1429,12 +1448,12 @@ async fn edit_user( #[delete("/organizations//users", data = "")] async fn bulk_delete_user( org_id: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, mut conn: DbConn, nt: Notify<'_>, ) -> Json { - let data: OrgBulkIds = data.into_inner(); + let data: BulkMembershipIds = data.into_inner(); let mut bulk_response = Vec::new(); for member_id in data.ids { @@ -1462,28 +1481,28 @@ async fn bulk_delete_user( #[delete("/organizations//users/")] async fn delete_user( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, 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: OrganizationId, - member_id: &str, + member_id: MembershipId, 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: &OrganizationId, - member_id: &str, + member_id: &MembershipId, headers: &AdminHeaders, conn: &mut DbConn, nt: &Notify<'_>, @@ -1525,11 +1544,11 @@ async fn _delete_user( #[post("/organizations//users/public-keys", data = "")] async fn bulk_public_keys( org_id: OrganizationId, - data: Json, + data: Json, _headers: AdminHeaders, mut conn: DbConn, ) -> Json { - let data: OrgBulkIds = data.into_inner(); + let data: BulkMembershipIds = data.into_inner(); let mut bulk_response = Vec::new(); // Check all received Membership UUID's and find the matching User to retrieve the public-key. @@ -2074,18 +2093,24 @@ async fn import(org_id: OrganizationId, data: Json, headers: Head #[put("/organizations//users//deactivate")] async fn deactivate_membership( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { - _revoke_membership(&org_id, member_id, &headers, &mut conn).await + _revoke_membership(&org_id, &member_id, &headers, &mut conn).await +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct BulkRevokeMembershipIds { + ids: Option>, } // Pre web-vault v2022.9.x endpoint #[put("/organizations//users/deactivate", data = "")] async fn bulk_deactivate_membership( org_id: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, conn: DbConn, ) -> Json { @@ -2095,23 +2120,17 @@ async fn bulk_deactivate_membership( #[put("/organizations//users//revoke")] async fn revoke_membership( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { - _revoke_membership(&org_id, member_id, &headers, &mut conn).await -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct OrgBulkRevokeData { - ids: Option>, + _revoke_membership(&org_id, &member_id, &headers, &mut conn).await } #[put("/organizations//users/revoke", data = "")] async fn bulk_revoke_membership( org_id: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, mut conn: DbConn, ) -> Json { @@ -2147,7 +2166,7 @@ async fn bulk_revoke_membership( async fn _revoke_membership( org_id: &OrganizationId, - member_id: &str, + member_id: &MembershipId, headers: &AdminHeaders, conn: &mut DbConn, ) -> EmptyResult { @@ -2189,18 +2208,18 @@ async fn _revoke_membership( #[put("/organizations//users//activate")] async fn activate_membership( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { - _restore_membership(&org_id, member_id, &headers, &mut conn).await + _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: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, conn: DbConn, ) -> Json { @@ -2210,17 +2229,17 @@ async fn bulk_activate_membership( #[put("/organizations//users//restore")] async fn restore_membership( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { - _restore_membership(&org_id, member_id, &headers, &mut conn).await + _restore_membership(&org_id, &member_id, &headers, &mut conn).await } #[put("/organizations//users/restore", data = "")] async fn bulk_restore_membership( org_id: OrganizationId, - data: Json, + data: Json, headers: AdminHeaders, mut conn: DbConn, ) -> Json { @@ -2251,7 +2270,7 @@ async fn bulk_restore_membership( async fn _restore_membership( org_id: &OrganizationId, - member_id: &str, + member_id: &MembershipId, headers: &AdminHeaders, conn: &mut DbConn, ) -> EmptyResult { @@ -2334,7 +2353,7 @@ struct GroupRequest { access_all: bool, external_id: Option, collections: Vec, - users: Vec, + users: Vec, } impl GroupRequest { @@ -2464,7 +2483,7 @@ async fn put_group( async fn add_update_group( mut group: Group, collections: Vec, - users: Vec, + members: Vec, org_id: OrganizationId, headers: &AdminHeaders, conn: &mut DbConn, @@ -2476,13 +2495,13 @@ async fn add_update_group( collection_group.save(conn).await?; } - for assigned_user_id in users { - let mut user_entry = GroupUser::new(group.uuid.clone(), assigned_user_id.clone()); + for assigned_member in members { + let mut user_entry = GroupUser::new(group.uuid.clone(), assigned_member.clone()); user_entry.save(conn).await?; log_event( EventType::OrganizationUserUpdatedGroups as i32, - &assigned_user_id, + &assigned_member, &org_id, &headers.user.uuid, headers.device.atype, @@ -2609,7 +2628,7 @@ async fn get_group_users( err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") }; - let group_users: Vec = GroupUser::find_by_group(group_id, &mut conn) + let group_users: Vec = GroupUser::find_by_group(group_id, &mut conn) .await .iter() .map(|entry| entry.users_organizations_uuid.clone()) @@ -2623,7 +2642,7 @@ async fn put_group_users( org_id: OrganizationId, group_id: &str, headers: AdminHeaders, - data: Json>, + data: Json>, mut conn: DbConn, ) -> EmptyResult { if !CONFIG.org_groups_enabled() { @@ -2636,14 +2655,14 @@ async fn put_group_users( GroupUser::delete_all_by_group(group_id, &mut conn).await?; - let assigned_user_ids = data.into_inner(); - for assigned_user_id in assigned_user_ids { - let mut user_entry = GroupUser::new(String::from(group_id), assigned_user_id.clone()); + let assigned_members = data.into_inner(); + for assigned_member in assigned_members { + let mut user_entry = GroupUser::new(String::from(group_id), assigned_member.clone()); user_entry.save(&mut conn).await?; log_event( EventType::OrganizationUserUpdatedGroups as i32, - &assigned_user_id, + &assigned_member, &org_id, &headers.user.uuid, headers.device.atype, @@ -2656,10 +2675,10 @@ async fn put_group_users( Ok(()) } -#[get("/organizations//users//groups")] +#[get("/organizations//users//groups")] async fn get_user_groups( org_id: OrganizationId, - user_id: &str, + member_id: MembershipId, _headers: AdminHeaders, mut conn: DbConn, ) -> JsonResult { @@ -2667,12 +2686,12 @@ async fn get_user_groups( 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(&member_id, &org_id, &mut conn).await.is_none() { err!("User could not be found!") }; let user_groups: Vec = - GroupUser::find_by_user(user_id, &mut conn).await.iter().map(|entry| entry.groups_uuid.clone()).collect(); + GroupUser::find_by_member(&member_id, &mut conn).await.iter().map(|entry| entry.groups_uuid.clone()).collect(); Ok(Json(json!(user_groups))) } @@ -2686,7 +2705,7 @@ struct OrganizationUserUpdateGroupsRequest { #[post("/organizations//users//groups", data = "")] async fn post_user_groups( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, headers: AdminHeaders, conn: DbConn, @@ -2697,7 +2716,7 @@ async fn post_user_groups( #[put("/organizations//users//groups", data = "")] async fn put_user_groups( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, data: Json, headers: AdminHeaders, mut conn: DbConn, @@ -2706,7 +2725,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."); } @@ -2714,13 +2733,13 @@ async fn put_user_groups( let assigned_group_ids = data.into_inner(); for assigned_group_id in assigned_group_ids.group_ids { - let mut group_user = GroupUser::new(assigned_group_id.clone(), String::from(member_id)); + let mut group_user = GroupUser::new(assigned_group_id.clone(), member_id.clone()); group_user.save(&mut conn).await?; } log_event( EventType::OrganizationUserUpdatedGroups as i32, - member_id, + &member_id, &org_id, &headers.user.uuid, headers.device.atype, @@ -2736,7 +2755,7 @@ async fn put_user_groups( async fn post_delete_group_user( org_id: OrganizationId, group_id: &str, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, conn: DbConn, ) -> EmptyResult { @@ -2747,7 +2766,7 @@ async fn post_delete_group_user( async fn delete_group_user( org_id: OrganizationId, group_id: &str, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> EmptyResult { @@ -2755,7 +2774,7 @@ 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."); } @@ -2765,7 +2784,7 @@ async fn delete_group_user( log_event( EventType::OrganizationUserUpdatedGroups as i32, - member_id, + &member_id, &org_id, &headers.user.uuid, headers.device.atype, @@ -2774,7 +2793,7 @@ async fn delete_group_user( ) .await; - GroupUser::delete_by_group_id_and_user_id(group_id, member_id, &mut conn).await + GroupUser::delete_by_group_and_member(group_id, &member_id, &mut conn).await } #[derive(Deserialize)] @@ -2817,7 +2836,7 @@ async fn get_organization_keys(org_id: OrganizationId, headers: Headers, conn: D #[put("/organizations//users//reset-password", data = "")] async fn put_reset_password( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, data: Json, mut conn: DbConn, @@ -2827,7 +2846,7 @@ async fn put_reset_password( err!("Required organization not found") }; - let Some(member) = Membership::find_by_uuid_and_org(member_id, &org.uuid, &mut conn).await else { + let Some(member) = Membership::find_by_uuid_and_org(&member_id, &org.uuid, &mut conn).await else { err!("User to reset isn't member of required organization") }; @@ -2835,7 +2854,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"); @@ -2860,7 +2879,7 @@ async fn put_reset_password( log_event( EventType::OrganizationUserAdminResetPassword as i32, - member_id, + &member_id, &org_id, &headers.user.uuid, headers.device.atype, @@ -2875,7 +2894,7 @@ async fn put_reset_password( #[get("/organizations//users//reset-password-details")] async fn get_reset_password_details( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: AdminHeaders, mut conn: DbConn, ) -> JsonResult { @@ -2883,7 +2902,7 @@ async fn get_reset_password_details( 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") }; @@ -2891,7 +2910,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!({ @@ -2908,7 +2927,7 @@ async fn get_reset_password_details( async fn check_reset_password_applicable_and_permissions( org_id: &OrganizationId, - member_id: &str, + member_id: &MembershipId, headers: &AdminHeaders, conn: &mut DbConn, ) -> EmptyResult { @@ -2945,7 +2964,7 @@ async fn check_reset_password_applicable(org_id: &OrganizationId, conn: &mut DbC #[put("/organizations//users//reset-password-enrollment", data = "")] async fn put_reset_password_enrollment( org_id: OrganizationId, - member_id: &str, + member_id: MembershipId, headers: Headers, data: Json, mut conn: DbConn, @@ -2982,7 +3001,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(()) } diff --git a/src/auth.rs b/src/auth.rs index a2cbc9ee..e09d6161 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,7 +14,7 @@ use std::{ net::IpAddr, }; -use crate::db::models::OrganizationId; +use crate::db::models::{MembershipId, OrganizationId}; use crate::{error::Error, CONFIG}; const JWT_ALGORITHM: Algorithm = Algorithm::RS256; @@ -192,7 +192,7 @@ pub struct InviteJwtClaims { pub email: String, pub org_id: Option, - pub member_id: Option, + pub member_id: Option, pub invited_by_email: Option, } @@ -200,7 +200,7 @@ pub fn generate_invite_claims( uuid: String, email: String, org_id: Option, - member_id: Option, + member_id: Option, invited_by_email: Option, ) -> InviteJwtClaims { let time_now = Utc::now(); diff --git a/src/db/models/group.rs b/src/db/models/group.rs index aeb8502a..c0a31761 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -1,4 +1,4 @@ -use super::{Membership, OrganizationId, User}; +use super::{Membership, MembershipId, OrganizationId, User}; use crate::api::EmptyResult; use crate::db::DbConn; use crate::error::MapResult; @@ -34,7 +34,7 @@ db_object! { #[diesel(primary_key(groups_uuid, users_organizations_uuid))] pub struct GroupUser { pub groups_uuid: String, - pub users_organizations_uuid: String + pub users_organizations_uuid: MembershipId } } @@ -124,7 +124,7 @@ impl CollectionGroup { } impl GroupUser { - pub fn new(groups_uuid: String, users_organizations_uuid: String) -> Self { + pub fn new(groups_uuid: String, users_organizations_uuid: MembershipId) -> Self { Self { groups_uuid, users_organizations_uuid, @@ -485,10 +485,10 @@ impl GroupUser { }} } - pub async fn find_by_user(users_organizations_uuid: &str, conn: &mut DbConn) -> Vec { + pub async fn find_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> Vec { db_run! { conn: { groups_users::table - .filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) + .filter(groups_users::users_organizations_uuid.eq(member_uuid)) .load::(conn) .expect("Error loading groups for user") .from_db() @@ -497,7 +497,7 @@ impl GroupUser { pub async fn has_access_to_collection_by_member( collection_uuid: &str, - member_uuid: &str, + member_uuid: &MembershipId, conn: &mut DbConn, ) -> bool { db_run! { conn: { @@ -513,7 +513,11 @@ impl GroupUser { }} } - pub async fn has_full_access_by_member(org_uuid: &OrganizationId, member_uuid: &str, conn: &mut DbConn) -> bool { + pub async fn has_full_access_by_member( + org_uuid: &OrganizationId, + member_uuid: &MembershipId, + conn: &mut DbConn, + ) -> bool { db_run! { conn: { groups_users::table .inner_join(groups::table.on( @@ -535,12 +539,12 @@ impl GroupUser { } } - pub async fn delete_by_group_id_and_user_id( + pub async fn delete_by_group_and_member( group_uuid: &str, - users_organizations_uuid: &str, + member_uuid: &MembershipId, conn: &mut DbConn, ) -> EmptyResult { - match Membership::find_by_uuid(users_organizations_uuid, conn).await { + match Membership::find_by_uuid(member_uuid, conn).await { Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await, None => warn!("Member could not be found!"), }; @@ -548,7 +552,7 @@ impl GroupUser { db_run! { conn: { diesel::delete(groups_users::table) .filter(groups_users::groups_uuid.eq(group_uuid)) - .filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) + .filter(groups_users::users_organizations_uuid.eq(member_uuid)) .execute(conn) .map_res("Error deleting group users") }} @@ -568,7 +572,7 @@ impl GroupUser { }} } - pub async fn delete_all_by_member(member_uuid: &str, conn: &mut DbConn) -> EmptyResult { + pub async fn delete_all_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> EmptyResult { match Membership::find_by_uuid(member_uuid, conn).await { Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await, None => warn!("Member could not be found!"), diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 69b7460e..fe61e42f 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -28,7 +28,7 @@ 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, OrganizationId, + Membership, MembershipId, MembershipStatus, MembershipType, Organization, OrganizationApiKey, OrganizationId, }; pub use self::send::{Send, SendType}; pub use self::two_factor::{TwoFactor, TwoFactorType}; diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index f0a99d24..c63fa16d 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, OrganizationId, TwoFactor}; +use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -336,7 +336,11 @@ impl OrgPolicy { false } - pub async fn is_enabled_for_member(member_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> bool { + pub async fn is_enabled_for_member( + member_uuid: &MembershipId, + policy_type: OrgPolicyType, + conn: &mut DbConn, + ) -> bool { if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await { if let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await { return policy.enabled; diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 73a86b7f..7a3657ed 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -30,7 +30,7 @@ db_object! { #[diesel(table_name = users_organizations)] #[diesel(primary_key(uuid))] pub struct Membership { - pub uuid: String, + pub uuid: MembershipId, pub user_uuid: String, pub org_uuid: OrganizationId, @@ -206,7 +206,7 @@ static ACTIVATE_REVOKE_DIFF: i32 = 128; impl Membership { pub fn new(user_uuid: String, org_uuid: OrganizationId) -> Self { Self { - uuid: crate::util::get_uuid(), + uuid: MembershipId(crate::util::get_uuid()), user_uuid, org_uuid, @@ -459,7 +459,7 @@ impl Membership { let twofactor_enabled = !TwoFactor::find_by_user(&user.uuid, conn).await.is_empty(); let groups: Vec = if include_groups && CONFIG.org_groups_enabled() { - GroupUser::find_by_user(&self.uuid, conn).await.iter().map(|gu| gu.groups_uuid.clone()).collect() + GroupUser::find_by_member(&self.uuid, conn).await.iter().map(|gu| gu.groups_uuid.clone()).collect() } else { // The Bitwarden clients seem to call this API regardless of whether groups are enabled, // so just act as if there are no groups. @@ -699,7 +699,7 @@ impl Membership { (self.access_all || self.atype >= MembershipType::Admin) && self.has_status(MembershipStatus::Confirmed) } - pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid(uuid: &MembershipId, conn: &mut DbConn) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::uuid.eq(uuid)) @@ -708,7 +708,11 @@ impl Membership { }} } - pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option { + pub async fn find_by_uuid_and_org( + uuid: &MembershipId, + org_uuid: &OrganizationId, + conn: &mut DbConn, + ) -> Option { db_run! { conn: { users_organizations::table .filter(users_organizations::uuid.eq(uuid)) @@ -1078,16 +1082,61 @@ impl<'r> FromParam<'r> for OrganizationId { #[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())) + Ok(Self(param.to_string())) } else { Err(()) } } } -#[derive(DieselNewType, Clone, Debug, Hash, PartialEq, Eq, Serialize)] +#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct MembershipId(String); +impl AsRef for MembershipId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Deref for MembershipId { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Borrow for MembershipId { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Display for MembershipId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for MembershipId { + fn from(raw: String) -> Self { + Self(raw) + } +} + +impl<'r> FromParam<'r> for MembershipId { + 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(Self(param.to_string())) + } else { + Err(()) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/mail.rs b/src/mail.rs index e1c8c6da..126adace 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, OrganizationId, User}, + db::models::{Device, DeviceType, MembershipId, OrganizationId, User}, error::Error, CONFIG, }; @@ -260,7 +260,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, - member_id: Option, + member_id: Option, org_name: &str, invited_by_email: Option, ) -> EmptyResult {