# frozen_string_literal: true module Todos module Destroy class EntityLeaveService < ::Todos::Destroy::BaseService extend ::Gitlab::Utils::Override attr_reader :user, :entity def initialize(user_id, entity_id, entity_type) unless %w(Group Project).include?(entity_type) raise ArgumentError, "#{entity_type} is not an entity user can leave" end @user = UserFinder.new(user_id).find_by_id @entity = entity_type.constantize.find_by(id: entity_id) # rubocop: disable CodeReuse/ActiveRecord end def execute return unless entity && user # if at least reporter, all entities including confidential issues can be accessed return if user_has_reporter_access? remove_confidential_resource_todos if entity.private? remove_project_todos remove_group_todos else enqueue_private_features_worker end end private def enqueue_private_features_worker projects.each do |project| TodosDestroyer::PrivateFeaturesWorker.perform_async(project.id, user.id) end end def remove_confidential_resource_todos Todo .for_target(confidential_issues.select(:id)) .for_type(Issue.name) .for_user(user) .delete_all end def remove_project_todos # Issues are viewable by guests (even in private projects), so remove those todos # from projects without guest access Todo .for_project(non_authorized_guest_projects) .for_user(user) .delete_all # MRs require reporter access, so remove those todos that are not authorized Todo .for_project(non_authorized_reporter_projects) .for_type(MergeRequest.name) .for_user(user) .delete_all end def remove_group_todos return unless entity.is_a?(Namespace) Todo .for_group(non_authorized_non_public_groups) .for_user(user) .delete_all end def projects condition = case entity when Project { id: entity.id } when Namespace { namespace_id: non_authorized_reporter_groups } end Project.where(condition) # rubocop: disable CodeReuse/ActiveRecord end def authorized_reporter_projects user.authorized_projects(Gitlab::Access::REPORTER).select(:id) end def authorized_guest_projects user.authorized_projects(Gitlab::Access::GUEST).select(:id) end def non_authorized_reporter_projects projects.id_not_in(authorized_reporter_projects) end def non_authorized_guest_projects projects.id_not_in(authorized_guest_projects) end def authorized_reporter_groups GroupsFinder.new(user, min_access_level: Gitlab::Access::REPORTER).execute.select(:id) end # since the entity is a private group, we can assume all subgroups are also # private. We can therefore limit GroupsFinder with `all_available: false`. # Otherwise it tries to include all public groups. This generates an expensive # SQL queries: https://gitlab.com/gitlab-org/gitlab/-/issues/325133 # rubocop: disable CodeReuse/ActiveRecord def non_authorized_non_public_groups return [] unless entity.is_a?(Namespace) return [] unless entity.private? entity.self_and_descendants.select(:id) .id_not_in(GroupsFinder.new(user, all_available: false).execute.select(:id).reorder(nil)) end # rubocop: enable CodeReuse/ActiveRecord def non_authorized_reporter_groups entity.self_and_descendants.select(:id) .id_not_in(authorized_reporter_groups) end def user_has_reporter_access? return unless entity.is_a?(Namespace) entity.member?(User.find(user.id), Gitlab::Access::REPORTER) end def confidential_issues assigned_ids = IssueAssignee.select(:issue_id).for_assignee(user) Issue .in_projects(projects) .confidential_only .not_in_projects(authorized_reporter_projects) .not_authored_by(user) .id_not_in(assigned_ids) end end end end Todos::Destroy::EntityLeaveService.prepend_if_ee('EE::Todos::Destroy::EntityLeaveService')