Skip to content
This repository has been archived by the owner on Nov 19, 2020. It is now read-only.

Commit

Permalink
Merged 15430, 15464 to 15469, 15475, 15476 (#285, #7839).
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.redmine.org/redmine/branches/3.3-stable@15478 e93f8b46-1217-0410-a6f0-8f06a7374b81
  • Loading branch information
jplang committed Jun 6, 2016
1 parent 7a97443 commit 6e68d00
Show file tree
Hide file tree
Showing 23 changed files with 524 additions and 82 deletions.
7 changes: 3 additions & 4 deletions app/controllers/context_menus_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,23 @@ def issues

@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)

@can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
@can = {:edit => @issues.all?(&:attributes_editable?),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
:copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
:add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
:delete => User.current.allowed_to?(:delete_issues, @projects)
:delete => @issues.all?(&:deletable?)
}
if @project
if @issue
@assignables = @issue.assignable_users
else
@assignables = @project.assignable_users
end
@trackers = @project.trackers
else
#when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&)
@trackers = @projects.map(&:trackers).reduce(:&)
end
@trackers = @projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
@versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)

@priorities = IssuePriority.active.reverse
Expand Down
21 changes: 18 additions & 3 deletions app/controllers/issues_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ def bulk_edit
unless User.current.allowed_to?(:copy_issues, @projects)
raise ::Unauthorized
end
else
unless @issues.all?(&:attributes_editable?)
raise ::Unauthorized
end
end

@allowed_projects = Issue.allowed_target_projects
Expand All @@ -230,7 +234,7 @@ def bulk_edit
end
@custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@trackers = target_projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
Expand Down Expand Up @@ -263,6 +267,10 @@ def bulk_update
unless User.current.allowed_to?(:add_issues, target_projects)
raise ::Unauthorized
end
else
unless @issues.all?(&:attributes_editable?)
raise ::Unauthorized
end
end

unsaved_issues = []
Expand Down Expand Up @@ -316,6 +324,7 @@ def bulk_update
end

def destroy
raise Unauthorized unless @issues.all?(&:deletable?)
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
if @hours > 0
case params[:todo]
Expand Down Expand Up @@ -465,9 +474,15 @@ def build_new_issue_from_params
@issue.safe_attributes = attrs

if @issue.project
@issue.tracker ||= @issue.project.trackers.first
@issue.tracker ||= @issue.allowed_target_trackers.first
if @issue.tracker.nil?
render_error l(:error_no_tracker_in_project)
if @issue.project.trackers.any?
# None of the project trackers is allowed to the user
render_error :message => l(:error_no_tracker_allowed_for_new_issue_in_project), :status => 403
else
# Project has no trackers
render_error l(:error_no_tracker_in_project)
end
return false
end
if @issue.status.nil?
Expand Down
10 changes: 10 additions & 0 deletions app/helpers/issues_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ def link_to_new_subtask(issue)
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
end

def trackers_options_for_select(issue)
trackers = issue.allowed_target_trackers
if issue.new_record? && issue.parent_issue_id.present?
trackers = trackers.reject do |tracker|
issue.tracker_id != tracker.id && tracker.disabled_core_fields.include?('parent_issue_id')
end
end
trackers.collect {|t| [t.name, t.id]}
end

class IssueFieldsRows
include ActionView::Helpers::TagHelper

Expand Down
98 changes: 76 additions & 22 deletions app/models/issue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ class Issue < ActiveRecord::Base
# Returns a SQL conditions string used to find all issues visible by the specified user
def self.visible_condition(user, options={})
Project.allowed_to_condition(user, :view_issues, options) do |role, user|
if user.id && user.logged?
sql = if user.id && user.logged?
case role.issues_visibility
when 'all'
nil
'1=1'
when 'default'
user_ids = [user.id] + user.groups.map(&:id).compact
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
Expand All @@ -136,13 +136,22 @@ def self.visible_condition(user, options={})
else
"(#{table_name}.is_private = #{connection.quoted_false})"
end
unless role.permissions_all_trackers?(:view_issues)
tracker_ids = role.permissions_tracker_ids(:view_issues)
if tracker_ids.any?
sql = "(#{sql} AND #{table_name}.tracker_id IN (#{tracker_ids.join(',')}))"
else
sql = '1=0'
end
end
sql
end
end

