Commit 9d2f2da0 authored by Tiago Botelho's avatar Tiago Botelho

Only show approver groups that are visible to the current user through the API

parent 6c43aa6b
...@@ -105,6 +105,9 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -105,6 +105,9 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
@commits = set_commits_for_rendering(@merge_request.commits) @commits = set_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit @commit = @merge_request.diff_head_commit
# FIXME: We have to assign a presenter to another instance variable
# due to class_name checks being made with issuable classes
@mr_presenter = @merge_request.present(current_user: current_user) @mr_presenter = @merge_request.present(current_user: current_user)
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
......
...@@ -341,6 +341,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -341,6 +341,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@target_project = @merge_request.target_project @target_project = @merge_request.target_project
@target_branches = @merge_request.target_project.repository.branch_names @target_branches = @merge_request.target_project.repository.branch_names
@noteable = @merge_request @noteable = @merge_request
# FIXME: We have to assign a presenter to another instance variable
# due to class_name checks being made with issuable classes
@mr_presenter = @merge_request.present(current_user: current_user) @mr_presenter = @merge_request.present(current_user: current_user)
end end
......
...@@ -3,13 +3,14 @@ ...@@ -3,13 +3,14 @@
module Emails module Emails
module MergeRequests module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id, reason = nil) def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id, present: true)
mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason)) mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason))
end end
def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id, present: true)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end end
...@@ -67,7 +68,7 @@ module Emails ...@@ -67,7 +68,7 @@ module Emails
end end
def add_merge_request_approver_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) def add_merge_request_approver_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id, present: true)
@updated_by = User.find(updated_by_user_id) @updated_by = User.find(updated_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
...@@ -96,11 +97,16 @@ module Emails ...@@ -96,11 +97,16 @@ module Emails
private private
def setup_merge_request_mail(merge_request_id, recipient_id) def setup_merge_request_mail(merge_request_id, recipient_id, present: false)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project @project = @merge_request.project
@target_url = project_merge_request_url(@project, @merge_request) @target_url = project_merge_request_url(@project, @merge_request)
if present
recipient = User.find(recipient_id)
@mr_presenter = @merge_request.present(current_user: recipient)
end
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key) @sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end end
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%p %p
Assignee: #{@merge_request.assignee_name} Assignee: #{@merge_request.assignee_name}
= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request = render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
- if @merge_request.description - if @merge_request.description
%div %div
......
...@@ -5,6 +5,6 @@ New Merge Request <%= @merge_request.to_reference %> ...@@ -5,6 +5,6 @@ New Merge Request <%= @merge_request.to_reference %>
<%= merge_path_description(@merge_request, 'to') %> <%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %> Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %> Assignee: <%= @merge_request.assignee_name %>
<%= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request %> <%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
<%= @merge_request.description %> <%= @merge_request.description %>
module Approvable module Approvable
include Gitlab::Utils::StrongMemoize # A method related to approvers that is user facing
# should be moved to VisibleApprovable because
def requires_approve? # of the fact that we use filtered versions of certain methods
# keep until UI changes implemented # such as approver_groups and target_project in presenters
true include ::VisibleApprovable
end
def approval_needed? def approval_needed?
approvals_required&.nonzero? approvals_required&.nonzero?
...@@ -36,66 +35,17 @@ module Approvable ...@@ -36,66 +35,17 @@ module Approvable
super super
end end
# Users in the list of approvers who have not already approved this MR. # Even though this method is used in VisibleApprovable
# # we do not want to encounter a scenario where there are
def approvers_left # no user approvers but there is one merge request group
strong_memoize(:approvers_left) do # approver that is not visible to the current_user,
User.where(id: all_approvers_including_groups.map(&:id)).where.not(id: approved_by_users.select(:id)) # which would make this method return false
end # when it should still report as overwritten.
end # To prevent this from happening, approvers_overwritten?
# makes use of the unfiltered version of approver_groups so that
# The list of approvers from either this MR (if they've been set on the MR) or the # it always takes into account every approver
# target project. Excludes the author if 'self-approval' isn't explicitly # available
# enabled on project settings.
# #
# Before a merge request has been created, author will be nil, so pass the current user
# on the MR create page.
#
def overall_approvers
approvers_relation = approvers_overwritten? ? approvers : target_project.approvers
if author && !authors_can_approve?
approvers_relation = approvers_relation.where.not(user_id: author.id)
end
approvers_relation.includes(:user)
end
def overall_approver_groups
approvers_overwritten? ? approver_groups : target_project.approver_groups
end
def all_approvers_including_groups
strong_memoize(:all_approvers_including_groups) do
approvers = []
# Approvers from direct assignment
approvers << approvers_from_users
approvers << approvers_from_groups
approvers.flatten
end
end
def approvers_from_users
overall_approvers.map(&:user)
end
def approvers_from_groups
group_approvers = []
overall_approver_groups.each do |approver_group|
group_approvers << approver_group.users
end
group_approvers.flatten!
group_approvers.delete(author) unless authors_can_approve?
group_approvers
end
def approvers_overwritten? def approvers_overwritten?
approvers.to_a.any? || approver_groups.to_a.any? approvers.to_a.any? || approver_groups.to_a.any?
end end
...@@ -145,12 +95,4 @@ module Approvable ...@@ -145,12 +95,4 @@ module Approvable
approver_groups.find_or_initialize_by(group_id: group_id, target_id: id) approver_groups.find_or_initialize_by(group_id: group_id, target_id: id)
end end
end end
def reset_approval_cache!
approvals(true)
approved_by_users(true)
clear_memoization(:approvers_left)
clear_memoization(:all_approvers_including_groups)
end
end end
# This module may only be used by presenters or modules
# that include the Approvable concern
module VisibleApprovable
include ::Gitlab::Utils::StrongMemoize
def requires_approve?
# keep until UI changes implemented
true
end
# Users in the list of approvers who have not already approved this MR.
#
def approvers_left
strong_memoize(:approvers_left) do
User.where(id: all_approvers_including_groups.map(&:id)).where.not(id: approved_by_users.select(:id))
end
end
# The list of approvers from either this MR (if they've been set on the MR) or the
# target project. Excludes the author if 'self-approval' isn't explicitly
# enabled on project settings.
#
# Before a merge request has been created, author will be nil, so pass the current user
# on the MR create page.
#
def overall_approvers
approvers_relation = approvers_overwritten? ? approvers : target_project.approvers
if author && !authors_can_approve?
approvers_relation = approvers_relation.where.not(user_id: author.id)
end
approvers_relation.includes(:user)
end
def overall_approver_groups
approvers_overwritten? ? approver_groups : target_project.approver_groups
end
def all_approvers_including_groups
strong_memoize(:all_approvers_including_groups) do
approvers = []
# Approvers from direct assignment
approvers << approvers_from_users
approvers << approvers_from_groups
approvers.flatten
end
end
def approvers_from_users
overall_approvers.map(&:user)
end
def approvers_from_groups
group_approvers = overall_approver_groups.flat_map(&:users)
group_approvers.delete(author) unless authors_can_approve?
group_approvers
end
def reset_approval_cache!
approvals(true)
approved_by_users(true)
clear_memoization(:approvers_left)
clear_memoization(:all_approvers_including_groups)
end
end
module EE module EE
module MergeRequestPresenter module MergeRequestPresenter
include ::Gitlab::Utils::StrongMemoize include ::VisibleApprovable
def approvals_path def approvals_path
if requires_approve? if requires_approve?
...@@ -8,34 +8,8 @@ module EE ...@@ -8,34 +8,8 @@ module EE
end end
end end
def all_approvers_including_groups def target_project
strong_memoize(:all_approvers_including_groups) do merge_request.target_project.present(current_user: current_user)
approvers = []
# approvers from direct assignment
approvers << merge_request.approvers_from_users
approvers << approvers_from_groups
approvers.flatten
end
end
def overall_approver_groups
if approvers_overwritten?
approver_groups
else
merge_request.target_project.present(current_user: current_user).approver_groups
end
end
def approvers_overwritten?
merge_request.approvers.to_a.any? || approver_groups.to_a.any?
end
private
def approvers_from_groups
overall_approver_groups.flat_map(&:users) - [merge_request.author]
end end
def approver_groups def approver_groups
......
- merge_request = local_assigns.fetch(:merge_request) - presenter = local_assigns.fetch(:presenter)
- return unless merge_request.approvers.any? - return unless presenter.approvers.any?
%p %p
Approvers: #{render_items_list(merge_request.approvers_left.map(&:name))} Approvers: #{render_items_list(presenter.approvers_left.map(&:name))}
<%- merge_request = local_assigns.fetch(:merge_request) -%> <%- presenter = local_assigns.fetch(:presenter) -%>
<%- return unless merge_request.approvers.any? -%> <%- return unless presenter.approvers.any? -%>
Approvers: <%= render_items_list(merge_request.approvers_left.map(&:name)) %> Approvers: <%= render_items_list(presenter.approvers_left.map(&:name)) %>
...@@ -8,9 +8,7 @@ ...@@ -8,9 +8,7 @@
%p %p
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
- if @merge_request.approvers.any? = render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
%p
Approvers: #{render_items_list(@merge_request.approvers_left.map(&:name))}
- if @merge_request.description - if @merge_request.description
= markdown(@merge_request.description, pipeline: :email, author: @merge_request.author) = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author)
...@@ -5,6 +5,4 @@ ...@@ -5,6 +5,4 @@
<%= merge_path_description(@merge_request, 'to') %> <%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %> Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %> Assignee: <%= @merge_request.assignee_name %>
<% if @merge_request.approvers.any? %> <%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
Approvers: <%= render_items_list(@merge_request.approvers_left.map(&:name)) %>
<% end %>
...@@ -3,10 +3,9 @@ ...@@ -3,10 +3,9 @@
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
- return unless issuable.is_a?(MergeRequest) - return unless issuable.is_a?(MergeRequest)
- return unless issuable.requires_approve? - return unless presenter.requires_approve?
- author = issuable.author || current_user - author = issuable.author || current_user
- authors_can_approve = issuable.merge_requests_author_approval?
- can_update_approvers = can?(current_user, :update_approvers, issuable) - can_update_approvers = can?(current_user, :update_approvers, issuable)
.form-group.row .form-group.row
...@@ -14,7 +13,7 @@ ...@@ -14,7 +13,7 @@
Approvers Approvers
.col-sm-10 .col-sm-10
- if can_update_approvers - if can_update_approvers
- skip_users = [*presenter.all_approvers_including_groups, (author unless authors_can_approve)].compact - skip_users = [*presenter.all_approvers_including_groups, (author unless presenter.authors_can_approve?)].compact
= users_select_tag("merge_request[approver_ids]", = users_select_tag("merge_request[approver_ids]",
multiple: true, multiple: true,
...@@ -22,6 +21,7 @@ ...@@ -22,6 +21,7 @@
email_user: true, email_user: true,
skip_users: skip_users, skip_users: skip_users,
project: issuable.target_project) project: issuable.target_project)
.form-text.text-muted .form-text.text-muted
This merge request must be approved by these users. This merge request must be approved by these users.
You can override the project settings by setting your own list of approvers. You can override the project settings by setting your own list of approvers.
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- else - else
- unsaved_approvers = !presenter.approvers_overwritten? - unsaved_approvers = !presenter.approvers_overwritten?
- item_classes = unsaved_approvers ? ['unsaved-approvers'] : [] - item_classes = unsaved_approvers ? ['unsaved-approvers'] : []
- issuable.overall_approvers.each do |approver| - presenter.overall_approvers.each do |approver|
%li{ id: dom_id(approver.user), class: item_classes + ['approver'] } %li{ id: dom_id(approver.user), class: item_classes + ['approver'] }
= link_to approver.user.name, approver.user = link_to approver.user.name, approver.user
- if can_update_approvers - if can_update_approvers
......
...@@ -42,7 +42,7 @@ module API ...@@ -42,7 +42,7 @@ module API
get 'approvals' do get 'approvals' do
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request.present(current_user: current_user), with: Entities::MergeRequestApprovals, current_user: current_user
end end
desc 'Change approval-related configuration' do desc 'Change approval-related configuration' do
...@@ -60,7 +60,7 @@ module API ...@@ -60,7 +60,7 @@ module API
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, approvals_before_merge: params[:approvals_required]).execute(merge_request) merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, approvals_before_merge: params[:approvals_required]).execute(merge_request)
if merge_request.valid? if merge_request.valid?
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request.present(current_user: current_user), with: Entities::MergeRequestApprovals, current_user: current_user
else else
handle_merge_request_errors! merge_request.errors handle_merge_request_errors! merge_request.errors
end end
...@@ -80,7 +80,7 @@ module API ...@@ -80,7 +80,7 @@ module API
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, declared(params, include_parent_namespaces: false).merge(remove_old_approvers: true)).execute(merge_request) merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, declared(params, include_parent_namespaces: false).merge(remove_old_approvers: true)).execute(merge_request)
if merge_request.valid? if merge_request.valid?
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request.present(current_user: current_user), with: Entities::MergeRequestApprovals, current_user: current_user
else else
handle_merge_request_errors! merge_request.errors handle_merge_request_errors! merge_request.errors
end end
...@@ -111,7 +111,7 @@ module API ...@@ -111,7 +111,7 @@ module API
.new(user_project, current_user) .new(user_project, current_user)
.execute(merge_request) .execute(merge_request)
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request.present(current_user: current_user), with: Entities::MergeRequestApprovals, current_user: current_user
end end
desc 'Remove an approval from a merge request' do desc 'Remove an approval from a merge request' do
...@@ -126,7 +126,7 @@ module API ...@@ -126,7 +126,7 @@ module API
.new(user_project, current_user) .new(user_project, current_user)
.execute(merge_request) .execute(merge_request)
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request.present(current_user: current_user), with: Entities::MergeRequestApprovals, current_user: current_user
end end
end end
end end
......
...@@ -15,7 +15,7 @@ module API ...@@ -15,7 +15,7 @@ module API
success ::API::Entities::ApprovalSettings success ::API::Entities::ApprovalSettings
end end
get '/' do get '/' do
present user_project, with: ::API::Entities::ApprovalSettings present user_project.present(current_user: current_user), with: ::API::Entities::ApprovalSettings
end end
desc 'Change approval-related configuration' do desc 'Change approval-related configuration' do
...@@ -34,7 +34,7 @@ module API ...@@ -34,7 +34,7 @@ module API
result = ::Projects::UpdateService.new(user_project, current_user, project_params).execute result = ::Projects::UpdateService.new(user_project, current_user, project_params).execute
if result[:status] == :success if result[:status] == :success
present user_project, with: ::API::Entities::ApprovalSettings present user_project.present(current_user: current_user), with: ::API::Entities::ApprovalSettings
else else
render_validation_error!(user_project) render_validation_error!(user_project)
end end
...@@ -53,7 +53,7 @@ module API ...@@ -53,7 +53,7 @@ module API
result = ::Projects::UpdateService.new(user_project, current_user, declared(params, include_parent_namespaces: false).merge(remove_old_approvers: true)).execute result = ::Projects::UpdateService.new(user_project, current_user, declared(params, include_parent_namespaces: false).merge(remove_old_approvers: true)).execute
if result[:status] == :success if result[:status] == :success
present user_project, with: ::API::Entities::ApprovalSettings present user_project.present(current_user: current_user), with: ::API::Entities::ApprovalSettings
else else
render_validation_error!(user_project) render_validation_error!(user_project)
end end
......
require 'spec_helper' require 'spec_helper'
describe Approvable do describe Approvable do
let(:merge_request) { create(:merge_request, :with_approver) } let(:merge_request) { create(:merge_request) }
describe '#approvers_left' do describe '#approvers_overwritten?' do
it 'only queries once' do subject { merge_request.approvers_overwritten? }
merge_request
expect(User).to receive(:where).and_call_original.once it 'returns false when merge request has no approvers' do
is_expected.to be false
3.times { merge_request.approvers_left }
end end
end
describe '#reset_approval_cache!' do it 'returns true when merge request has user approver' do
it 'clears the cache of approvers left' do create(:approver, target: merge_request)
user_can_approve = merge_request.approvers_left.first
merge_request.approvals.create!(user: user_can_approve) is_expected.to be true
end
merge_request.reset_approval_cache! it 'returns true when merge request has group approver' do
group = create(:group_with_members)
create(:approver_group, target: merge_request, group: group)
expect(merge_request.approvers_left).to be_empty is_expected.to be true
end end
end end
end end
require 'spec_helper'
describe VisibleApprovable do
let(:resource) { create(:merge_request, source_project: project) }
let!(:project) { create(:project, :repository) }
let!(:user) { project.creator }
describe '#requires_approve' do
subject { resource.requires_approve? }
it { is_expected.to be true }
end
describe '#approvers_left' do
let!(:private_group) { create(:group_with_members, :private) }
let!(:public_group) { create(:group_with_members) }
let!(:public_approver_group) { create(:approver_group, target: resource, group: public_group) }
let!(:private_approver_group) { create(:approver_group, target: resource, group: private_group) }
let!(:approver) { create(:approver, target: resource) }
subject { resource.approvers_left }
it 'only queries once' do
expect(User).to receive(:where).and_call_original.once
3.times { subject }
end
it 'returns all approvers left' do
resource.approvals.create!(user: approver.user)
is_expected.to match_array(public_approver_group.users + private_approver_group.users)
end
end
describe '#overall_approvers' do
let!(:project_approver) { create(:approver, target: project) }
subject { resource.overall_approvers }
it 'returns a list of all the project approvers' do
is_expected.to match_array(project_approver)
end
context 'when author is approver' do
let!(:author_approver) { create(:approver, target: project, user: resource.author) }
it 'excludes author if authors cannot approve' do
is_expected.not_to include(author_approver)
end
it 'includes author if authors are able to approve' do
project.update(merge_requests_author_approval: true)
is_expected.to include(author_approver)
end
end
context 'when approvers are overwritten' do
let!(:approver) { create(:approver, target: resource) }
it 'returns the list of all the merge request user approvers' do
is_expected.to match_array(approver)
end
end
end
describe '#overall_approver_groups' do
before do
group = create(:group_with_members)
create(:approver_group, target: project, group: group)
end
subject { resource.overall_approver_groups }
it 'returns all the project approver groups' do
is_expected.to match_array(project.approver_groups)
end
context 'when group approvers are overwritten' do
it 'returns all the merge request approver groups' do
group = create(:group_with_members)
create(:approver_group, target: resource, group: group)
is_expected.to match_array(resource.approver_groups)
end
end
end
describe '#all_approvers_including_groups' do
let!(:group) { create(:group_with_members) }
let!(:approver_group) { create(:approver_group, target: resource, group: group) }
let!(:approver) { create(:approver, target: resource) }
subject { resource.all_approvers_including_groups }
it 'only queries once' do
expect(resource).to receive(:approvers_from_users).and_call_original.once
3.times { subject }
end
it 'returns all approvers (groups and users)' do
is_expected.to match_array(approver_group.users + [approver.user])
end
end
describe '#authors_can_approve?' do
subject { resource.authors_can_approve? }
it 'returns false when merge_requests_author_approval flag is off' do
is_expected.to be false
end
it 'returns true when merge_requests_author_approval flag is turned on' do
project.update(merge_requests_author_approval: true)
is_expected.to be true
end
end
describe '#reset_approval_cache!' do
before do
create(:approver, target: resource)
end
subject { resource.reset_approval_cache! }
it 'clears the cache of approvers left' do
user_can_approve = resource.approvers_left.first
resource.approvals.create!(user: user_can_approve)
subject
expect(resource.approvers_left).to be_empty
end
it 'clears the all_approvers_including_groups cache' do
resource.all_approvers_including_groups.first.destroy!
subject
expect(resource.all_approvers_including_groups).to be_empty
end
end
end
...@@ -5,16 +5,28 @@ describe MergeRequestPresenter do ...@@ -5,16 +5,28 @@ describe MergeRequestPresenter do
let!(:project) { create(:project, :repository) } let!(:project) { create(:project, :repository) }
let!(:user) { project.creator } let!(:user) { project.creator }
describe '#all_approvers_including_groups' do describe '#approvals_path' do
subject { described_class.new(resource, current_user: user).approvals_path }
it 'returns path' do
is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/approvals")
end
end
describe '#approvers_left' do
let!(:private_group) { create(:group_with_members, :private) } let!(:private_group) { create(:group_with_members, :private) }
let!(:public_group) { create(:group_with_members) } let!(:public_group) { create(:group_with_members) }
let!(:public_approver_group) { create(:approver_group, target: resource, group: public_group) } let!(:public_approver_group) { create(:approver_group, target: resource, group: public_group) }
let!(:private_approver_group) { create(:approver_group, target: resource, group: private_group) } let!(:private_approver_group) { create(:approver_group, target: resource, group: private_group) }
let!(:approver) { create(:approver, target: resource) } let!(:approver) { create(:approver, target: resource) }
subject { described_class.new(resource, current_user: user).all_approvers_including_groups } before do
resource.approvals.create!(user: approver.user)
end
subject { described_class.new(resource, current_user: user).approvers_left }
it { is_expected.to match_array([public_approver_group.users, approver.user].flatten) } it { is_expected.to match_array(public_approver_group.users) }
context 'when user has access to private group' do context 'when user has access to private group' do
before do before do
...@@ -22,7 +34,7 @@ describe MergeRequestPresenter do ...@@ -22,7 +34,7 @@ describe MergeRequestPresenter do
end end
it do it do
approvers = [public_approver_group.users, private_approver_group.users, approver.user].flatten - [user] approvers = public_approver_group.users + private_approver_group.users - [user]
is_expected.to match_array(approvers) is_expected.to match_array(approvers)
end end
...@@ -30,43 +42,45 @@ describe MergeRequestPresenter do ...@@ -30,43 +42,45 @@ describe MergeRequestPresenter do
end end
describe '#overall_approver_groups' do describe '#overall_approver_groups' do
subject { described_class.new(resource, current_user: user).overall_approver_groups } let!(:private_group) { create(:group_with_members, :private) }
let!(:public_group) { create(:group_with_members) }
let!(:public_approver_group) { create(:approver_group, target: resource, group: public_group) }
let!(:private_approver_group) { create(:approver_group, target: resource, group: private_group) }
context 'when approvers is overwritten' do subject { described_class.new(resource, current_user: user).overall_approver_groups }
let!(:public_approver_group) { create(:approver_group, target: project) }
it { is_expected.to match_array(project.approver_groups) } it { is_expected.to match_array([public_approver_group]) }
end
context 'when approvers is not overwritten' do context 'when user has access to private group' do
let!(:public_approver_group) { create(:approver_group, target: resource) } before do
private_group.add_user(user, Gitlab::Access::DEVELOPER)
end
it { is_expected.to match_array([public_approver_group]) } it { is_expected.to match_array([public_approver_group, private_approver_group]) }
end end
end end
describe '#approvers_overwritten?' do describe '#all_approvers_including_groups' do
subject { described_class.new(resource, current_user: user).approvers_overwritten? } let!(:private_group) { create(:group_with_members, :private) }
let!(:public_group) { create(:group_with_members) }
it { is_expected.to be_falsey } let!(:public_approver_group) { create(:approver_group, target: resource, group: public_group) }
let!(:private_approver_group) { create(:approver_group, target: resource, group: private_group) }
context 'when merge request has user and group approvers' do let!(:approver) { create(:approver, target: resource) }
let!(:public_approver_group) { create(:approver_group, target: resource) }
let!(:approver) { create(:approver, user: user, target: resource) }
it { is_expected.to be_truthy } subject { described_class.new(resource, current_user: user).all_approvers_including_groups }
end
context 'when merge request only has user approvers' do it { is_expected.to match_array(public_approver_group.users + [approver.user]) }
let!(:approver) { create(:approver, user: user, target: resource) }
it { is_expected.to be_truthy } context 'when user has access to private group' do
end before do
private_group.add_user(user, Gitlab::Access::DEVELOPER)
end
context 'when merge request only has group approvers' do it do
let!(:public_approver_group) { create(:approver_group, target: resource) } approvers = [public_approver_group.users, private_approver_group.users, approver.user].flatten - [user]
it { is_expected.to be_truthy } is_expected.to match_array(approvers)
end
end end
end end
end end
...@@ -33,6 +33,29 @@ describe API::MergeRequestApprovals do ...@@ -33,6 +33,29 @@ describe API::MergeRequestApprovals do
expect(json_response['approver_groups'][0]['group']['name']).to eq(group.name) expect(json_response['approver_groups'][0]['group']['name']).to eq(group.name)
end end
context 'when private group approver' do
before do
private_group = create(:group, :private)
merge_request.approver_groups.create(group: private_group)
end
it 'only shows group approvers visible to the user' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user)
expect(response).to have_gitlab_http_status(200)
expect(json_response['approver_groups']).to be_empty
end
context 'when admin' do
it 'shows all approver groups' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response['approver_groups'].size).to eq(1)
end
end
end
context 'when approvers are set to zero' do context 'when approvers are set to zero' do
before do before do
project.update!(approvals_before_merge: 0) project.update!(approvals_before_merge: 0)
...@@ -100,17 +123,29 @@ describe API::MergeRequestApprovals do ...@@ -100,17 +123,29 @@ describe API::MergeRequestApprovals do
expect(response).to have_gitlab_http_status(422) expect(response).to have_gitlab_http_status(422)
end end
end end
it 'only shows approver groups that are visible to current user' do
private_group = create(:group, :private)
merge_request.approver_groups.create(group: private_group)
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", current_user), approvals_required: 5
expect(response).to have_gitlab_http_status(201)
expect(json_response['approver_groups'].size).to eq(approver_groups_count)
end
end end
context 'as a project admin' do context 'as a project admin' do
it_behaves_like 'user allowed to override approvals required' do it_behaves_like 'user allowed to override approvals required' do
let(:current_user) { user } let(:current_user) { user }
let(:approver_groups_count) { 0 }
end end
end end
context 'as a global admin' do context 'as a global admin' do
it_behaves_like 'user allowed to override approvals required' do it_behaves_like 'user allowed to override approvals required' do
let(:current_user) { admin } let(:current_user) { admin }
let(:approver_groups_count) { 1 }
end end
end end
...@@ -196,17 +231,30 @@ describe API::MergeRequestApprovals do ...@@ -196,17 +231,30 @@ describe API::MergeRequestApprovals do
end end
end end
end end
it 'only shows approver groups that are visible to current user' do
private_group = create(:group, :private)
merge_request.approver_groups.create(group: private_group)
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvers", current_user),
approver_ids: [approver.id], approver_group_ids: [private_group.id, approver_group.id]
expect(response).to have_gitlab_http_status(200)
expect(json_response['approver_groups'].size).to eq(approver_groups_count)
end
end end
context 'as a project admin' do context 'as a project admin' do
it_behaves_like 'user allowed to change approvers' do it_behaves_like 'user allowed to change approvers' do
let(:current_user) { user } let(:current_user) { user }
let(:approver_groups_count) { 1 }
end end
end end
context 'as a global admin' do context 'as a global admin' do
it_behaves_like 'user allowed to change approvers' do it_behaves_like 'user allowed to change approvers' do
let(:current_user) { admin } let(:current_user) { admin }
let(:approver_groups_count) { 2 }
end end
end end
...@@ -288,6 +336,16 @@ describe API::MergeRequestApprovals do ...@@ -288,6 +336,16 @@ describe API::MergeRequestApprovals do
expect(merge_request.reload.approvals_left).to eq(2) expect(merge_request.reload.approvals_left).to eq(2)
end end
end end
it 'only shows group approvers visible to the user' do
private_group = create(:group, :private)
merge_request.approver_groups.create(group: private_group)
approve
expect(response).to have_gitlab_http_status(201)
expect(json_response['approver_groups']).to be_empty
end
end end
end end
...@@ -302,11 +360,11 @@ describe API::MergeRequestApprovals do ...@@ -302,11 +360,11 @@ describe API::MergeRequestApprovals do
project.add_developer(create(:user)) project.add_developer(create(:user))
merge_request.approvals.create(user: approver) merge_request.approvals.create(user: approver)
merge_request.approvals.create(user: unapprover) merge_request.approvals.create(user: unapprover)
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
end end
it 'unapproves the merge request' do it 'unapproves the merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['approvals_left']).to eq(1) expect(json_response['approvals_left']).to eq(1)
usernames = json_response['approved_by'].map { |u| u['user']['username'] } usernames = json_response['approved_by'].map { |u| u['user']['username'] }
...@@ -315,6 +373,16 @@ describe API::MergeRequestApprovals do ...@@ -315,6 +373,16 @@ describe API::MergeRequestApprovals do
expect(json_response['user_has_approved']).to be false expect(json_response['user_has_approved']).to be false
expect(json_response['user_can_approve']).to be true expect(json_response['user_can_approve']).to be true
end end
it 'only shows group approvers visible to the user' do
private_group = create(:group, :private)
merge_request.approver_groups.create(group: private_group)
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
expect(response).to have_gitlab_http_status(201)
expect(json_response['approver_groups']).to be_empty
end
end end
end end
end end
...@@ -27,6 +27,16 @@ describe API::ProjectApprovals do ...@@ -27,6 +27,16 @@ describe API::ProjectApprovals do
expect(response).to match_response_schema('public_api/v4/project_approvers', dir: 'ee') expect(response).to match_response_schema('public_api/v4/project_approvers', dir: 'ee')
end end
end end
it 'only shows approver groups that are visible to the user' do
private_group = create(:group, :private)
project.approver_groups.create(group: private_group)
get api(url, user)
expect(response).to match_response_schema('public_api/v4/project_approvers', dir: 'ee')
expect(json_response["approver_groups"]).to be_empty
end
end end
describe 'POST /projects/:id/approvers' do describe 'POST /projects/:id/approvers' do
...@@ -68,18 +78,30 @@ describe API::ProjectApprovals do ...@@ -68,18 +78,30 @@ describe API::ProjectApprovals do
expect(JSON.parse(response.body).symbolize_keys).to include(settings) expect(JSON.parse(response.body).symbolize_keys).to include(settings)
end end
it 'only shows approver groups that are visible to the current user' do
private_group = create(:group, :private)
project.approver_groups.create(group: private_group)
post api(url, current_user), approvals_before_merge: 3
expect(response).to match_response_schema('public_api/v4/project_approvers', dir: 'ee')
expect(json_response["approver_groups"].size).to eq(visible_approver_groups_count)
end
end end
end end
context 'as a project admin' do context 'as a project admin' do
it_behaves_like 'a user with access' do it_behaves_like 'a user with access' do
let(:current_user) { user } let(:current_user) { user }
let(:visible_approver_groups_count) { 0 }
end end
end end
context 'as a global admin' do context 'as a global admin' do
it_behaves_like 'a user with access' do it_behaves_like 'a user with access' do
let(:current_user) { admin } let(:current_user) { admin }
let(:visible_approver_groups_count) { 1 }
end end
end end
...@@ -94,6 +116,7 @@ describe API::ProjectApprovals do ...@@ -94,6 +116,7 @@ describe API::ProjectApprovals do
describe 'PUT /projects/:id/approvers' do describe 'PUT /projects/:id/approvers' do
let(:url) { "/projects/#{project.id}/approvers" } let(:url) { "/projects/#{project.id}/approvers" }
shared_examples_for 'a user with access' do shared_examples_for 'a user with access' do
it 'removes all approvers if no params are given' do it 'removes all approvers if no params are given' do
project.approvers.create(user: approver) project.approvers.create(user: approver)
...@@ -136,17 +159,31 @@ describe API::ProjectApprovals do ...@@ -136,17 +159,31 @@ describe API::ProjectApprovals do
expect(json_response['approvers'][0]['user']['username']).to eq(approver.username) expect(json_response['approvers'][0]['user']['username']).to eq(approver.username)
expect(json_response['approver_groups'][0]['group']['name']).to eq(group.name) expect(json_response['approver_groups'][0]['group']['name']).to eq(group.name)
end end
it 'only shows approver groups that are visible to the current user' do
private_group = create(:group, :private)
project.approvers.create(user: approver)
expect do
put api(url, current_user), approver_ids: [approver.id], approver_group_ids: [private_group.id]
end.to change { project.approver_groups.count }.from(0).to(1)
expect(response).to match_response_schema('public_api/v4/project_approvers', dir: 'ee')
expect(json_response["approver_groups"].size).to eq(visible_approver_groups_count)
end
end end
context 'as a project admin' do context 'as a project admin' do
it_behaves_like 'a user with access' do it_behaves_like 'a user with access' do
let(:current_user) { user } let(:current_user) { user }
let(:visible_approver_groups_count) { 0 }
end end
end end
context 'as a global admin' do context 'as a global admin' do
it_behaves_like 'a user with access' do it_behaves_like 'a user with access' do
let(:current_user) { admin } let(:current_user) { admin }
let(:visible_approver_groups_count) { 1 }
end end
end end
......
...@@ -434,33 +434,6 @@ describe MergeRequestPresenter do ...@@ -434,33 +434,6 @@ describe MergeRequestPresenter do
end end
end end
describe '#approvals_path' do
before do
allow(resource).to receive(:requires_approve?) { requires_approve }
end
subject do
described_class.new(resource, current_user: user).approvals_path
end
context 'when approvals required' do
let(:requires_approve) { true }
it 'returns path' do
is_expected
.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/approvals")
end
end
context 'when approvals not required' do
let(:requires_approve) { false }
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe '#rebase_path' do describe '#rebase_path' do
before do before do
allow(resource).to receive(:rebase_in_progress?) { rebase_in_progress } allow(resource).to receive(:rebase_in_progress?) { rebase_in_progress }
......
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