Skip to content

Commit c4fd175

Browse files
committed
Adds permission to edit and delete issues by role/tracker (#285).
git-svn-id: http://svn.redmine.org/redmine/trunk@15466 e93f8b46-1217-0410-a6f0-8f06a7374b81
1 parent a23450f commit c4fd175

File tree

10 files changed

+106
-27
lines changed

10 files changed

+106
-27
lines changed

app/controllers/context_menus_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ def issues
2929

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

32-
@can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
32+
@can = {:edit => @issues.all?(&:attributes_editable?),
3333
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
3434
:copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
3535
:add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
36-
:delete => User.current.allowed_to?(:delete_issues, @projects)
36+
:delete => @issues.all?(&:deletable?)
3737
}
3838
if @project
3939
if @issue

app/controllers/issues_controller.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ def bulk_edit
211211
unless User.current.allowed_to?(:copy_issues, @projects)
212212
raise ::Unauthorized
213213
end
214+
else
215+
unless @issues.all?(&:attributes_editable?)
216+
raise ::Unauthorized
217+
end
214218
end
215219

216220
@allowed_projects = Issue.allowed_target_projects
@@ -263,6 +267,10 @@ def bulk_update
263267
unless User.current.allowed_to?(:add_issues, target_projects)
264268
raise ::Unauthorized
265269
end
270+
else
271+
unless @issues.all?(&:attributes_editable?)
272+
raise ::Unauthorized
273+
end
266274
end
267275

268276
unsaved_issues = []
@@ -316,6 +324,7 @@ def bulk_update
316324
end
317325

318326
def destroy
327+
raise Unauthorized unless @issues.all?(&:deletable?)
319328
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
320329
if @hours > 0
321330
case params[:todo]

app/models/issue.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,24 @@ def visible?(usr=nil)
172172
end
173173
end
174174

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

180180
# Returns true if user or current user is allowed to edit the issue
181181
def attributes_editable?(user=User.current)
182-
user.allowed_to?(:edit_issues, project)
182+
user_tracker_permission?(user, :edit_issues)
183+
end
184+
185+
# Returns true if user or current user is allowed to add notes to the issue
186+
def notes_addable?(user=User.current)
187+
user_tracker_permission?(user, :add_issue_notes)
188+
end
189+
190+
# Returns true if user or current user is allowed to delete the issue
191+
def deletable?(user=User.current)
192+
user_tracker_permission?(user, :delete_issues)
183193
end
184194

185195
def initialize(attributes=nil, *args)
@@ -429,10 +439,10 @@ def estimated_hours=(h)
429439
'custom_fields',
430440
'lock_version',
431441
'notes',
432-
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
442+
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user) }
433443

434444
safe_attributes 'notes',
435-
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
445+
:if => lambda {|issue, user| issue.notes_addable?(user)}
436446

437447
safe_attributes 'private_notes',
438448
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
@@ -447,7 +457,7 @@ def estimated_hours=(h)
447457
}
448458

449459
safe_attributes 'parent_issue_id',
450-
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
460+
:if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) &&
451461
user.allowed_to?(:manage_subtasks, issue.project)}
452462

453463
def safe_attribute_names(user=nil)
@@ -1406,6 +1416,11 @@ def self.allowed_target_trackers(project, user=User.current, current_tracker=nil
14061416

14071417
private
14081418

1419+
def user_tracker_permission?(user, permission)
1420+
roles = user.roles_for_project(project).select {|r| r.has_permission?(permission)}
1421+
roles.any? {|r| r.permissions_all_trackers?(permission) || r.permissions_tracker_ids?(permission, tracker_id)}
1422+
end
1423+
14091424
def after_project_change
14101425
# Update project_id on related time entries
14111426
TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])

app/views/issues/_action_menu.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
<%= 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) %>
44
<%= watcher_link(@issue, User.current) %>
55
<%= 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? %>
6-
<%= 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) %>
6+
<%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if @issue.deletable? %>
77
</div>

app/views/issues/_edit.html.erb

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,22 @@
2727
<% end %>
2828
</fieldset>
2929
<% end %>
30-
31-
<fieldset><legend><%= l(:field_notes) %></legend>
32-
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :no_label => true %>
33-
<%= wikitoolbar_for 'issue_notes' %>
34-
35-
<% if @issue.safe_attribute? 'private_notes' %>
36-
<%= f.check_box :private_notes, :no_label => true %> <label for="issue_private_notes"><%= l(:field_private_notes) %></label>
30+
<% if @issue.notes_addable? %>
31+
<fieldset><legend><%= l(:field_notes) %></legend>
32+
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :no_label => true %>
33+
<%= wikitoolbar_for 'issue_notes' %>
34+
35+
<% if @issue.safe_attribute? 'private_notes' %>
36+
<%= f.check_box :private_notes, :no_label => true %> <label for="issue_private_notes"><%= l(:field_private_notes) %></label>
37+
<% end %>
38+
39+
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
40+
</fieldset>
41+
42+
<fieldset><legend><%= l(:label_attachment_plural) %></legend>
43+
<p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
44+
</fieldset>
3745
<% end %>
38-
39-
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
40-
</fieldset>
41-
42-
<fieldset><legend><%= l(:label_attachment_plural) %></legend>
43-
<p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
44-
</fieldset>
4546
</div>
4647

