Commit 02b07792 authored by Jarka Kadlecová's avatar Jarka Kadlecová

Fix removing todos for users without access

parent 4d4b8f8b
...@@ -2,65 +2,99 @@ module Todos ...@@ -2,65 +2,99 @@ module Todos
module Destroy module Destroy
class EntityLeaveService < ::Todos::Destroy::BaseService class EntityLeaveService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
include Gitlab::Utils::StrongMemoize
attr_reader :user_id, :entity attr_reader :user, :entity
def initialize(user_id, entity_id, entity_type) def initialize(user_id, entity_id, entity_type)
unless %w(Group Project).include?(entity_type) unless %w(Group Project).include?(entity_type)
raise ArgumentError.new("#{entity_type} is not an entity user can leave") raise ArgumentError.new("#{entity_type} is not an entity user can leave")
end end
@user_id = user_id @user = User.find_by(id: user_id)
@entity = entity_type.constantize.find_by(id: entity_id) @entity = entity_type.constantize.find_by(id: entity_id)
end end
private def execute
return unless entity && user
# if at least reporter, all entities including confidential issues can be accessed
return if main_group_reporter?
remove_confidential_issue_todos
override :todos
def todos
if entity.private? if entity.private?
Todo.where('(project_id IN (?) OR group_id IN (?)) AND user_id = ?', project_ids, group_ids, user_id) remove_project_todos
remove_group_todos
else else
project_ids.each do |project_id| enqueue_private_features_worker
TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user_id) end
end end
Todo.where( private
target_id: confidential_issues.select(:id), target_type: Issue, user_id: user_id
) def enqueue_private_features_worker
project_ids.each do |project_id|
TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user.id)
end end
end end
def remove_confidential_issue_todos
Todo.where(
target_id: confidential_issues.select(:id), target_type: Issue, user_id: user.id
).delete_all
end
def remove_project_todos
Todo.where(project_id: non_authorized_projects, user_id: user.id).delete_all
end
def remove_group_todos
Todo.where(group_id: non_authorized_groups, user_id: user.id).delete_all
end
override :project_ids override :project_ids
def project_ids def project_ids
case entity condition = case entity
when Project when Project
[entity.id] { id: entity.id }
when Namespace when Namespace
Project.select(:id).where(namespace_id: group_ids) { namespace_id: non_member_groups }
end end
Project.where(condition).select(:id)
end end
def group_ids def non_authorized_projects
case entity project_ids.where('id NOT IN (?)', user.authorized_projects.select(:id))
when Project
[]
when Namespace
entity.self_and_descendants.select(:id)
end
end end
override :todos_to_remove? def non_authorized_groups
def todos_to_remove? return [] unless entity.is_a?(Namespace)
# if an entity is provided we want to check always at least private features
!!entity entity.self_and_descendants.select(:id)
.where('id NOT IN (?)', GroupsFinder.new(user).execute.select(:id))
end
def non_member_groups
entity.self_and_descendants.select(:id)
.where('id NOT IN (?)', user.membership_groups.select(:id))
end
def main_group_reporter?
return unless entity.is_a?(Namespace)
entity.member?(User.find(user.id), Gitlab::Access::REPORTER)
end end
def confidential_issues def confidential_issues
assigned_ids = IssueAssignee.select(:issue_id).where(user_id: user_id) assigned_ids = IssueAssignee.select(:issue_id).where(user_id: user.id)
authorized_reporter_projects = user
.authorized_projects(Gitlab::Access::REPORTER).select(:id)
Issue.where(project_id: project_ids, confidential: true) Issue.where(project_id: project_ids, confidential: true)
.where('author_id != ?', user_id) .where('project_id NOT IN(?)', authorized_reporter_projects)
.where('author_id != ?', user.id)
.where('id NOT IN (?)', assigned_ids) .where('id NOT IN (?)', assigned_ids)
end end
end end
......
...@@ -18,7 +18,7 @@ module Todos ...@@ -18,7 +18,7 @@ module Todos
override :authorized_users override :authorized_users
def authorized_users def authorized_users
GroupMember.select(:user_id).where(source: group.id) group.direct_and_indirect_users.select(:id)
end end
override :todos_to_remove? override :todos_to_remove?
......
...@@ -13,7 +13,7 @@ module Todos ...@@ -13,7 +13,7 @@ module Todos
override :todos override :todos
def todos def todos
Todo.where(project_id: project_ids) Todo.where(project_id: project.id)
end end
override :project_ids override :project_ids
......
...@@ -29,12 +29,8 @@ describe Todos::Destroy::ConfidentialIssueService do ...@@ -29,12 +29,8 @@ describe Todos::Destroy::ConfidentialIssueService do
issue.update!(confidential: true) issue.update!(confidential: true)
end end
it 'removes issue todos for a user who is not a project member' do it 'removes issue todos for users who can not access the confidential issue' do
expect { subject }.to change { Todo.count }.from(6).to(4) expect { subject }.to change { Todo.count }.from(6).to(4)
expect(user.todos).to match_array([todo_another_non_member])
expect(author.todos).to match_array([todo_issue_author])
expect(project_member.todos).to match_array([todo_issue_member])
end end
end end
......
...@@ -5,7 +5,7 @@ describe Todos::Destroy::EntityLeaveService do ...@@ -5,7 +5,7 @@ describe Todos::Destroy::EntityLeaveService do
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project, confidential: true) }
let(:mr) { create(:merge_request, source_project: project) } let(:mr) { create(:merge_request, source_project: project) }
let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) } let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) }
...@@ -25,17 +25,20 @@ describe Todos::Destroy::EntityLeaveService do ...@@ -25,17 +25,20 @@ describe Todos::Destroy::EntityLeaveService do
expect(user.todos).to match_array([todo_group_user]) expect(user.todos).to match_array([todo_group_user])
expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2])
end end
end
context 'when project is not private' do context 'when the user is member of the project' do
before do before do
group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) project.add_developer(user)
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end end
context 'when a user is not an author of confidential issue' do context 'when the user is a project guest' do
before do before do
issue.update!(confidential: true) project.add_guest(user)
end end
it 'removes only confidential issues todos' do it 'removes only confidential issues todos' do
...@@ -43,24 +46,79 @@ describe Todos::Destroy::EntityLeaveService do ...@@ -43,24 +46,79 @@ describe Todos::Destroy::EntityLeaveService do
end end
end end
context 'when a user is an author of confidential issue' do context 'when the user is member of a parent group' do
before do before do
issue.update!(author: user, confidential: true) group.add_developer(user)
end end
it 'removes only confidential issues todos' do it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count } expect { subject }.not_to change { Todo.count }
end end
end end
context 'when a user is an assignee of confidential issue' do context 'when the user is guest of a parent group' do
before do before do
issue.update!(confidential: true) project.add_guest(user)
issue.assignees << user
end end
it 'removes only confidential issues todos' do it 'removes only confidential issues todos' do
expect { subject }.not_to change { Todo.count } expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
end
context 'when project is not private' do
before do
group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
context 'confidential issues' do
context 'when a user is not an author of confidential issue' do
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when a user is an author of confidential issue' do
before do
issue.update!(author: user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'when a user is an assignee of confidential issue' do
before do
issue.assignees << user
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'when a user is a project guest' do
before do
project.add_guest(user)
end
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when a user is a project guest but group developer' do
before do
project.add_guest(user)
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end end
end end
...@@ -89,37 +147,128 @@ describe Todos::Destroy::EntityLeaveService do ...@@ -89,37 +147,128 @@ describe Todos::Destroy::EntityLeaveService do
expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2])
end end
context 'when the user is member of the group' do
before do
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'when the user is member of the group project but not the group' do
before do
project.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'with nested groups', :nested_groups do context 'with nested groups', :nested_groups do
let(:subgroup) { create(:group, :private, parent: group) } let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) } let(:subproject) { create(:project, group: subgroup) }
let(:subproject2) { create(:project, group: subgroup2) }
let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) } let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) }
let!(:todo_subproject2_user) { create(:todo, user: user, project: subproject2) }
let!(:todo_subgroup_user) { create(:todo, user: user, group: subgroup) } let!(:todo_subgroup_user) { create(:todo, user: user, group: subgroup) }
let!(:todo_subgroup2_user) { create(:todo, user: user, group: subgroup2) }
let!(:todo_subproject_user2) { create(:todo, user: user2, project: subproject) } let!(:todo_subproject_user2) { create(:todo, user: user2, project: subproject) }
let!(:todo_subpgroup_user2) { create(:todo, user: user2, group: subgroup) } let!(:todo_subpgroup_user2) { create(:todo, user: user2, group: subgroup) }
it 'removes todos for the user including subprojects todos' do context 'when the user is not a member of any groups/projects' do
expect { subject }.to change { Todo.count }.from(9).to(4) it 'removes todos for the user including subprojects todos' do
expect { subject }.to change { Todo.count }.from(11).to(4)
expect(user.todos).to be_empty
expect(user2.todos)
.to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
)
end
end
context 'when the user is member of a parent group' do
before do
parent_group = create(:group)
group.update!(parent: parent_group)
parent_group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'when the user is member of a subgroup' do
before do
subgroup.add_developer(user)
end
it 'does not remove group and subproject todos' do
expect { subject }.to change { Todo.count }.from(11).to(7)
expect(user.todos).to match_array([todo_group_user, todo_subgroup_user, todo_subproject_user])
expect(user2.todos)
.to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
)
end
end
expect(user.todos).to be_empty context 'when the user is member of a child project' do
expect(user2.todos) before do
.to match_array( subproject.add_developer(user)
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2] end
)
it 'does not remove subproject and group todos' do
expect { subject }.to change { Todo.count }.from(11).to(7)
expect(user.todos).to match_array([todo_subgroup_user, todo_group_user, todo_subproject_user])
expect(user2.todos)
.to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
)
end
end end
end end
end end
context 'when group is not private' do context 'when group is not private' do
before do before do
issue.update!(confidential: true)
group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end end
it 'removes only confidential issues todos' do context 'when user is not member' do
expect { subject }.to change { Todo.count }.from(5).to(4) it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when user is a project guest' do
before do
project.add_guest(user)
end
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when user is a project guest & group developer' do
before do
project.add_guest(user)
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Todos::Destroy::GroupPrivateService do describe Todos::Destroy::GroupPrivateService do
let(:group) { create(:group, :public) } let(:group) { create(:group, :public) }
let(:user) { create(:user) } let(:project) { create(:project, group: group) }
let(:group_member) { create(:user) } let(:user) { create(:user) }
let(:group_member) { create(:user) }
let(:project_member) { create(:user) }
let!(:todo_non_member) { create(:todo, user: user, group: group) } let!(:todo_non_member) { create(:todo, user: user, group: group) }
let!(:todo_member) { create(:todo, user: group_member, group: group) }
let!(:todo_another_non_member) { create(:todo, user: user, group: group) } let!(:todo_another_non_member) { create(:todo, user: user, group: group) }
let!(:todo_group_member) { create(:todo, user: group_member, group: group) }
let!(:todo_project_member) { create(:todo, user: project_member, group: group) }
describe '#execute' do describe '#execute' do
before do before do
group.add_developer(group_member) group.add_developer(group_member)
project.add_developer(project_member)
end end
subject { described_class.new(group.id).execute } subject { described_class.new(group.id).execute }
context 'when a project set to private' do context 'when a group set to private' do
before do before do
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end end
it 'removes todos for a user who is not a member' do it 'removes todos only for users who are not group users' do
expect { subject }.to change { Todo.count }.from(3).to(1) expect { subject }.to change { Todo.count }.from(4).to(2)
expect(user.todos).to be_empty expect(user.todos).to be_empty
expect(group_member.todos).to match_array([todo_member]) expect(group_member.todos).to match_array([todo_group_member])
expect(project_member.todos).to match_array([todo_project_member])
end
context 'with nested groups' do
let(:parent_group) { create(:group) }
let(:subgroup) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
let(:parent_member) { create(:user) }
let(:subgroup_member) { create(:user) }
let(:subgproject_member) { create(:user) }
let!(:todo_parent_member) { create(:todo, user: parent_member, group: group) }
let!(:todo_subgroup_member) { create(:todo, user: subgroup_member, group: group) }
let!(:todo_subproject_member) { create(:todo, user: subgproject_member, group: group) }
before do
group.update!(parent: parent_group)
parent_group.add_developer(parent_member)
subgroup.add_developer(subgroup_member)
subproject.add_developer(subgproject_member)
end
it 'removes todos only for users who are not group users' do
expect { subject }.to change { Todo.count }.from(7).to(5)
end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Todos::Destroy::ProjectPrivateService do describe Todos::Destroy::ProjectPrivateService do
let(:project) { create(:project, :public) } let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) { create(:user) } let(:project_member) { create(:user) }
let(:group_member) { create(:user) }
let!(:todo_issue_non_member) { create(:todo, user: user, project: project) } let!(:todo_non_member) { create(:todo, user: user, project: project) }
let!(:todo_issue_member) { create(:todo, user: project_member, project: project) } let!(:todo2_non_member) { create(:todo, user: user, project: project) }
let!(:todo_another_non_member) { create(:todo, user: user, project: project) } let!(:todo_member) { create(:todo, user: project_member, project: project) }
let!(:todo_member) { create(:todo, user: project_member, project: project) }
let!(:todo_group_member) { create(:todo, user: group_member, project: project) }
describe '#execute' do describe '#execute' do
before do before do
project.add_developer(project_member) project.add_developer(project_member)
group.add_developer(group_member)
end end
subject { described_class.new(project.id).execute } subject { described_class.new(project.id).execute }
...@@ -22,10 +27,11 @@ describe Todos::Destroy::ProjectPrivateService do ...@@ -22,10 +27,11 @@ describe Todos::Destroy::ProjectPrivateService do
end end
it 'removes issue todos for a user who is not a member' do it 'removes issue todos for a user who is not a member' do
expect { subject }.to change { Todo.count }.from(3).to(1) expect { subject }.to change { Todo.count }.from(4).to(2)
expect(user.todos).to be_empty expect(user.todos).to be_empty
expect(project_member.todos).to match_array([todo_issue_member]) expect(project_member.todos).to match_array([todo_member])
expect(group_member.todos).to match_array([todo_group_member])
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