opf/openproject

View on GitHub
app/models/work_packages/scopes/allowed_to.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++

module WorkPackages::Scopes
  module AllowedTo
    extend ActiveSupport::Concern

    class_methods do
      # Returns an ActiveRecord::Relation to find all work packages for which
      # +user+ has the given +permission+ either directly on the work package
      # or by the linked project
      def allowed_to(user, permission) # rubocop:disable Metrics/AbcSize
        permissions = Authorization.contextual_permissions(permission, :work_package, raise_on_unknown: true)

        return none if user.locked?
        return none if permissions.empty?

        if user.admin? && permissions.all?(&:grant_to_admin?)
          where(id: allowed_to_admin_relation(permissions))
        elsif user.anonymous?
          where(project_id: Project.allowed_to(user, permissions))
        else
          allowed_via_wp_membership = allowed_to_member_relation(user, permissions).select(arel_table[:id]).arel
          allowed_via_project_membership = Project.unscoped.allowed_to(user, permissions).select(:id)

          with(
            allowed_work_packages: allowed_via_wp_membership,
            allowed_projects: allowed_via_project_membership
          ).where("work_packages.project_id IN (SELECT id FROM allowed_projects) OR work_packages.id IN (SELECT id FROM allowed_work_packages)")
        end
      end

      private

      def allowed_to_admin_relation(permissions)
        unscoped
        .joins(:project)
        .joins(allowed_to_enabled_module_join(permissions))
          .where(Project.arel_table[:active].eq(true))
      end

      def allowed_to_member_relation(user, permissions)
        Member
          .joins(allowed_to_member_in_work_package_join)
          .joins(active_project_join)
          .joins(allowed_to_enabled_module_join(permissions))
          .joins(member_roles: :role)
          .joins(allowed_to_role_permission_join(permissions))
          .where(member_conditions(user))
          .select(arel_table[:id])
      end

      def allowed_to_enabled_module_join(permissions) # rubocop:disable Metrics/AbcSize
        project_module = permissions.filter_map(&:project_module).uniq
        enabled_module_table = EnabledModule.arel_table
        projects_table = Project.arel_table

        if project_module.any?
          arel_table.join(enabled_module_table, Arel::Nodes::InnerJoin)
                    .on(projects_table[:id].eq(enabled_module_table[:project_id])
                          .and(enabled_module_table[:name].in(project_module))
                          .and(projects_table[:active].eq(true)))
                    .join_sources
        end
      end

      def allowed_to_role_permission_join(permissions) # rubocop:disable Metrics/AbcSize
        return if permissions.all?(&:public?)

        role_permissions_table = RolePermission.arel_table
        enabled_modules_table = EnabledModule.arel_table
        roles_table = Role.arel_table

        condition = permissions.inject(Arel::Nodes::False.new) do |or_condition, permission|
          permission_condition = role_permissions_table[:permission].eq(permission.name)

          if permission.project_module.present?
            permission_condition = permission_condition.and(enabled_modules_table[:name].eq(permission.project_module))
          end

          or_condition.or(permission_condition)
        end

        arel_table
          .join(role_permissions_table, Arel::Nodes::InnerJoin)
          .on(roles_table[:id].eq(role_permissions_table[:role_id])
                              .and(condition))
          .join_sources
      end

      def active_project_join
        projects_table = Project.arel_table
        arel_table
          .join(projects_table)
                  .on(projects_table[:active].eq(true)
                   .and(projects_table[:id].eq(arel_table[:project_id])))
                  .join_sources
      end

      def allowed_to_member_in_work_package_join
        members_table = Member.arel_table
        arel_table.join(arel_table)
        .on(members_table[:entity_id].eq(arel_table[:id]))
        .join_sources
      end

      def member_conditions(user)
        Member.arel_table[:user_id].eq(user.id)
        .and(Member.arel_table[:entity_type].eq(model_name.name))
      end
    end
  end
end