4748
<%= f.hidden_field :lock_version %>

app/views/issues/_history.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<% reply_links = authorize_for('issues', 'edit') -%>
1+
<% reply_links = issue.notes_addable? -%>
22
<% for journal in journals %>
33
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
44
<div id="note-<%= journal.indice %>">

app/views/issues/show.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ end %>
7777
<% if @issue.description? %>
7878
<div class="description">
7979
<div class="contextual">
80-
<%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if authorize_for('issues', 'edit') %>
80+
<%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if @issue.notes_addable? %>
8181
</div>
8282

8383
<p><strong><%=l(:field_description)%></strong></p>

app/views/roles/_form.html.erb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@
6464

6565
<div id="role-permissions-trackers">
6666
<h3><%= l(:label_issue_tracking) %></h3>
67-
<% permissions = %w(view_issues add_issues) %>
67+
<% permissions = %w(view_issues add_issues edit_issues add_issue_notes delete_issues) %>
68+
69+
<div class="autoscroll">
6870
<table class="list">
6971
<thead>
7072
<tr>
@@ -87,7 +89,7 @@
8789
<% end %>
8890
</tr>
8991
<% Tracker.sorted.all.each do |tracker| %>
90-
<tr>
92+
<tr class="<%= cycle("odd", "even") %>">
9193
<td class="name"><%= tracker.name %></td>
9294
<% permissions.each do |permission| %>
9395
<td><%= check_box_tag "role[permissions_tracker_ids][#{permission}][]",
@@ -100,6 +102,7 @@
100102
<% end %>
101103
</tbody>
102104
</table>
105+
</div>
103106

104107
<% permissions.each do |permission| %>
105108
<%= hidden_field_tag "role[permissions_tracker_ids][#{permission}][]", '' %>

public/stylesheets/application.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ table.list td.buttons img, div.buttons img {vertical-align:middle;}
152152
table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
153153
table.list table.progress td {padding-right:0px;}
154154
table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
155+
#role-permissions-trackers table.list th {white-space:normal;}
155156

156157
.table-list-cell {display: table-cell; vertical-align: top; padding:2px; }
157158

test/functional/issues_controller_test.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,6 +3872,30 @@ def test_put_update_should_redirect_with_previous_and_next_issue_ids_params
38723872
assert_redirected_to '/issues/11?issue_count=3&issue_position=2&next_issue_id=12&prev_issue_id=8'
38733873
end
38743874

3875+
def test_update_with_permission_on_tracker_should_be_allowed
3876+
role = Role.find(1)
3877+
role.set_permission_trackers :edit_issues, [1]
3878+
role.save!
3879+
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Original subject')
3880+
3881+
@request.session[:user_id] = 2
3882+
put :update, :id => issue.id, :issue => {:subject => 'Changed subject'}
3883+
assert_response 302
3884+
assert_equal 'Changed subject', issue.reload.subject
3885+
end
3886+
3887+
def test_update_without_permission_on_tracker_should_be_denied
3888+
role = Role.find(1)
3889+
role.set_permission_trackers :edit_issues, [1]
3890+
role.save!
3891+
issue = Issue.generate!(:project_id => 1, :tracker_id => 2, :subject => 'Original subject')
3892+
3893+
@request.session[:user_id] = 2
3894+
put :update, :id => issue.id, :issue => {:subject => 'Changed subject'}
3895+
assert_response 302
3896+
assert_equal 'Original subject', issue.reload.subject
3897+
end
3898+
38753899
def test_get_bulk_edit
38763900
@request.session[:user_id] = 2
38773901
get :bulk_edit, :ids => [1, 3]
@@ -4702,6 +4726,32 @@ def test_destroy_invalid_should_respond_with_404
47024726
assert_response 404
47034727
end
47044728

4729+
def test_destroy_with_permission_on_tracker_should_be_allowed
4730+
role = Role.find(1)
4731+
role.set_permission_trackers :delete_issues, [1]
4732+
role.save!
4733+
issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
4734+
4735+
@request.session[:user_id] = 2
4736+
assert_difference 'Issue.count', -1 do
4737+
delete :destroy, :id => issue.id
4738+
end
4739+
assert_response 302
4740+
end
4741+
4742+
def test_destroy_without_permission_on_tracker_should_be_denied
4743+
role = Role.find(1)
4744+
role.set_permission_trackers :delete_issues, [2]
4745+
role.save!
4746+
issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
4747+
4748+
@request.session[:user_id] = 2
4749+
assert_no_difference 'Issue.count' do
4750+
delete :destroy, :id => issue.id
4751+
end
4752+
assert_response 403
4753+
end
4754+
47054755
def test_default_search_scope
47064756
get :index
47074757

0 commit comments

Comments
 (0)