Don't treat anonymous users as owners when group has pending invites

The `members` table can have entries where `user_id: nil`, because people can
invite group members by email. We never want to include those as members,
because it might cause confusion with the anonymous (logged out) user.
This commit is contained in:
Sean McGivern 2017-07-24 11:35:54 +01:00
parent f81ed493e1
commit ccac2abeba
6 changed files with 37 additions and 6 deletions

View file

@ -167,10 +167,14 @@ class Group < Namespace
end end
def has_owner?(user) def has_owner?(user)
return false unless user
members_with_parents.owners.where(user_id: user).any? members_with_parents.owners.where(user_id: user).any?
end end
def has_master?(user) def has_master?(user)
return false unless user
members_with_parents.masters.where(user_id: user).any? members_with_parents.masters.where(user_id: user).any?
end end
@ -212,7 +216,7 @@ class Group < Namespace
end end
def members_with_parents def members_with_parents
GroupMember.non_request.where(source_id: ancestors.pluck(:id).push(id)) GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil)
end end
def users_with_parents def users_with_parents

View file

@ -10,7 +10,8 @@ class ProjectPolicy < BasePolicy
desc "User is a project owner" desc "User is a project owner"
condition :owner do condition :owner do
@user && project.owner == @user || (project.group && project.group.has_owner?(@user)) (project.owner.present? && project.owner == @user) ||
project.group&.has_owner?(@user)
end end
desc "Project has public builds enabled" desc "Project has public builds enabled"

View file

@ -0,0 +1,4 @@
---
title: Fix anonymous access to public projects in groups with pending invites
merge_request:
author:

View file

@ -98,7 +98,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true) user2 = build(:user, external: true)
users = [user1, user2] users = [user1, user2]
expect(project).to receive(:owner).twice.and_return(user1) expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project)) expect(described_class.users_that_can_read_project(users, project))
.to eq([user1]) .to eq([user1])
@ -109,7 +109,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true) user2 = build(:user, external: true)
users = [user1, user2] users = [user1, user2]
expect(project.team).to receive(:members).twice.and_return([user1]) expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project)) expect(described_class.users_that_can_read_project(users, project))
.to eq([user1]) .to eq([user1])
@ -140,7 +140,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true) user2 = build(:user, external: true)
users = [user1, user2] users = [user1, user2]
expect(project).to receive(:owner).twice.and_return(user1) expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project)) expect(described_class.users_that_can_read_project(users, project))
.to eq([user1]) .to eq([user1])
@ -151,7 +151,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true) user2 = build(:user, external: true)
users = [user1, user2] users = [user1, user2]
expect(project.team).to receive(:members).twice.and_return([user1]) expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project)) expect(described_class.users_that_can_read_project(users, project))
.to eq([user1]) .to eq([user1])

View file

@ -236,6 +236,7 @@ describe Group, models: true do
describe '#has_owner?' do describe '#has_owner?' do
before do before do
@members = setup_group_members(group) @members = setup_group_members(group)
create(:group_member, :invited, :owner, group: group)
end end
it { expect(group.has_owner?(@members[:owner])).to be_truthy } it { expect(group.has_owner?(@members[:owner])).to be_truthy }
@ -244,11 +245,13 @@ describe Group, models: true do
it { expect(group.has_owner?(@members[:reporter])).to be_falsey } it { expect(group.has_owner?(@members[:reporter])).to be_falsey }
it { expect(group.has_owner?(@members[:guest])).to be_falsey } it { expect(group.has_owner?(@members[:guest])).to be_falsey }
it { expect(group.has_owner?(@members[:requester])).to be_falsey } it { expect(group.has_owner?(@members[:requester])).to be_falsey }
it { expect(group.has_owner?(nil)).to be_falsey }
end end
describe '#has_master?' do describe '#has_master?' do
before do before do
@members = setup_group_members(group) @members = setup_group_members(group)
create(:group_member, :invited, :master, group: group)
end end
it { expect(group.has_master?(@members[:owner])).to be_falsey } it { expect(group.has_master?(@members[:owner])).to be_falsey }
@ -257,6 +260,7 @@ describe Group, models: true do
it { expect(group.has_master?(@members[:reporter])).to be_falsey } it { expect(group.has_master?(@members[:reporter])).to be_falsey }
it { expect(group.has_master?(@members[:guest])).to be_falsey } it { expect(group.has_master?(@members[:guest])).to be_falsey }
it { expect(group.has_master?(@members[:requester])).to be_falsey } it { expect(group.has_master?(@members[:requester])).to be_falsey }
it { expect(group.has_master?(nil)).to be_falsey }
end end
describe '#lfs_enabled?' do describe '#lfs_enabled?' do

View file

@ -127,6 +127,24 @@ describe ProjectPolicy, models: true do
end end
end end
context 'when a project has pending invites, and the current user is anonymous' do
let(:group) { create(:group, :public) }
let(:project) { create(:empty_project, :public, namespace: group) }
let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
let(:anonymous_permissions) { guest_permissions - user_permissions }
subject { described_class.new(nil, project) }
before do
create(:group_member, :invited, group: group)
end
it 'does not grant owner access' do
expect_allowed(*anonymous_permissions)
expect_disallowed(*user_permissions)
end
end
context 'abilities for non-public projects' do context 'abilities for non-public projects' do
let(:project) { create(:empty_project, namespace: owner.namespace) } let(:project) { create(:empty_project, namespace: owner.namespace) }