Project

General

Profile

Patch #24623 » 0001-Implements-permissions-and-restrictions-to-issue-att-6.0.patch

For Redmine 6.0-stable - Frederico Camara, 2024-12-07 21:48

View differences:

app/controllers/issues_controller.rb
100 100
    if !api_request? || include_in_api_response?('relations')
101 101
      @relations = @issue.relations.select {|r| r.other_issue(@issue)&.visible?}
102 102
    end
103
    @attachments = @issue.attachments_visible? ? @issue.attachments : []
103 104
    if !api_request? || include_in_api_response?('allowed_statuses')
104 105
      @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
105 106
    end
......
149 150
    end
150 151

  
151 152
    call_hook(:controller_issues_new_before_save, {:params => params, :issue => @issue})
152
    @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
153
    if @issue.attachments_addable?
154
      @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
155
    end
153 156
    if @issue.save
154 157
      call_hook(:controller_issues_new_after_save, {:params => params, :issue => @issue})
155 158
      respond_to do |format|
......
184 187
  def edit
185 188
    return unless update_issue_from_params
186 189

  
190
    @attachments = @issue.attachments_visible?(User.current) ? @issue.attachments : []
187 191
    respond_to do |format|
188 192
      format.html {}
189 193
      format.js
......
328 332
    @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
329 333
    @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
330 334
    if @copy
331
      @attachments_present = @issues.detect {|i| i.attachments.any?}.present? &&
335
      @attachments_present = @issues.detect {|i| i.attachments.any? && i.attachments_visible?}.present? &&
332 336
                               (Setting.copy_attachments_on_issue_copy == 'ask')
333 337
      @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
334 338
      @watchers_present = User.current.allowed_to?(:add_issue_watchers, @projects) &&
......
396 400
      end
397 401
      journal = issue.init_journal(User.current, params[:notes])
398 402
      issue.safe_attributes = attributes
403
      issue.attachments = [] unless issue.attachments_addable? if @copy
399 404
      call_hook(:controller_issues_bulk_edit_before_save, {:params => params, :issue => issue})
400 405
      if issue.save
401 406
        saved_issues << issue
......
647 652

  
648 653
    @priorities = IssuePriority.active
649 654
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
655
    @issue.attachments = [] unless @issue.attachments_addable?
650 656
  end
651 657

  
652 658
  # Saves @issue and a time_entry from the parameters
app/models/issue.rb
42 42
  has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
43 43
  has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
44 44

  
45
  acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
45
  acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed,
46
                     :view_permission => :view_attachments, :edit_permission => :edit_attachments,
47
                     :delete_permission => :delete_attachments
48

  
46 49
  acts_as_customizable
47 50
  acts_as_watchable
48 51
  acts_as_searchable :columns => ['subject', "#{table_name}.description"],
......
200 203
    )
201 204
  end
202 205

  
206
  # Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_visible?
207
  def attachments_visible?(user=User.current)
208
    user_tracker_permission?(user, :view_attachments)
209
  end
210

  
211
  # Returns true if user or current user is allowed to add the attachment to the issue
203 212
  def attachments_addable?(user=User.current)
204
    attributes_editable?(user) || notes_addable?(user)
213
    user_tracker_permission?(user, :add_attachments)
205 214
  end
206 215

  
207 216
  # Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_editable?
208 217
  def attachments_editable?(user=User.current)
209
    attributes_editable?(user)
218
    user_tracker_permission?(user, :edit_attachments)
210 219
  end
211 220

  
212 221
  # Returns true if user or current user is allowed to add notes to the issue
......
221 230

  
222 231
  # Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_deletable?
223 232
  def attachments_deletable?(user=User.current)
224
    attributes_editable?(user)
233
    user_tracker_permission?(user, :delete_attachments)
225 234
  end
226 235

  
227 236
  def initialize(attributes=nil, *args)
......
309 318
      self.status = issue.status
310 319
    end
311 320
    self.author = User.current
312
    unless options[:attachments] == false
321
    if options[:attachments] == true && issue.attachments_visible?
313 322
      self.attachments = issue.attachments.map do |attachement|
314 323
        attachement.copy(:container => self)
315 324
      end
......
1801 1810
                 child.assigned_to.status == User::STATUS_ACTIVE
1802 1811
          copy.assigned_to = nil
1803 1812
        end
