Commit 7080bd2e authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'if-217637-project_share_to_avoid_after_commit_hook' into 'master'

Project share to avoid after_commit hook

See merge request gitlab-org/gitlab!33144
parents 631cfa7e b2027429
...@@ -22,8 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -22,8 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
def update def update
@group_link = @project.project_group_links.find(params[:id]) @group_link = @project.project_group_links.find(params[:id])
Projects::GroupLinks::UpdateService.new(@group_link).execute(group_link_params)
@group_link.update(group_link_params)
end end
def destroy def destroy
......
...@@ -15,8 +15,6 @@ class ProjectGroupLink < ApplicationRecord ...@@ -15,8 +15,6 @@ class ProjectGroupLink < ApplicationRecord
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) } scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
after_commit :refresh_group_members_authorized_projects
alias_method :shared_with_group, :group alias_method :shared_with_group, :group
def self.access_options def self.access_options
...@@ -49,10 +47,6 @@ class ProjectGroupLink < ApplicationRecord ...@@ -49,10 +47,6 @@ class ProjectGroupLink < ApplicationRecord
errors.add(:base, _("Project cannot be shared with the group it is in or one of its ancestors.")) errors.add(:base, _("Project cannot be shared with the group it is in or one of its ancestors."))
end end
end end
def refresh_group_members_authorized_projects
group.refresh_members_authorized_projects
end
end end
ProjectGroupLink.prepend_if_ee('EE::ProjectGroupLink') ProjectGroupLink.prepend_if_ee('EE::ProjectGroupLink')
...@@ -13,6 +13,7 @@ module Projects ...@@ -13,6 +13,7 @@ module Projects
) )
if link.save if link.save
group.refresh_members_authorized_projects
success(link: link) success(link: link)
else else
error(link.errors.full_messages.to_sentence, 409) error(link.errors.full_messages.to_sentence, 409)
......
...@@ -12,7 +12,9 @@ module Projects ...@@ -12,7 +12,9 @@ module Projects
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id) TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
end end
group_link.destroy group_link.destroy.tap do |link|
link.group.refresh_members_authorized_projects
end
end end
end end
end end
......
# frozen_string_literal: true
module Projects
module GroupLinks
class UpdateService < BaseService
def initialize(group_link, user = nil)
super(group_link.project, user)
@group_link = group_link
end
def execute(group_link_params)
group_link.update!(group_link_params)
if requires_authorization_refresh?(group_link_params)
group_link.group.refresh_members_authorized_projects
end
end
private
attr_reader :group_link
def requires_authorization_refresh?(params)
params.include?(:group_access)
end
end
end
end
...@@ -7,7 +7,9 @@ class RemoveExpiredGroupLinksWorker # rubocop:disable Scalability/IdempotentWork ...@@ -7,7 +7,9 @@ class RemoveExpiredGroupLinksWorker # rubocop:disable Scalability/IdempotentWork
feature_category :authentication_and_authorization feature_category :authentication_and_authorization
def perform def perform
ProjectGroupLink.expired.destroy_all # rubocop: disable Cop/DestroyAll ProjectGroupLink.expired.find_each do |link|
Projects::GroupLinks::DestroyService.new(link.project, nil).execute(link)
end
GroupGroupLink.expired.find_in_batches do |link_batch| GroupGroupLink.expired.find_in_batches do |link_batch|
Groups::GroupLinks::DestroyService.new(nil, nil).execute(link_batch) Groups::GroupLinks::DestroyService.new(nil, nil).execute(link_batch)
......
...@@ -477,9 +477,8 @@ describe Gitlab::GitAccess do ...@@ -477,9 +477,8 @@ describe Gitlab::GitAccess do
def self.run_group_permission_checks(permissions_matrix) def self.run_group_permission_checks(permissions_matrix)
permissions_matrix.each_pair do |role, matrix| permissions_matrix.each_pair do |role, matrix|
it "has the correct permissions for group #{role}s" do it "has the correct permissions for group #{role}s" do
project create(:project_group_link, role, group: group,
.project_group_links project: project)
.create(group: group, group_access: Gitlab::Access.sym_options[role])
protected_branch.save protected_branch.save
......
...@@ -502,7 +502,9 @@ module API ...@@ -502,7 +502,9 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id]) link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link not_found!('Group Link') unless link
destroy_conditionally!(link) destroy_conditionally!(link) do
::Projects::GroupLinks::DestroyService.new(user_project, current_user).execute(link)
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -5,10 +5,15 @@ FactoryBot.define do ...@@ -5,10 +5,15 @@ FactoryBot.define do
project project
group group
expires_at { nil } expires_at { nil }
group_access { Gitlab::Access::DEVELOPER }
trait(:guest) { group_access { Gitlab::Access::GUEST } } trait(:guest) { group_access { Gitlab::Access::GUEST } }
trait(:reporter) { group_access { Gitlab::Access::REPORTER } } trait(:reporter) { group_access { Gitlab::Access::REPORTER } }
trait(:developer) { group_access { Gitlab::Access::DEVELOPER } } trait(:developer) { group_access { Gitlab::Access::DEVELOPER } }
trait(:maintainer) { group_access { Gitlab::Access::MAINTAINER } } trait(:maintainer) { group_access { Gitlab::Access::MAINTAINER } }
after(:create) do |project_group_link, evaluator|
project_group_link.group.refresh_members_authorized_projects
end
end end
end end
...@@ -49,22 +49,6 @@ describe ProjectGroupLink do ...@@ -49,22 +49,6 @@ describe ProjectGroupLink do
end end
end end
describe "destroying a record", :delete do
it "refreshes group users' authorized projects" do
project = create(:project, :private)
group = create(:group)
reporter = create(:user)
group_users = group.users
group.add_reporter(reporter)
project.project_group_links.create(group: group)
group_users.each { |user| expect(user.authorized_projects).to include(project) }
project.project_group_links.destroy_all # rubocop: disable Cop/DestroyAll
group_users.each { |user| expect(user.authorized_projects).not_to include(project) }
end
end
describe 'search by group name' do describe 'search by group name' do
let_it_be(:project_group_link) { create(:project_group_link) } let_it_be(:project_group_link) { create(:project_group_link) }
let_it_be(:group) { project_group_link.group } let_it_be(:group) { project_group_link.group }
......
...@@ -96,11 +96,9 @@ describe ProjectTeam do ...@@ -96,11 +96,9 @@ describe ProjectTeam do
it 'returns invited members of a group' do it 'returns invited members of a group' do
group_member = create(:group_member) group_member = create(:group_member)
create(:project_group_link, group: group_member.group,
project.project_group_links.create!( project: project,
group: group_member.group, group_access: Gitlab::Access::GUEST)
group_access: Gitlab::Access::GUEST
)
expect(project.team.members) expect(project.team.members)
.to contain_exactly(group_member.user, project.owner) .to contain_exactly(group_member.user, project.owner)
...@@ -108,11 +106,9 @@ describe ProjectTeam do ...@@ -108,11 +106,9 @@ describe ProjectTeam do
it 'returns invited members of a group of a specified level' do it 'returns invited members of a group of a specified level' do
group_member = create(:group_member) group_member = create(:group_member)
create(:project_group_link, group: group_member.group,
project.project_group_links.create!( project: project,
group: group_member.group, group_access: Gitlab::Access::REPORTER)
group_access: Gitlab::Access::REPORTER
)
expect(project.team.guests).to be_empty expect(project.team.guests).to be_empty
expect(project.team.reporters).to contain_exactly(group_member.user) expect(project.team.reporters).to contain_exactly(group_member.user)
......
...@@ -2845,10 +2845,10 @@ describe User do ...@@ -2845,10 +2845,10 @@ describe User do
it "includes projects shared with user's group" do it "includes projects shared with user's group" do
user = create(:user) user = create(:user)
project = create(:project, :private) project = create(:project, :private)
group = create(:group) group = create(:group) do |group|
group.add_reporter(user)
group.add_reporter(user) end
project.project_group_links.create(group: group) create(:project_group_link, group: group, project: project)
expect(user.authorized_projects).to include(project) expect(user.authorized_projects).to include(project)
end end
......
...@@ -2055,10 +2055,12 @@ describe API::Projects do ...@@ -2055,10 +2055,12 @@ describe API::Projects do
end end
describe "POST /projects/:id/share" do describe "POST /projects/:id/share" do
let(:group) { create(:group) } let_it_be(:group) { create(:group, :private) }
let_it_be(:group_user) { create(:user) }
before do before do
group.add_developer(user) group.add_developer(user)
group.add_developer(group_user)
end end
it "shares project with group" do it "shares project with group" do
...@@ -2074,6 +2076,14 @@ describe API::Projects do ...@@ -2074,6 +2076,14 @@ describe API::Projects do
expect(json_response['expires_at']).to eq(expires_at.to_s) expect(json_response['expires_at']).to eq(expires_at.to_s)
end end
it 'updates project authorization' do
expect do
post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
end.to(
change { group_user.can?(:read_project, project) }.from(false).to(true)
)
end
it "returns a 400 error when group id is not given" do it "returns a 400 error when group id is not given" do
post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER } post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
...@@ -2123,9 +2133,12 @@ describe API::Projects do ...@@ -2123,9 +2133,12 @@ describe API::Projects do
describe 'DELETE /projects/:id/share/:group_id' do describe 'DELETE /projects/:id/share/:group_id' do
context 'for a valid group' do context 'for a valid group' do
let(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :private) }
let_it_be(:group_user) { create(:user) }
before do before do
group.add_developer(group_user)
create(:project_group_link, group: group, project: project) create(:project_group_link, group: group, project: project)
end end
...@@ -2136,6 +2149,14 @@ describe API::Projects do ...@@ -2136,6 +2149,14 @@ describe API::Projects do
expect(project.project_group_links).to be_empty expect(project.project_group_links).to be_empty
end end
it 'updates project authorization' do
expect do
delete api("/projects/#{project.id}/share/#{group.id}", user)
end.to(
change { group_user.can?(:read_project, project) }.from(true).to(false)
)
end
it_behaves_like '412 response' do it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) } let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
end end
......
...@@ -3,16 +3,17 @@ ...@@ -3,16 +3,17 @@
require 'spec_helper' require 'spec_helper'
describe Projects::GroupLinks::CreateService, '#execute' do describe Projects::GroupLinks::CreateService, '#execute' do
let(:user) { create :user } let_it_be(:user) { create :user }
let(:group) { create :group } let_it_be(:group) { create :group }
let(:project) { create :project } let_it_be(:project) { create :project }
let(:opts) do let(:opts) do
{ {
link_group_access: '30', link_group_access: '30',
expires_at: nil expires_at: nil
} }
end end
let(:subject) { described_class.new(project, user, opts) }
subject { described_class.new(project, user, opts) }
before do before do
group.add_developer(user) group.add_developer(user)
...@@ -22,6 +23,12 @@ describe Projects::GroupLinks::CreateService, '#execute' do ...@@ -22,6 +23,12 @@ describe Projects::GroupLinks::CreateService, '#execute' do
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1) expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
end end
it 'updates authorization' do
expect { subject.execute(group) }.to(
change { Ability.allowed?(user, :read_project, project) }
.from(false).to(true))
end
it 'returns false if group is blank' do it 'returns false if group is blank' do
expect { subject.execute(nil) }.not_to change { project.project_group_links.count } expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
end end
......
...@@ -3,15 +3,25 @@ ...@@ -3,15 +3,25 @@
require 'spec_helper' require 'spec_helper'
describe Projects::GroupLinks::DestroyService, '#execute' do describe Projects::GroupLinks::DestroyService, '#execute' do
let(:project) { create(:project, :private) } let_it_be(:user) { create :user }
let!(:group_link) { create(:project_group_link, project: project) } let_it_be(:project) { create(:project, :private) }
let(:user) { create :user } let_it_be(:group) { create(:group) }
let(:subject) { described_class.new(project, user) } let!(:group_link) { create(:project_group_link, project: project, group: group) }
subject { described_class.new(project, user) }
it 'removes group from project' do it 'removes group from project' do
expect { subject.execute(group_link) }.to change { project.project_group_links.count }.from(1).to(0) expect { subject.execute(group_link) }.to change { project.project_group_links.count }.from(1).to(0)
end end
it 'updates authorization' do
group.add_maintainer(user)
expect { subject.execute(group_link) }.to(
change { Ability.allowed?(user, :read_project, project) }
.from(true).to(false))
end
it 'returns false if group_link is blank' do it 'returns false if group_link is blank' do
expect { subject.execute(nil) }.not_to change { project.project_group_links.count } expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::GroupLinks::UpdateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
let_it_be(:project) { create :project }
let!(:link) { create(:project_group_link, project: project, group: group) }
let(:expiry_date) { 1.month.from_now.to_date }
let(:group_link_params) do
{ group_access: Gitlab::Access::GUEST,
expires_at: expiry_date }
end
subject { described_class.new(link).execute(group_link_params) }
before do
group.add_developer(user)
end
it 'updates existing link' do
expect(link.group_access).to eq(Gitlab::Access::DEVELOPER)
expect(link.expires_at).to be_nil
subject
link.reload
expect(link.group_access).to eq(Gitlab::Access::GUEST)
expect(link.expires_at).to eq(expiry_date)
end
it 'updates project permissions' do
expect { subject }.to change { user.can?(:create_release, project) }.from(true).to(false)
end
it 'executes UserProjectAccessChangedService' do
expect_next_instance_of(UserProjectAccessChangedService) do |service|
expect(service).to receive(:execute)
end
subject
end
context 'with only param not requiring authorization refresh' do
let(:group_link_params) { { expires_at: Date.tomorrow } }
it 'does not execute UserProjectAccessChangedService' do
expect(UserProjectAccessChangedService).not_to receive(:new)
subject
end
end
end
...@@ -23,30 +23,53 @@ describe RemoveExpiredGroupLinksWorker do ...@@ -23,30 +23,53 @@ describe RemoveExpiredGroupLinksWorker do
subject.perform subject.perform
expect(non_expiring_project_group_link.reload).to be_present expect(non_expiring_project_group_link.reload).to be_present
end end
end
context 'GroupGroupLinks' do it 'removes project authorization' do
let(:mock_destroy_service) { instance_double(Groups::GroupLinks::DestroyService) } user = create(:user)
project = expired_project_group_link.project
group = expired_project_group_link.group
group.add_maintainer(user)
before do expect { subject.perform }.to(
allow(Groups::GroupLinks::DestroyService).to( change { user.can?(:read_project, project) }.from(true).to(false))
receive(:new).and_return(mock_destroy_service))
end end
end
context 'GroupGroupLinks' do
context 'expired GroupGroupLink exists' do context 'expired GroupGroupLink exists' do
before do let!(:group_group_link) { create(:group_group_link, expires_at: 1.hour.ago) }
create(:group_group_link, expires_at: 1.hour.ago)
end
it 'calls Groups::GroupLinks::DestroyService' do it 'calls Groups::GroupLinks::DestroyService' do
mock_destroy_service = instance_double(Groups::GroupLinks::DestroyService)
allow(Groups::GroupLinks::DestroyService).to(
receive(:new).and_return(mock_destroy_service))
expect(mock_destroy_service).to receive(:execute).once expect(mock_destroy_service).to receive(:execute).once
subject.perform subject.perform
end end
it 'removes project authorization' do
shared_group = group_group_link.shared_group
shared_with_group = group_group_link.shared_with_group
project = create(:project, group: shared_group)
user = create(:user)
shared_with_group.add_maintainer(user)
expect { subject.perform }.to(
change { user.can?(:read_project, project) }.from(true).to(false))
end
end end
context 'expired GroupGroupLink does not exist' do context 'expired GroupGroupLink does not exist' do
it 'does not call Groups::GroupLinks::DestroyService' do it 'does not call Groups::GroupLinks::DestroyService' do
mock_destroy_service = instance_double(Groups::GroupLinks::DestroyService)
allow(Groups::GroupLinks::DestroyService).to(
receive(:new).and_return(mock_destroy_service))
expect(mock_destroy_service).not_to receive(:execute) expect(mock_destroy_service).not_to receive(:execute)
subject.perform subject.perform
......
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