From 84b8de124e640540fdbdb17d3445f55471a4a6ba Mon Sep 17 00:00:00 2001 From: tom27052006 <83423411+tom27052006@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:20:37 +0200 Subject: [PATCH] Keep per-collection manage working for Custom members The manage flag in collection JSON was gated on atype == Manager, a raw comparison that now excludes Custom (4). A Custom member holding an explicit per-collection manage assignment (or full read/write access) would have lost the manage capability in /sync and the collection details endpoints - before this PR they were stored as Manager and matched. Compare by access level (>= Manager, which Manager and Custom share) instead, restoring the exact pre-PR behavior for migrated members. Admins/Owners are unaffected: they are caught by the earlier has_full_access / >= Admin arms. --- src/db/models/collection.rs | 11 ++++++----- src/db/models/organization.rs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index f29843f7..51a8d2c2 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -102,8 +102,9 @@ impl Collection { // Owners and Admins always have true. Users are not able to have full access Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager), Some(m) => { - // Only let a manager manage collections when the have full read/write access - let is_manager = m.atype == MembershipType::Manager; + // Only let a manager-level member (Manager or Custom) manage collections + // when they have full read/write access + let is_manager = m.atype >= MembershipType::Manager; if let Some(cu) = cipher_sync_data.user_collections.get(&self.uuid) { ( cu.read_only, @@ -125,11 +126,11 @@ impl Collection { } else { match Membership::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager), - Some(m) if m.atype == MembershipType::Manager && self.is_manageable_by_user(user_uuid, conn).await => { + Some(m) if m.atype >= MembershipType::Manager && self.is_manageable_by_user(user_uuid, conn).await => { (false, false, true) } Some(m) => { - let is_manager = m.atype == MembershipType::Manager; + let is_manager = m.atype >= MembershipType::Manager; let read_only = !self.is_writable_by_user(user_uuid, conn).await; let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await; (read_only, hide_passwords, is_manager && !read_only && !hide_passwords) @@ -945,7 +946,7 @@ impl CollectionMembership { "hidePasswords": self.hide_passwords, "manage": membership_type >= MembershipType::Admin || self.manage - || (membership_type == MembershipType::Manager + || (membership_type >= MembershipType::Manager && !self.read_only && !self.hide_passwords), }) diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 92ec4973..11789ec9 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -584,7 +584,7 @@ impl Membership { ( cu.read_only, cu.hide_passwords, - cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords), + cu.manage || (self.atype >= MembershipType::Manager && !cu.read_only && !cu.hide_passwords), ) // If previous checks failed it might be that this user has access via a group, but we should not return those elements here // Those are returned via a special group endpoint