1813
        copy.attachments = [] unless copy.attachments_addable?
1804 1814
        unless copy.save
1805 1815
          if logger
1806 1816
            logger.error(
app/models/journal.rb
111 111
        detail.custom_field && detail.custom_field.visible_by?(project, user)
112 112
      elsif detail.property == 'relation'
113 113
        Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
114
      elsif detail.property == 'attachment'
115
        self.issue.attachments_visible?
114 116
      else
115 117
        true
116 118
      end
app/models/mailer.rb
110 110
  end
111 111

  
112 112
  # Builds a mail for notifying user about an issue update
113
  def issue_edit(user, journal)
113
  def issue_edit(user, journal, att=false)
114 114
    issue = journal.journalized
115 115
    redmine_headers 'Project' => issue.project.identifier,
116 116
                    'Issue-Tracker' => issue.tracker.name,
......
129 129
    @journal = journal
130 130
    @journal_details = journal.visible_details
131 131
    @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
132
    @att = att
132 133

  
133 134
    mail :to => user,
134 135
      :subject => s
......
144 145
      journal.notes? || journal.visible_details(user).any?
145 146
    end
146 147
    users.each do |user|
147
      issue_edit(user, journal).deliver_later
148
      issue_edit(user, journal, journal.issue.attachments_visible?(user)).deliver_later
148 149
    end
149 150
  end
150 151

  
app/views/issues/_edit.html.erb
47 47
      <%= update_data_sources_for_auto_complete({users: watchers_autocomplete_for_mention_path(project_id: @issue.project, q: '', object_type: 'issue',
48 48
                                                                                               object_id: @issue.id)}) %>
49 49
    <% end %>
50
    <% if @issue.attachments_addable? %>
51
      <fieldset id="add_attachments"><legend><%= l(:label_attachment_plural) %></legend>
52
        <% if @issue.attachments.any? && @issue.safe_attribute?('deleted_attachment_ids') %>
50
      <fieldset id="add_attachments" style="<%= "display: none;" unless @issue.attachments_addable?(User.current) %>"><legend><%= l(:label_attachment_plural) %></legend>
51
        <% if @attachments.any? && @issue.safe_attribute?('deleted_attachment_ids') %>
53 52
        <div class="contextual"><%= link_to l(:label_edit_attachments), '#', :onclick => "$('#existing-attachments').toggle(); return false;" %></div>
54 53
        <div id="existing-attachments" style="<%= @issue.deleted_attachment_ids.blank? ? 'display:none;' : '' %>">
55
          <% @issue.attachments.each do |attachment| %>
54
          <% @attachments.each do |attachment| %>
56 55
          <span class="existing-attachment">
57 56
            <%= sprite_icon('attachment', size: 12) %>
58 57
            <%= text_field_tag '', attachment.filename, :class => "icon icon-attachment filename", :disabled => true %>
......
72 71
          <%= render :partial => 'attachments/form', :locals => {:container => @issue} %>
73 72
        </div>
74 73
      </fieldset>
75
    <% end %>
76 74
    </div>
77 75

  
78 76
    <%= f.hidden_field :lock_version %>
app/views/issues/edit.js.erb
7 7

  
8 8
<% if @issue.notes_addable? %>
9 9
  $('#add_notes').show();
10
  $('#add_attachments').show();
11 10
<% else %>
12 11
  $('#add_notes').hide();
12
<% end %>
13

  
14
<% if @issue.attachments_addable? %>
15
  $('#add_attachments').show();
16
  $('#attachments_form').show();
17
<% else %>
13 18
  $('#add_attachments').hide();
19
  $('#attachments_form').hide();
14 20
<% end %>
app/views/issues/index.api.rsb
31 31
      api.updated_on issue.updated_on
32 32
      api.closed_on  issue.closed_on
33 33

  
34
      api.array :attachments do
35
        issue.attachments.each do |attachment|
36
          render_api_attachment(attachment, api)
37
        end
38
      end if include_in_api_response?('attachments')
34
      if issue.attachments_visible?
35
        api.array :attachments do
36
          issue.attachments.each do |attachment|
37
            render_api_attachment(attachment, api)
38
          end
39
        end if include_in_api_response?('attachments')
40
      end
39 41

  
40 42
      api.array :relations do
41 43
        issue.relations.each do |relation|
app/views/issues/new.html.erb
17 17
      <%= check_box_tag 'link_copy', '1', @link_copy %>
18 18
    </p>
19 19
    <% end %>
20
    <% if @copy_from && Setting.copy_attachments_on_issue_copy == 'ask' && @copy_from.attachments.any? %>
20
    <% if @copy_from && Setting.copy_attachments_on_issue_copy == 'ask' && @copy_from.attachments.any? &&
21
          @copy_from.attachments_visible? && @issue.attachments_addable? %>
21 22
    <p>
22 23
      <label for="copy_attachments"><%= l(:label_copy_attachments) %></label>
23 24
      <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
......
30 31
    </p>
31 32
    <% end %>
32 33

  
33
    <p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
34
    <p id="attachments_form" style="<%= "display: none;" unless @issue.attachments_addable?(User.current) %>"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
34 35

  
35 36
    <div id="watchers_form_container">
36 37
      <%= render :partial => 'issues/watchers_form' %>
app/views/issues/new.js.erb
8 8
    '<%= escape_javascript(
9 9
           @issue.category.try(:assigned_to).try(:name)).presence || '&nbsp;'.html_safe %>');
