Commit 6b7ef4ee authored by Sarah Yasonik's avatar Sarah Yasonik Committed by Natalia Tepluhina

Limit alert assignment to only users who can read alerts

parent a6eb8c15
...@@ -25,6 +25,9 @@ export default { ...@@ -25,6 +25,9 @@ export default {
UPDATE_ALERT_ASSIGNEES_ERROR: s__( UPDATE_ALERT_ASSIGNEES_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.', 'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
), ),
UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR: s__(
'AlertManagement|This assignee cannot be assigned to this alert.',
),
components: { components: {
GlIcon, GlIcon,
GlDropdown, GlDropdown,
...@@ -156,9 +159,17 @@ export default { ...@@ -156,9 +159,17 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}, },
}) })
.then(() => { .then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => {
this.hideDropdown(); this.hideDropdown();
this.$emit('alert-refresh');
if (errors[0]) {
return this.$emit(
'alert-sidebar-error',
`${this.$options.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
);
}
return this.$emit('alert-refresh');
}) })
.catch(() => { .catch(() => {
this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR); this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
......
...@@ -19,6 +19,8 @@ module AlertManagement ...@@ -19,6 +19,8 @@ module AlertManagement
return error_no_updates if params.empty? return error_no_updates if params.empty?
filter_assignees filter_assignees
return error_no_assignee_permissions if unauthorized_assignees?
old_assignees = alert.assignees.to_a old_assignees = alert.assignees.to_a
if alert.update(params) if alert.update(params)
...@@ -38,10 +40,6 @@ module AlertManagement ...@@ -38,10 +40,6 @@ module AlertManagement
current_user&.can?(:update_alert_management_alert, alert) current_user&.can?(:update_alert_management_alert, alert)
end end
def assignee_todo_allowed?
assignee&.can?(:read_alert_management_alert, alert)
end
def todo_service def todo_service
strong_memoize(:todo_service) do strong_memoize(:todo_service) do
TodoService.new TodoService.new
...@@ -64,18 +62,20 @@ module AlertManagement ...@@ -64,18 +62,20 @@ module AlertManagement
error(_('Please provide attributes to update')) error(_('Please provide attributes to update'))
end end
def error_no_assignee_permissions
error(_('Assignee has no permissions'))
end
# ----- Assignee-related behavior ------ # ----- Assignee-related behavior ------
def unauthorized_assignees?
params[:assignees]&.any? { |user| !user.can?(:read_alert_management_alert, alert) }
end
def filter_assignees def filter_assignees
return if params[:assignees].nil? return if params[:assignees].nil?
params[:assignees] = Array(assignee) # Always take first assignee while multiple are not currently supported
end params[:assignees] = Array(params[:assignees].first)
def assignee
strong_memoize(:assignee) do
# Take first assignee while multiple are not currently supported
params[:assignees]&.first
end
end end
def process_assignement(old_assignees) def process_assignement(old_assignees)
...@@ -84,8 +84,7 @@ module AlertManagement ...@@ -84,8 +84,7 @@ module AlertManagement
end end
def assign_todo def assign_todo
# Remove check in follow-up issue https://gitlab.com/gitlab-org/gitlab/-/issues/222672 return if alert.assignees.empty?
return unless assignee_todo_allowed?
todo_service.assign_alert(alert, current_user) todo_service.assign_alert(alert, current_user)
end end
......
---
title: Limit alert assignment to only users who can read alerts
merge_request: 34681
author:
type: fixed
...@@ -2011,6 +2011,9 @@ msgstr "" ...@@ -2011,6 +2011,9 @@ msgstr ""
msgid "AlertManagement|There was an error while updating the status of the alert. Please try again." msgid "AlertManagement|There was an error while updating the status of the alert. Please try again."
msgstr "" msgstr ""
msgid "AlertManagement|This assignee cannot be assigned to this alert."
msgstr ""
msgid "AlertManagement|Tool" msgid "AlertManagement|Tool"
msgstr "" msgstr ""
...@@ -3020,6 +3023,9 @@ msgid_plural "%d Assignees" ...@@ -3020,6 +3023,9 @@ msgid_plural "%d Assignees"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Assignee has no permissions"
msgstr ""
msgid "Assignee lists not available with your current license" msgid "Assignee lists not available with your current license"
msgstr "" msgstr ""
......
...@@ -59,7 +59,7 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -59,7 +59,7 @@ describe('Alert Details Sidebar Assignees', () => {
describe('updating the alert status', () => { describe('updating the alert status', () => {
const mockUpdatedMutationResult = { const mockUpdatedMutationResult = {
data: { data: {
updateAlertStatus: { alertSetAssignees: {
errors: [], errors: [],
alert: { alert: {
assigneeUsernames: ['root'], assigneeUsernames: ['root'],
...@@ -125,6 +125,26 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -125,6 +125,26 @@ describe('Alert Details Sidebar Assignees', () => {
}); });
}); });
it('shows an error when request contains error messages', () => {
wrapper.setData({ isDropdownSearching: false });
const errorMutationResult = {
data: {
alertSetAssignees: {
errors: ['There was a problem for sure.'],
alert: {},
},
},
};
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult);
return wrapper.vm.$nextTick().then(() => {
const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0);
SideBarAssigneeItem.vm.$emit('click');
expect(wrapper.emitted('alert-refresh')).toBeUndefined();
});
});
it('stops updating and cancels loading when the request fails', () => { it('stops updating and cancels loading when the request fails', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
wrapper.vm.updateAlertAssignees('root'); wrapper.vm.updateAlertAssignees('root');
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
describe AlertManagement::Alerts::UpdateService do describe AlertManagement::Alerts::UpdateService do
let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:other_user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) }
let_it_be(:alert, reload: true) { create(:alert_management_alert) } let_it_be(:alert, reload: true) { create(:alert_management_alert) }
let_it_be(:project) { alert.project } let_it_be(:project) { alert.project }
...@@ -15,119 +16,131 @@ describe AlertManagement::Alerts::UpdateService do ...@@ -15,119 +16,131 @@ describe AlertManagement::Alerts::UpdateService do
before_all do before_all do
project.add_developer(user_with_permissions) project.add_developer(user_with_permissions)
project.add_developer(other_user_with_permissions)
end end
describe '#execute' do describe '#execute' do
shared_examples 'does not add a todo' do
specify { expect { response }.not_to change(Todo, :count) }
end
shared_examples 'does not add a system note' do
specify { expect { response }.not_to change(Note, :count) }
end
shared_examples 'error response' do |message|
it_behaves_like 'does not add a todo'
it_behaves_like 'does not add a system note'
it 'has an informative message' do
expect(response).to be_error
expect(response.message).to eq(message)
end
end
subject(:response) { service.execute } subject(:response) { service.execute }
context 'when the current_user is nil' do context 'when the current_user is nil' do
let(:current_user) { nil } let(:current_user) { nil }
it 'results in an error' do it_behaves_like 'error response', 'You have no permissions'
expect(response).to be_error
expect(response.message).to eq('You have no permissions')
end
end end
context 'when user does not have permission to update alerts' do context 'when current_user does not have permission to update alerts' do
let(:current_user) { user_without_permissions } let(:current_user) { user_without_permissions }
it 'results in an error' do it_behaves_like 'error response', 'You have no permissions'
expect(response).to be_error
expect(response.message).to eq('You have no permissions')
end
end end
context 'when no parameters are included' do context 'when no parameters are included' do
it 'results in an error' do it_behaves_like 'error response', 'Please provide attributes to update'
expect(response).to be_error
expect(response.message).to eq('Please provide attributes to update')
end
end end
context 'when an error occures during update' do context 'when an error occurs during update' do
let(:params) { { title: nil } } let(:params) { { title: nil } }
it 'results in an error' do it_behaves_like 'error response', "Title can't be blank"
expect { response }.not_to change { alert.reload.notes.count }
expect(response).to be_error
expect(response.message).to eq("Title can't be blank")
end
end end
context 'when a model attribute is included without assignees' do context 'when a model attribute is included without assignees' do
let(:params) { { title: 'This is an updated alert.' } } let(:params) { { title: 'This is an updated alert.' } }
it_behaves_like 'does not add a todo'
it_behaves_like 'does not add a system note'
it 'updates the attribute' do it 'updates the attribute' do
original_title = alert.title original_title = alert.title
expect { response }.to change { alert.title }.from(original_title).to(params[:title]) expect { response }.to change { alert.title }.from(original_title).to(params[:title])
expect(response).to be_success expect(response).to be_success
end end
end
it 'skips adding a todo' do context 'when assignees are included' do
expect { response }.not_to change(Todo, :count) shared_examples 'adds a todo' do
let(:assignee) { expected_assignees.first }
specify do
expect { response }.to change { assignee.reload.todos.count }.by(1)
expect(assignee.todos.last.author).to eq(current_user)
end end
end end
context 'when assignees are included' do shared_examples 'adds a system note' do
let(:params) { { assignees: [user_with_permissions] } } specify { expect { response }.to change { alert.reload.notes.count }.by(1) }
end
shared_examples 'successful assignment' do
it_behaves_like 'adds a system note'
it_behaves_like 'adds a todo'
after do after do
alert.assignees = [] alert.assignees = []
end end
it 'assigns the user' do specify do
expect { response }.to change { alert.reload.assignees }.from([]).to(params[:assignees]) expect { response }.to change { alert.reload.assignees }.from([]).to(expected_assignees)
expect(response).to be_success expect(response).to be_success
end end
it 'creates a system note for the assignment' do
expect { response }.to change { alert.reload.notes.count }.by(1)
end end
it 'adds a todo' do let(:expected_assignees) { params[:assignees] }
expect { response }.to change { Todo.where(user: user_with_permissions).count }.by(1)
end
context 'when current user is not the assignee' do context 'when the assignee is the current user' do
let(:assignee_user) { create(:user) } let(:params) { { assignees: [current_user] } }
let(:params) { { assignees: [assignee_user] } }
it 'skips adding todo for assignee without permission to read alert' do it_behaves_like 'successful assignment'
expect { response }.not_to change(Todo, :count)
end end
context 'when assignee has read permission' do context 'when the assignee has read permissions' do
before do let(:params) { { assignees: [other_user_with_permissions] } }
project.add_developer(assignee_user)
it_behaves_like 'successful assignment'
end end
it 'adds a todo' do context 'when the assignee does not have read permissions' do
response let(:params) { { assignees: [user_without_permissions] } }
expect(Todo.first.author).to eq(current_user) it_behaves_like 'error response', 'Assignee has no permissions'
end end
end
context 'when current_user is nil' do
let(:current_user) { nil }
it 'skips adding todo if current_user is nil' do context 'when user is already assigned' do
project.add_developer(assignee_user) let(:params) { { assignees: [user_with_permissions] } }
expect { response }.not_to change(Todo, :count) before do
end alert.assignees << user_with_permissions
end end
it_behaves_like 'does not add a system note'
# TODO: We should not add another todo in this scenario
it_behaves_like 'adds a todo'
end end
context 'with multiple users included' do context 'with multiple users included' do
let(:params) { { assignees: [user_with_permissions, user_without_permissions] } } let(:params) { { assignees: [user_with_permissions, user_without_permissions] } }
let(:expected_assignees) { [user_with_permissions] }
it 'assigns the first permissioned user' do it_behaves_like 'successful assignment'
expect { response }.to change { alert.reload.assignees }.from([]).to([user_with_permissions])
expect(response).to be_success
end
end end
end end
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