# Returns true if usr or current user is allowed to view the issue
def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
if user.logged?
visible = if user.logged?
case role.issues_visibility
when 'all'
true
Expand All @@ -156,17 +165,36 @@ def visible?(usr=nil)
else
!self.is_private?
end
unless role.permissions_all_trackers?(:view_issues)
visible &&= role.permissions_tracker_ids?(:view_issues, tracker_id)
end
visible
end
end

# Returns true if user or current user is allowed to edit or add a note to the issue
# Returns true if user or current user is allowed to edit or add notes to the issue
def editable?(user=User.current)
attributes_editable?(user) || user.allowed_to?(:add_issue_notes, project)
attributes_editable?(user) || notes_addable?(user)
end

# Returns true if user or current user is allowed to edit the issue
def attributes_editable?(user=User.current)
user.allowed_to?(:edit_issues, project)
user_tracker_permission?(user, :edit_issues)
end

# Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_editable?
def attachments_editable?(user=User.current)
attributes_editable?(user)
end

# Returns true if user or current user is allowed to add notes to the issue
def notes_addable?(user=User.current)
user_tracker_permission?(user, :add_issue_notes)
end

# Returns true if user or current user is allowed to delete the issue
def deletable?(user=User.current)
user_tracker_permission?(user, :delete_issues)
end

def initialize(attributes=nil, *args)
Expand Down Expand Up @@ -416,10 +444,10 @@ def estimated_hours=(h)
'custom_fields',
'lock_version',
'notes',
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user) }

safe_attributes 'notes',
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
:if => lambda {|issue, user| issue.notes_addable?(user)}

safe_attributes 'private_notes',
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
Expand All @@ -434,7 +462,7 @@ def estimated_hours=(h)
}

safe_attributes 'parent_issue_id',
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
:if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) &&
user.allowed_to?(:manage_subtasks, issue.project)}

def safe_attribute_names(user=nil)
Expand Down Expand Up @@ -479,12 +507,14 @@ def safe_attributes=(attrs, user=User.current)
end

if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
self.tracker_id = t
if allowed_target_trackers(user).where(:id => t.to_i).exists?
self.tracker_id = t
end
end
if project
# Set the default tracker to accept custom field values
# Set a default tracker to accept custom field values
# even if tracker is not specified
self.tracker ||= project.trackers.first
self.tracker ||= allowed_target_trackers(user).first
end

statuses_allowed = new_statuses_allowed_to(user)
Expand Down Expand Up @@ -822,16 +852,6 @@ def children?
!leaf?
end

def assignable_trackers
trackers = project.trackers
if new_record? && parent_issue_id.present?
trackers = trackers.reject do |tracker|
tracker_id != tracker.id && tracker.disabled_core_fields.include?('parent_issue_id')
end
end
trackers
end

# Users the issue can be assigned to
def assignable_users
users = project.assignable_users.to_a
Expand Down Expand Up @@ -1373,9 +1393,43 @@ def self.allowed_target_projects(user=User.current, current_project=nil)
end
Project.where(condition).having_trackers
end

# Returns a scope of trackers that user can assign the issue to
def allowed_target_trackers(user=User.current)
self.class.allowed_target_trackers(project, user, tracker_id_was)
end

# Returns a scope of trackers that user can assign project issues to
def self.allowed_target_trackers(project, user=User.current, current_tracker=nil)
if project
scope = project.trackers.sorted
unless user.admin?
roles = user.roles_for_project(project).select {|r| r.has_permission?(:add_issues)}
unless roles.any? {|r| r.permissions_all_trackers?(:add_issues)}
tracker_ids = roles.map {|r| r.permissions_tracker_ids(:add_issues)}.flatten.uniq
if current_tracker
tracker_ids << current_tracker
end
scope = scope.where(:id => tracker_ids)
end
end
scope
else
Tracker.none
end
end