10 10
<% end %>
11
<% if @issue.attachments_addable? %>
12
  <% if @copy_from && @copy_from.attachments_visible? %>
13
    $('#copy_attachments').parent().show();
14
  <% else %>
15
    $('#copy_attachments').parent().hide();
16
  <% end %>
17
  $('#attachments_form').show();
18
<% else %>
19
  $('#copy_attachments').parent().hide();
20
  $('#attachments_form').hide();
21
<% end %>
app/views/issues/show.api.rsb
32 32
  render_api_issue_children(@issue, api) if include_in_api_response?('children')
33 33

  
34 34
  api.array :attachments do
35
    @issue.attachments.each do |attachment|
35
    @attachments.each do |attachment|
36 36
      render_api_attachment(attachment, api)
37 37
    end
38 38
  end if include_in_api_response?('attachments')
app/views/issues/show.html.erb
93 93

  
94 94
  <p><strong><%=l(:field_description)%></strong></p>
95 95
  <div id="issue_description_wiki" class="wiki">
96
  <%= textilizable @issue, :description, :attachments => @issue.attachments %>
96
  <%= textilizable @issue, :description, :attachments => @attachments %>
97 97
  </div>
98 98
</div>
99 99
<% end %>
100
<% if @issue.attachments.any? %>
100
<% if @attachments.any? %>
101 101
  <hr />
102 102
  <p><strong><%=l(:label_attachment_plural)%></strong></p>
103 103
  <%= link_to_attachments @issue, :thumbnails => true %>
app/views/mailer/_issue.html.erb
7 7

  
8 8
<%= textilizable(issue, :description, :only_path => false) %>
9 9

  
10
<% if issue.attachments.any? %>
10
<% if issue.attachments.any? && @att %>
11 11
  <fieldset class="attachments"><legend><%= l(:label_attachment_plural) %></legend>
12 12
  <% issue.attachments.each do |attachment| %>
13 13
    <%= link_to_attachment attachment, :download => true, :only_path => false %>
app/views/mailer/_issue.text.erb
5 5
----------------------------------------
6 6
<%= issue.description %>
7 7

  
8
<% if issue.attachments.any? -%>
8
<% if issue.attachments.any? && @att -%>
9 9
---<%= l(:label_attachment_plural).ljust(37, '-') %>
10 10
<% issue.attachments.each do |attachment| -%>
11 11
<%= attachment.filename %> (<%= number_to_human_size(attachment.filesize) %>)
app/views/roles/_form.html.erb
72 72

  
73 73
<div id="role-permissions-trackers" class="view_issues_shown">
74 74
<h3><%= l(:label_issue_tracking) %></h3>
75
<% permissions = [:view_issues, :add_issues, :edit_issues, :add_issue_notes, :delete_issues] & setable_permissions.collect(&:name) %>
75
<% permissions = [:view_issues, :add_issues, :edit_issues, :add_issue_notes, :delete_issues, :view_attachments, :add_attachments, :edit_attachments, :delete_attachments] & setable_permissions.collect(&:name) %>
76 76

  
77 77
<div class="autoscroll">
78 78
<table class="list">
config/locales/en.yml
548 548
  permission_view_private_notes: View private notes
549 549
  permission_set_notes_private: Set notes as private
550 550
  permission_delete_issues: Delete issues
551
  permission_view_attachments: View attachments
552
  permission_add_attachments: Add attachments
553
  permission_edit_attachments: Edit attachments
554
  permission_delete_attachments: Delete attachments
551 555
  permission_manage_public_queries: Manage public queries
552 556
  permission_save_queries: Save queries
553 557
  permission_view_gantt: View gantt chart
config/locales/pt-BR.yml
777 777
  permission_manage_members: Gerenciar membros
778 778
  permission_edit_messages: Editar mensagens
779 779
  permission_delete_issues: Excluir tarefas
780
  permission_view_attachments: Ver arquivos anexos
781
  permission_add_attachments: Adicionar arquivos anexos
782
  permission_edit_attachments: Editar arquivos anexos
783
  permission_delete_attachments: Apagar arquivos anexos
780 784
  permission_view_issue_watchers: Ver lista de observadores
781 785
  permission_manage_repository: Gerenciar repositório
782 786
  permission_commit_access: Acesso do commit
db/migrate/20241207140210_add_attachments_permissions.rb
1
class AddAttachmentsPermissions < ActiveRecord::Migration[4.2]
2
  def self.up
3
    Role.all.each do |r|
4
      r.add_permission!(:view_attachments) if r.has_permission?(:view_issues)
5
      r.add_permission!(:add_attachments) if r.has_permission?(:add_issues)
6
      r.add_permission!(:edit_attachments) if r.has_permission?(:edit_issues)
7
      r.add_permission!(:delete_attachments) if r.has_permission?(:delete_issues)
8
    end
9
  end
10

  
11
  def self.down
12
    Role.all.each do |r|
13
      r.remove_permission!(:view_attachments)
14
      r.remove_permission!(:add_attachments)
15
      r.remove_permission!(:edit_attachments)
16
      r.remove_permission!(:delete_attachments)
17
    end
18
  end
19
end
lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb
136 136
              r |= fetch_ranks_and_ids(
137 137
                search_scope(user, projects, options).
138 138
                joins(:attachments).
139
                where("#{Project.allowed_to_condition(user, :view_attachments)}", false).
139 140
                where(search_tokens_condition(["#{Attachment.table_name}.filename", "#{Attachment.table_name}.description"], tokens, options[:all_words])),
140 141
                options[:limit]
141 142
              )
lib/redmine/export/pdf/issues_pdf_helper.rb
234 234
            end
235 235
          end
236 236

  
237
          if issue.attachments.any?
237
          if issue.attachments.any? && issue.attachments_visible?
238 238
            pdf.SetFontStyle('B', 9)
239 239
            pdf.RDMCell(190, 5, l(:label_attachment_plural), "B")
240 240
            pdf.ln
lib/redmine/preparation.rb
59 59
                                        :queries => :index,
60 60
                                        :reports => [:issue_report, :issue_report_details]},
61 61
                         :read => true
62
          map.permission :add_issues, {:issues => [:new, :create], :attachments => :upload}
63
          map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
64
          map.permission :edit_own_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
65
          map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update], :attachments => :upload}
62
          map.permission :add_issues, {:issues => [:new, :create]}
63
          map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new]}
64
          map.permission :edit_own_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new]}
65
          map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update]}
66 66
          map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
67 67
          map.permission :manage_subtasks, {}
68 68
          map.permission :set_issues_private, {}
69 69
          map.permission :set_own_issues_private, {}, :require => :loggedin
70
          map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
70
          map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
71 71
          map.permission :edit_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
72 72
          map.permission :edit_own_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
73 73
          map.permission :view_private_notes, {}, :read => true, :require => :member
74 74
          map.permission :set_notes_private, {}, :require => :member
75 75
          map.permission :delete_issues, {:issues => :destroy}, :require => :member
76
          # Attachments
77
          map.permission :add_attachments, {:attachments => :upload}
78
          map.permission :view_attachments, {}
79
          map.permission :edit_attachments, {}
80
          map.permission :delete_attachments, {:attachments => :destroy}, :require => :member
76 81
          # Watchers
77 82
          map.permission :view_issue_watchers, {}, :read => true
78 83
          map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user, :autocomplete_for_mention]}
(19-19/19)