OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/app/services/authorization/user_allowed_service.rb

135 lines
4.4 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
class Authorization::UserAllowedService
attr_accessor :user
def initialize(user)
self.user = user
end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. controller: '/projects', action: 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def call(action, context, options = {})
if supported_context?(context, options)
ServiceResult.new(success: true,
result: allowed_to?(action, context, options))
else
ServiceResult.new(success: false,
result: false)
end
end
def preload_projects_allowed_to(action)
project_authorization_cache.cache(action)
end
private
def allowed_to?(action, context, options = {})
action = normalize_action(action)
if context.nil? && options[:global]
allowed_to_globally?(action, options)
elsif context.is_a? Project
allowed_to_in_project?(action, context, options)
elsif context.respond_to?(:to_a)
context = context.to_a
# Authorize if user is authorized on every element of the array
context.present? && context.all? do |project|
allowed_to?(action, project, options)
end
else
false
end
end
def allowed_to_in_project?(action, project, _options = {})
if project_authorization_cache.cached?(action)
return project_authorization_cache.allowed?(action, project)
end
# No action allowed on archived projects
return false unless project.active?
# No action allowed on disabled modules
return false unless project.allows_to?(action)
# Admin users are authorized for anything else
return true if user.admin?
has_authorized_role?(action, project)
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, _options = {})
# Admin users are always authorized
return true if user.admin?
has_authorized_role?(action)
end
def has_authorized_role?(action, project = nil)
project_role_cache[project] ||= ::Authorization.roles(user, project)
project_role_cache[project].any? do |role|
role.allowed_to?(action)
end
end
def project_authorization_cache
@project_authorization_cache ||= User::ProjectAuthorizationCache.new(user)
end
def project_role_cache
@project_role_cache ||= {}
end
def normalize_action(action)
if action.is_a?(Hash) && action[:controller] && action[:controller].to_s.starts_with?('/')
action = action.dup
action[:controller] = action[:controller][1..-1]
end
action
end
def supported_context?(context, options)
(context.nil? && options[:global]) ||
context.is_a?(Project) ||
(!context.nil? && context.respond_to?(:to_a))
end
end