diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 09946658..3ccead45 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1871,6 +1871,8 @@ async fn post_org_import( for mut cipher_data in data.ciphers { // Always clear folder_id's via an organization import cipher_data.folder_id = None; + // Replace the client-provided, unvalidated organizationId with the real target org + cipher_data.organization_id = Some(org_id.clone()); let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); update_cipher_from_data( &mut cipher, @@ -2461,8 +2463,14 @@ async fn get_groups_data( err!("Organization not found", "Organization id's do not match"); } - if !headers.membership.has_full_access() { - err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + // For now, both the group list and the details view require full access to the organization + // (directly or via a group). Bitwarden also lets a manager of a specific collection read the + // plain list (to assign groups); handling that case is left for a follow-up. + let has_full_access = headers.membership.has_full_access() + || (CONFIG.org_groups_enabled() + && GroupUser::has_full_access_by_member(&org_id, &headers.membership.uuid, &conn).await); + if !has_full_access { + err_code!("Resource not found.", "User does not have access", rocket::http::Status::NotFound.code); } let groups: Vec = if CONFIG.org_groups_enabled() { diff --git a/src/auth.rs b/src/auth.rs index 9015de43..762088e5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1104,6 +1104,8 @@ impl<'r> FromRequest<'r> for ClientIp { }) } else { if CONFIG._ip_header_enabled() && req.headers().get_one(&CONFIG.ip_header()).is_some() { + // Log the canonical IP, which is what the user filter will need to match against + let remote = remote.map(|ip| ip.to_canonical()); debug!("Ignoring the '{}' header, {remote:?} is not a trusted proxy", CONFIG.ip_header()); } None