private

def user_tracker_permission?(user, permission)
if user.admin?
true
else
roles = user.roles_for_project(project).select {|r| r.has_permission?(permission)}
roles.any? {|r| r.permissions_all_trackers?(permission) || r.permissions_tracker_ids?(permission, tracker_id)}
end
end

def after_project_change
# Update project_id on related time entries
TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
Expand Down
2 changes: 1 addition & 1 deletion app/models/issue_import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def project
# Returns a scope of trackers that user is allowed to
# import issue to
def allowed_target_trackers
project.trackers
Issue.allowed_target_trackers(project, user)
end

def tracker
Expand Down
13 changes: 8 additions & 5 deletions app/models/mail_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,14 @@ def receive_issue
end

issue = Issue.new(:author => user, :project => project)
issue.safe_attributes = issue_attributes_from_keywords(issue)
attributes = issue_attributes_from_keywords(issue)
if handler_options[:no_permission_check]
issue.tracker_id = attributes['tracker_id']
if project
issue.tracker_id ||= project.trackers.first.try(:id)
end
end
issue.safe_attributes = attributes
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
issue.subject = cleaned_up_subject
if issue.subject.blank?
Expand Down Expand Up @@ -420,10 +427,6 @@ def issue_attributes_from_keywords(issue)
'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0')
}.delete_if {|k, v| v.blank? }

if issue.new_record? && attrs['tracker_id'].nil?
attrs['tracker_id'] = issue.project.trackers.first.try(:id)
end

attrs
end

Expand Down
51 changes: 51 additions & 0 deletions app/models/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def copy(source_role)
acts_as_positioned :scope => :builtin

serialize :permissions, ::Role::PermissionsAttributeCoder
store :settings, :accessors => [:permissions_all_trackers, :permissions_tracker_ids]
attr_protected :builtin

validates_presence_of :name
Expand Down Expand Up @@ -188,6 +189,56 @@ def setable_permissions
setable_permissions
end

def permissions_tracker_ids(*args)
if args.any?
Array(permissions_tracker_ids[args.first.to_s]).map(&:to_i)
else
super || {}
end
end

def permissions_tracker_ids=(arg)
h = arg.to_hash
h.values.each {|v| v.reject!(&:blank?)}
super(h)
end

# Returns true if tracker_id belongs to the list of
# trackers for which permission is given
def permissions_tracker_ids?(permission, tracker_id)
permissions_tracker_ids(permission).include?(tracker_id)
end

def permissions_all_trackers
super || {}
end

def permissions_all_trackers=(arg)
super(arg.to_hash)
end

# Returns true if permission is given for all trackers
def permissions_all_trackers?(permission)
permissions_all_trackers[permission.to_s].to_s != '0'
end

# Sets the trackers that are allowed for a permission.
# tracker_ids can be an array of tracker ids or :all for
# no restrictions.
#
# Examples:
# role.set_permission_trackers :add_issues, [1, 3]
# role.set_permission_trackers :add_issues, :all
def set_permission_trackers(permission, tracker_ids)
h = {permission.to_s => (tracker_ids == :all ? '1' : '0')}
self.permissions_all_trackers = permissions_all_trackers.merge(h)

h = {permission.to_s => (tracker_ids == :all ? [] : tracker_ids)}
self.permissions_tracker_ids = permissions_tracker_ids.merge(h)

self
end

# Find all the roles that can be given to a project member
def self.find_all_givable
Role.givable.to_a
Expand Down
2 changes: 1 addition & 1 deletion app/views/issues/_action_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
<%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %>
<%= watcher_link(@issue, User.current) %>
<%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), :class => 'icon icon-copy' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %>
<%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if User.current.allowed_to?(:delete_issues, @project) %>
<%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if @issue.deletable? %>
</div>
Loading

0 comments on commit 6e68d00

Please sign in to comment.