diff --git a/app/models/user_search.rb b/app/models/user_search.rb index 469c4dcb5f6..7b9f0b9a4dd 100644 --- a/app/models/user_search.rb +++ b/app/models/user_search.rb @@ -15,7 +15,7 @@ class UserSearch @limit = opts[:limit] || 20 @groups = opts[:groups] @guardian = Guardian.new(@searching_user) - @groups.each { |group| @guardian.ensure_can_see_group!(group) } if @groups + @guardian.ensure_can_see_groups!(@groups) if @groups end def scoped_users diff --git a/lib/guardian.rb b/lib/guardian.rb index 87565ba42dd..27bdff357f5 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -208,6 +208,25 @@ class Guardian true end + def can_see_groups?(groups) + return false if groups.blank? + return true if groups.all? { |g| g.visibility_level == Group.visibility_levels[:public] } + return true if is_admin? + return true if is_staff? && groups.all? { |g| g.visibility_level == Group.visibility_levels[:staff] } + return false if user.blank? + + memberships = GroupUser.where(group: groups, user_id: user.id).pluck(:owner) + + return false if memberships.empty? || memberships.length < groups.size + + if !memberships.all? + return false if groups.all? { |g| g.visibility_level == Group.visibility_levels[:owners] } + return false if groups.all? { |g| g.visibility_level == Group.visibility_levels[:staff] } + end + + true + end + # Can we impersonate this user? def can_impersonate?(target) target && diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 64c1ecabbb4..ae6b742e305 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -3038,6 +3038,115 @@ describe Guardian do end + describe '#can_see_groups?' do + it 'correctly handles owner visibile groups' do + group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:owners]) + + member = Fabricate(:user) + group.add(member) + group.save! + + owner = Fabricate(:user) + group.add_owner(owner) + group.reload + + expect(Guardian.new(admin).can_see_groups?([group])).to eq(true) + expect(Guardian.new(moderator).can_see_groups?([group])).to eq(false) + expect(Guardian.new(member).can_see_groups?([group])).to eq(false) + expect(Guardian.new.can_see_groups?([group])).to eq(false) + expect(Guardian.new(owner).can_see_groups?([group])).to eq(true) + end + + it 'correctly handles the case where the user does not own every group' do + group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:owners]) + group2 = Group.new(name: 'group2', visibility_level: Group.visibility_levels[:owners]) + group2.save! + + member = Fabricate(:user) + group.add(member) + group.save! + + owner = Fabricate(:user) + group.add_owner(owner) + group.reload + + expect(Guardian.new(admin).can_see_groups?([group, group2])).to eq(true) + expect(Guardian.new(moderator).can_see_groups?([group, group2])).to eq(false) + expect(Guardian.new(member).can_see_groups?([group, group2])).to eq(false) + expect(Guardian.new.can_see_groups?([group, group2])).to eq(false) + expect(Guardian.new(owner).can_see_groups?([group, group2])).to eq(false) + end + + it 'correctly handles staff visibile groups' do + group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:staff]) + + member = Fabricate(:user) + group.add(member) + group.save! + + owner = Fabricate(:user) + group.add_owner(owner) + group.reload + + expect(Guardian.new(member).can_see_groups?([group])).to eq(false) + expect(Guardian.new(admin).can_see_groups?([group])).to eq(true) + expect(Guardian.new(moderator).can_see_groups?([group])).to eq(true) + expect(Guardian.new(owner).can_see_groups?([group])).to eq(true) + expect(Guardian.new.can_see_groups?([group])).to eq(false) + end + + it 'correctly handles member visibile groups' do + group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:members]) + + member = Fabricate(:user) + group.add(member) + group.save! + + owner = Fabricate(:user) + group.add_owner(owner) + group.reload + + expect(Guardian.new(moderator).can_see_groups?([group])).to eq(false) + expect(Guardian.new.can_see_groups?([group])).to eq(false) + expect(Guardian.new(admin).can_see_groups?([group])).to eq(true) + expect(Guardian.new(member).can_see_groups?([group])).to eq(true) + expect(Guardian.new(owner).can_see_groups?([group])).to eq(true) + end + + it 'correctly handles the case where the user is not a member of every group' do + group1 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:members]) + group2 = Group.new(name: 'group2', visibility_level: Group.visibility_levels[:members]) + group2.save! + + member = Fabricate(:user) + group1.add(member) + group1.save! + + owner = Fabricate(:user) + group1.add_owner(owner) + group1.reload + + expect(Guardian.new(moderator).can_see_groups?([group1, group2])).to eq(false) + expect(Guardian.new.can_see_groups?([group1, group2])).to eq(false) + expect(Guardian.new(admin).can_see_groups?([group1, group2])).to eq(true) + expect(Guardian.new(member).can_see_groups?([group1, group2])).to eq(false) + expect(Guardian.new(owner).can_see_groups?([group1, group2])).to eq(false) + end + + it 'correctly handles public groups' do + group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:public]) + + expect(Guardian.new.can_see_groups?([group])).to eq(true) + end + + it 'correctly handles there case where not every group is public' do + group1 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:public]) + group2 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:private]) + + expect(Guardian.new.can_see_groups?([group1, group2])).to eq(false) + end + end + context 'topic featured link category restriction' do before { SiteSetting.topic_featured_link_enabled = true } let(:guardian) { Guardian.new }