From 8a8dfdd3dec94353fb42ee9a2550fc08f2a44a36 Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Wed, 17 Aug 2016 18:14:33 +0200 Subject: [PATCH] move project allowed query to authorization --- app/models/project.rb | 61 +--------- app/services/authorization.rb | 4 + app/services/authorization/project_query.rb | 121 ++++++++++++++++++++ 3 files changed, 126 insertions(+), 60 deletions(-) create mode 100644 app/services/authorization/project_query.rb diff --git a/app/models/project.rb b/app/models/project.rb index 4b0e4eb358..66341ec11c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -340,67 +340,8 @@ class Project < ActiveRecord::Base .references(:projects) end - # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ - # - # Valid options: - # * project: limit the condition to project - # * with_subprojects: limit the condition to project and its subprojects - # * member: limit the condition to the user projects - # * project_alias: the alias to use for the project's table - default: 'projects' def self.allowed_to_condition(user, permission, options = {}) - table_alias = options.fetch(:project_alias, Project.table_name) - - base_statement = "#{table_alias}.status=#{Project::STATUS_ACTIVE}" - if perm = Redmine::AccessControl.permission(permission) - unless perm.project_module.nil? - # If the permission belongs to a project module, make sure the module is enabled - enabled_module_statement = "SELECT em.project_id FROM #{EnabledModule.table_name} em" - - if options[:project] && options[:with_subprojects] - enabled_module_statement << " JOIN #{table_alias} ON #{table_alias}.id = em.project_id" - end - - enabled_module_statement << " WHERE em.name='#{perm.project_module}'" - - if options[:project] - project_statement = "em.project_id = #{options[:project].id}" - project_statement << " OR (#{table_alias}.lft > #{options[:project].lft} AND #{table_alias}.rgt < #{options[:project].rgt})" if options[:with_subprojects] - - enabled_module_statement << " AND (#{project_statement})" - end - base_statement << " AND #{table_alias}.id IN (#{enabled_module_statement})" - end - end - if options[:project] - project_statement = "#{table_alias}.id = #{options[:project].id}" - project_statement << " OR (#{table_alias}.lft > #{options[:project].lft} AND #{table_alias}.rgt < #{options[:project].rgt})" if options[:with_subprojects] - base_statement = "(#{project_statement}) AND (#{base_statement})" - end - - if user.admin? - base_statement - else - statement_by_role = {} - if user.logged? - if Role.non_member.allowed_to?(permission) && !options[:member] - statement_by_role[Role.non_member] = "#{table_alias}.is_public = #{connection.quoted_true}" - end - user.projects_by_role.each do |role, projects| - if role.allowed_to?(permission) - statement_by_role[role] = "#{table_alias}.id IN (#{projects.map(&:id).join(',')})" - end - end - else - if Role.anonymous.allowed_to?(permission) && !options[:member] - statement_by_role[Role.anonymous] = "#{table_alias}.is_public = #{connection.quoted_true}" - end - end - if statement_by_role.empty? - '1=0' - else - "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" - end - end + Authorization::ProjectQuery.query(user, permission, options) end # Returns the Systemwide and project specific activities diff --git a/app/services/authorization.rb b/app/services/authorization.rb index c1e9be0373..07f7d47a2f 100644 --- a/app/services/authorization.rb +++ b/app/services/authorization.rb @@ -32,6 +32,10 @@ class Authorization Authorization::UserAllowedQuery.query(action, project) end + def self.projects(action, user) + Authorization::ProjectQuery.query(user, action) + end + def self.roles(user, project = nil) if project Authorization::UserProjectRolesQuery.query(user, project) diff --git a/app/services/authorization/project_query.rb b/app/services/authorization/project_query.rb new file mode 100644 index 0000000000..fff881ad0c --- /dev/null +++ b/app/services/authorization/project_query.rb @@ -0,0 +1,121 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# 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 doc/COPYRIGHT.rdoc for more details. +#++ + +class Authorization::ProjectQuery + # Returns a SQL conditions string used to find all projects for which +user+ + # has the given +permission+ + # + # Valid options: + # * project: limit the condition to project + # * with_subprojects: limit the condition to project and its subprojects + # * member: limit the condition to the user projects + # * project_alias: the alias to use for the project's table - default: 'projects' + def self.query(user, permission, options = {}) + table_alias = options.fetch(:project_alias, Project.table_name) + + base_statement = "#{table_alias}.status=#{Project::STATUS_ACTIVE}" + if perm = Redmine::AccessControl.permission(permission) + unless perm.project_module.nil? + # If the permission belongs to a project module, make sure the module is enabled + module_query = enabled_module_query(perm.project_module, + options[:project], + options[:with_subproject]) + base_statement << " AND #{table_alias}.id IN (#{module_query})" + end + end + if options[:project] + project_statement = "#{table_alias}.id = #{options[:project].id}" + if options[:with_subprojects] + project_statement << " OR (#{descendants_condition(options[:project], table_alias)})" + end + base_statement = "(#{project_statement}) AND (#{base_statement})" + end + + if user.admin? + base_statement + else + statement_by_role = {} + if user.logged? + if Role.non_member.allowed_to?(permission) && !options[:member] + non_member_statements = [public_project_condition(table_alias)] + + member_project_ids = user.memberships.map(&:project_id) + + unless member_project_ids.empty? + non_member_statements << "#{table_alias}.id NOT IN (#{member_project_ids.join(', ')})" + end + + statement_by_role[Role.non_member] = non_member_statements.join(" AND ") + end + user.projects_by_role.each do |role, projects| + if role.allowed_to?(permission) + statement_by_role[role] = "#{table_alias}.id IN (#{projects.map(&:id).join(',')})" + end + end + elsif Role.anonymous.allowed_to?(permission) && !options[:member] + statement_by_role[Role.anonymous] = public_project_condition(table_alias) + end + if statement_by_role.empty? + '1=0' + else + "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" + end + end + end + + def self.enabled_module_query(project_module, project, with_subprojects) + enabled_module_statement = "SELECT em.project_id FROM #{EnabledModule.table_name} em" + + if project && with_subprojects + enabled_module_statement << " JOIN #{table_alias} ON #{table_alias}.id = em.project_id" + end + + enabled_module_statement << " WHERE em.name='#{project_module}'" + + if project + project_statement = "em.project_id = #{project.id}" + + if with_subprojects + project_statement << " OR (#{descendants_condition(project, table_alias)})" + end + + enabled_module_statement << " AND (#{project_statement})" + end + + enabled_module_statement + end + + def self.descendants_condition(project, table_alias) + "#{table_alias}.lft > #{project.lft} AND #{table_alias}.rgt < #{project.rgt}" + end + + def self.public_project_condition(table_alias) + "#{table_alias}.is_public = #{Project.connection.quoted_true}" + end +end