Commit c5ce46a8 authored by Manoj M J's avatar Manoj M J

Limit Project Authorizations refresh to direct members of the group

This change limits the project authorizations
refresh jobs only to direct members of the group
being shared with.
parent 9b664910
...@@ -365,13 +365,28 @@ class Group < Namespace ...@@ -365,13 +365,28 @@ class Group < Namespace
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def refresh_members_authorized_projects(blocking: true, priority: UserProjectAccessChangedService::HIGH_PRIORITY) def refresh_members_authorized_projects(
blocking: true,
priority: UserProjectAccessChangedService::HIGH_PRIORITY,
direct_members_only: false
)
user_ids = if direct_members_only
users_ids_of_direct_members
else
user_ids_for_project_authorizations
end
UserProjectAccessChangedService UserProjectAccessChangedService
.new(user_ids_for_project_authorizations) .new(user_ids)
.execute(blocking: blocking, priority: priority) .execute(blocking: blocking, priority: priority)
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def users_ids_of_direct_members
direct_members.pluck(:user_id)
end
def user_ids_for_project_authorizations def user_ids_for_project_authorizations
members_with_parents.pluck(:user_id) members_with_parents.pluck(:user_id)
end end
...@@ -382,6 +397,12 @@ class Group < Namespace ...@@ -382,6 +397,12 @@ class Group < Namespace
end end
end end
def direct_members
GroupMember.active_without_invites_and_requests
.non_minimal_access
.where(source_id: id)
end
def members_with_parents def members_with_parents
# Avoids an unnecessary SELECT when the group has no parents # Avoids an unnecessary SELECT when the group has no parents
source_ids = source_ids =
......
...@@ -31,11 +31,11 @@ module Groups ...@@ -31,11 +31,11 @@ module Groups
# If any other groups are shared with the group that is being destroyed, # If any other groups are shared with the group that is being destroyed,
# we should specifically trigger update of all project authorizations # we should specifically trigger update of all project authorizations
# for users that are the members of this group. # for users that are the direct members of this group.
# If not, the project authorization records of these users to projects within the shared groups # If not, the project authorization records of these users to projects within the shared groups
# will never be removed, causing inconsistencies with access permissions. # will never be removed, causing inconsistencies with access permissions.
if any_other_groups_are_shared_with_this_group? if any_other_groups_are_shared_with_this_group?
user_ids_for_project_authorizations_refresh = group.user_ids_for_project_authorizations user_ids_for_project_authorizations_refresh = group.users_ids_of_direct_members
end end
group.destroy group.destroy
......
...@@ -18,7 +18,7 @@ module Groups ...@@ -18,7 +18,7 @@ module Groups
) )
if link.save if link.save
group.refresh_members_authorized_projects group.refresh_members_authorized_projects(direct_members_only: true)
success(link: link) success(link: link)
else else
error(link.errors.full_messages.to_sentence, 409) error(link.errors.full_messages.to_sentence, 409)
......
...@@ -16,7 +16,7 @@ module Groups ...@@ -16,7 +16,7 @@ module Groups
groups_to_refresh = links.map(&:shared_with_group) groups_to_refresh = links.map(&:shared_with_group)
groups_to_refresh.uniq.each do |group| groups_to_refresh.uniq.each do |group|
group.refresh_members_authorized_projects group.refresh_members_authorized_projects(direct_members_only: true)
end end
else else
Gitlab::AppLogger.info( Gitlab::AppLogger.info(
......
...@@ -13,7 +13,7 @@ module Groups ...@@ -13,7 +13,7 @@ module Groups
group_link.update!(group_link_params) group_link.update!(group_link_params)
if requires_authorization_refresh?(group_link_params) if requires_authorization_refresh?(group_link_params)
group_link.shared_with_group.refresh_members_authorized_projects group_link.shared_with_group.refresh_members_authorized_projects(direct_members_only: true)
end end
end end
......
---
title: Limit Project Authorizations refresh for shared groups only to direct members
of the group being shared with
merge_request: 51869
author:
type: performance
...@@ -740,6 +740,33 @@ RSpec.describe Group do ...@@ -740,6 +740,33 @@ RSpec.describe Group do
end end
end end
describe '#direct_members' do
let_it_be(:group) { create(:group, :nested) }
let_it_be(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
let_it_be(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
it 'does not return members of the parent' do
expect(group.direct_members).not_to include(maintainer)
end
it 'returns the direct member of the group' do
expect(group.direct_members).to include(developer)
end
context 'group sharing' do
let!(:shared_group) { create(:group) }
before do
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
it 'does not return members of the shared_with group' do
expect(shared_group.direct_members).not_to(
include(developer))
end
end
end
describe '#members_with_parents' do describe '#members_with_parents' do
let!(:group) { create(:group, :nested) } let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) } let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
...@@ -932,6 +959,65 @@ RSpec.describe Group do ...@@ -932,6 +959,65 @@ RSpec.describe Group do
end end
end end
describe '#refresh_members_authorized_projects' do
let_it_be(:group) { create(:group, :nested) }
let_it_be(:parent_group_user) { create(:user) }
let_it_be(:group_user) { create(:user) }
before do
group.parent.add_maintainer(parent_group_user)
group.add_developer(group_user)
end
context 'users for which authorizations refresh is executed' do
it 'processes authorizations refresh for all members of the group' do
expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id, parent_group_user.id)).and_call_original
group.refresh_members_authorized_projects
end
context 'when explicitly specified to run only for direct members' do
it 'processes authorizations refresh only for direct members of the group' do
expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id)).and_call_original
group.refresh_members_authorized_projects(direct_members_only: true)
end
end
end
end
describe '#users_ids_of_direct_members' do
let_it_be(:group) { create(:group, :nested) }
let_it_be(:parent_group_user) { create(:user) }
let_it_be(:group_user) { create(:user) }
before do
group.parent.add_maintainer(parent_group_user)
group.add_developer(group_user)
end
it 'does not return user ids of the members of the parent' do
expect(group.users_ids_of_direct_members).not_to include(parent_group_user.id)
end
it 'returns the user ids of the direct member of the group' do
expect(group.users_ids_of_direct_members).to include(group_user.id)
end
context 'group sharing' do
let!(:shared_group) { create(:group) }
before do
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
it 'does not return the user ids of members of the shared_with group' do
expect(shared_group.users_ids_of_direct_members).not_to(
include(group_user.id))
end
end
end
describe '#user_ids_for_project_authorizations' do describe '#user_ids_for_project_authorizations' do
it 'returns the user IDs for which to refresh authorizations' do it 'returns the user IDs for which to refresh authorizations' do
maintainer = create(:user) maintainer = create(:user)
......
...@@ -229,10 +229,10 @@ RSpec.describe Groups::DestroyService do ...@@ -229,10 +229,10 @@ RSpec.describe Groups::DestroyService do
# will still be executed for the nested group as they fall under the same hierarchy # will still be executed for the nested group as they fall under the same hierarchy
# and hence we need to account for this scenario. # and hence we need to account for this scenario.
expect(UserProjectAccessChangedService) expect(UserProjectAccessChangedService)
.to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original .to receive(:new).with(shared_with_group.users_ids_of_direct_members).and_call_original
expect(UserProjectAccessChangedService) expect(UserProjectAccessChangedService)
.not_to receive(:new).with(shared_group.user_ids_for_project_authorizations) .not_to receive(:new).with(shared_group.users_ids_of_direct_members)
destroy_group(shared_group, user, false) destroy_group(shared_group, user, false)
end end
...@@ -246,7 +246,7 @@ RSpec.describe Groups::DestroyService do ...@@ -246,7 +246,7 @@ RSpec.describe Groups::DestroyService do
it 'makes use of a specific service to update project authorizations' do it 'makes use of a specific service to update project authorizations' do
expect(UserProjectAccessChangedService) expect(UserProjectAccessChangedService)
.to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original .to receive(:new).with(shared_with_group.users_ids_of_direct_members).and_call_original
destroy_group(shared_with_group, user, false) destroy_group(shared_with_group, user, false)
end end
......
...@@ -74,46 +74,56 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do ...@@ -74,46 +74,56 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
end end
end end
context 'group hierarchies' do context 'project authorizations based on group hierarchies' do
before do before do
group_parent.add_owner(parent_group_user) group_parent.add_owner(parent_group_user)
group.add_owner(group_user) group.add_owner(group_user)
group_child.add_owner(child_group_user) group_child.add_owner(child_group_user)
end end
context 'group user' do context 'project authorizations refresh' do
let(:user) { group_user } it 'is executed only for the direct members of the group' do
expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id)).and_call_original
it 'create proper authorizations' do
subject.execute(shared_group) subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_truthy
expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
end end
end end
context 'parent group user' do context 'project authorizations' do
let(:user) { parent_group_user } context 'group user' do
let(:user) { group_user }
it 'create proper authorizations' do it 'create proper authorizations' do
subject.execute(shared_group) subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_falsey expect(Ability.allowed?(user, :read_project, project)).to be_truthy
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
end
end end
end
context 'child group user' do context 'parent group user' do
let(:user) { child_group_user } let(:user) { parent_group_user }
it 'create proper authorizations' do it 'create proper authorizations' do
subject.execute(shared_group) subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
end
end
context 'child group user' do
let(:user) { child_group_user }
it 'create proper authorizations' do
subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_falsey expect(Ability.allowed?(user, :read_project, project)).to be_falsey
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
end
end end
end end
end end
......
...@@ -47,8 +47,8 @@ RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do ...@@ -47,8 +47,8 @@ RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
it 'updates project authorization once per group' do it 'updates project authorization once per group' do
expect(GroupGroupLink).to receive(:delete).and_call_original expect(GroupGroupLink).to receive(:delete).and_call_original
expect(group).to receive(:refresh_members_authorized_projects).once expect(group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true).once
expect(another_group).to receive(:refresh_members_authorized_projects).once expect(another_group).to receive(:refresh_members_authorized_projects).with(direct_members_only: true).once
subject.execute(links) subject.execute(links)
end end
......
...@@ -8,7 +8,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do ...@@ -8,7 +8,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
let_it_be(:group) { create(:group, :private) } let_it_be(:group) { create(:group, :private) }
let_it_be(:shared_group) { create(:group, :private) } let_it_be(:shared_group) { create(:group, :private) }
let_it_be(:project) { create(:project, group: shared_group) } let_it_be(:project) { create(:project, group: shared_group) }
let(:group_member) { create(:user) } let(:group_member_user) { create(:user) }
let!(:link) { create(:group_group_link, shared_group: shared_group, shared_with_group: group) } let!(:link) { create(:group_group_link, shared_group: shared_group, shared_with_group: group) }
let(:expiry_date) { 1.month.from_now.to_date } let(:expiry_date) { 1.month.from_now.to_date }
...@@ -20,7 +20,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do ...@@ -20,7 +20,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
subject { described_class.new(link).execute(group_link_params) } subject { described_class.new(link).execute(group_link_params) }
before do before do
group.add_developer(group_member) group.add_developer(group_member_user)
end end
it 'updates existing link' do it 'updates existing link' do
...@@ -36,11 +36,11 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do ...@@ -36,11 +36,11 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
end end
it 'updates project permissions' do it 'updates project permissions' do
expect { subject }.to change { group_member.can?(:create_release, project) }.from(true).to(false) expect { subject }.to change { group_member_user.can?(:create_release, project) }.from(true).to(false)
end end
it 'executes UserProjectAccessChangedService' do it 'executes UserProjectAccessChangedService' do
expect_next_instance_of(UserProjectAccessChangedService) do |service| expect_next_instance_of(UserProjectAccessChangedService, [group_member_user.id]) do |service|
expect(service).to receive(:execute) expect(service).to receive(:execute)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment