From 2c549984c0f78f35d6a8fcce7390a35b71c2344f Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:27:11 +0100 Subject: [PATCH] let invited members access OrgMemberHeaders (#5461) --- src/auth.rs | 53 ++++++++++++++++++++++++----------- src/db/models/organization.rs | 14 +++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index e4827c80..cfb7c30b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -542,10 +542,29 @@ pub struct OrgHeaders { pub device: Device, pub user: User, pub membership_type: MembershipType, + pub membership_status: MembershipStatus, pub membership: Membership, pub ip: ClientIp, } +impl OrgHeaders { + fn is_member(&self) -> bool { + // NOTE: we don't care about MembershipStatus at the moment because this is only used + // where an invited, accepted or confirmed user is expected if this ever changes or + // if from_i32 is changed to return Some(Revoked) this check needs to be changed accordingly + self.membership_type >= MembershipType::User + } + fn is_confirmed_and_admin(&self) -> bool { + self.membership_status == MembershipStatus::Confirmed && self.membership_type >= MembershipType::Admin + } + fn is_confirmed_and_manager(&self) -> bool { + self.membership_status == MembershipStatus::Confirmed && self.membership_type >= MembershipType::Manager + } + fn is_confirmed_and_owner(&self) -> bool { + self.membership_status == MembershipStatus::Confirmed && self.membership_type == MembershipType::Owner + } +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for OrgHeaders { type Error = &'static str; @@ -574,15 +593,8 @@ 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 { - Some(member) => { - if member.status == MembershipStatus::Confirmed as i32 { - member - } else { - err_handler!("The current user isn't confirmed member of the organization") - } - } - None => err_handler!("The current user isn't member of the organization"), + let Some(membership) = Membership::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await else { + err_handler!("The current user isn't member of the organization"); }; Outcome::Success(Self { @@ -590,13 +602,22 @@ impl<'r> FromRequest<'r> for OrgHeaders { device: headers.device, user, membership_type: { - if let Some(org_usr_type) = MembershipType::from_i32(membership.atype) { - org_usr_type + if let Some(member_type) = MembershipType::from_i32(membership.atype) { + member_type } else { // This should only happen if the DB is corrupted err_handler!("Unknown user type in the database") } }, + membership_status: { + if let Some(member_status) = MembershipStatus::from_i32(membership.status) { + // NOTE: add additional check for revoked if from_i32 is ever changed + // to return Revoked status. + member_status + } else { + err_handler!("User status is either revoked or invalid.") + } + }, membership, ip: headers.ip, }) @@ -621,7 +642,7 @@ impl<'r> FromRequest<'r> for AdminHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); - if headers.membership_type >= MembershipType::Admin { + if headers.is_confirmed_and_admin() { Outcome::Success(Self { host: headers.host, device: headers.device, @@ -683,7 +704,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); - if headers.membership_type >= MembershipType::Manager { + if headers.is_confirmed_and_manager() { match get_col_id(request) { Some(col_id) => { let mut conn = match DbConn::from_request(request).await { @@ -738,7 +759,7 @@ impl<'r> FromRequest<'r> for ManagerHeadersLoose { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); - if headers.membership_type >= MembershipType::Manager { + if headers.is_confirmed_and_manager() { Outcome::Success(Self { host: headers.host, device: headers.device, @@ -801,7 +822,7 @@ impl<'r> FromRequest<'r> for OwnerHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); - if headers.membership_type == MembershipType::Owner { + if headers.is_confirmed_and_owner() { Outcome::Success(Self { device: headers.device, user: headers.user, @@ -826,7 +847,7 @@ impl<'r> FromRequest<'r> for OrgMemberHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); - if headers.membership_type >= MembershipType::User { + if headers.is_member() { Outcome::Success(Self { host: headers.host, user: headers.user, diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 1aa5fb40..aa3e1d01 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -55,6 +55,7 @@ db_object! { } // https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs +#[derive(PartialEq)] pub enum MembershipStatus { Revoked = -1, Invited = 0, @@ -62,6 +63,19 @@ pub enum MembershipStatus { Confirmed = 2, } +impl MembershipStatus { + pub fn from_i32(status: i32) -> Option { + match status { + 0 => Some(Self::Invited), + 1 => Some(Self::Accepted), + 2 => Some(Self::Confirmed), + // NOTE: we don't care about revoked members where this is used + // if this ever changes also adapt the OrgHeaders check. + _ => None, + } + } +} + #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] pub enum MembershipType { Owner = 0,