>
+<% if @issue.safe_attribute_names.include?('is_private') %>
+
+
+
+<% end %>
<%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %>
<%= observe_field :issue_tracker_id, :url => { :action => :new, :project_id => @project, :id => @issue },
:update => :attributes,
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 307984a..82c568f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -305,6 +305,7 @@ en:
field_visible: Visible
field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
field_issues_visibility: Issues visibility
+ field_is_private: Private
setting_app_title: Application title
setting_app_subtitle: Application subtitle
@@ -377,6 +378,8 @@ en:
permission_add_issues: Add issues
permission_edit_issues: Edit issues
permission_manage_issue_relations: Manage issue relations
+ permission_set_issues_private: Set issues public or private
+ permission_set_own_issues_private: Set own issues public or private
permission_add_issue_notes: Add notes
permission_edit_issue_notes: Edit notes
permission_edit_own_issue_notes: Edit own notes
@@ -806,6 +809,7 @@ en:
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
label_issues_visibility_all: All issues
+ label_issues_visibility_public: All non private issues
label_issues_visibility_own: Issues created by or assigned to the user
button_login: Login
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 0b777a7..7d799af 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -309,6 +309,7 @@ fr:
field_visible: Visible
field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé"
field_issues_visibility: Visibilité des demandes
+ field_is_private: Privée
setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application
@@ -378,6 +379,8 @@ fr:
permission_add_issues: Créer des demandes
permission_edit_issues: Modifier les demandes
permission_manage_issue_relations: Gérer les relations
+ permission_set_issues_private: Rendre les demandes publiques ou privées
+ permission_set_own_issues_private: Rendre ses propres demandes publiques ou privées
permission_add_issue_notes: Ajouter des notes
permission_edit_issue_notes: Modifier les notes
permission_edit_own_issue_notes: Modifier ses propres notes
@@ -793,6 +796,7 @@ fr:
label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur
label_issues_visibility_all: Toutes les demandes
+ label_issues_visibility_public: Toutes les demandes non privées
label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
button_login: Connexion
diff --git a/db/migrate/20110412065600_add_issues_is_private.rb b/db/migrate/20110412065600_add_issues_is_private.rb
new file mode 100644
index 0000000..2cc4e4b
--- /dev/null
+++ b/db/migrate/20110412065600_add_issues_is_private.rb
@@ -0,0 +1,9 @@
+class AddIssuesIsPrivate < ActiveRecord::Migration
+ def self.up
+ add_column :issues, :is_private, :boolean, :default => false, :null => false
+ end
+
+ def self.down
+ remove_column :issues, :is_private
+ end
+end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 5ecf2ea..72d6b35 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -71,6 +71,8 @@ Redmine::AccessControl.map do |map|
map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]}
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
map.permission :manage_subtasks, {}
+ map.permission :set_issues_private, {}
+ map.permission :set_own_issues_private, {}
map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
diff --git a/lib/redmine/default_data/loader.rb b/lib/redmine/default_data/loader.rb
index e0e0cc8..ee10142 100644
--- a/lib/redmine/default_data/loader.rb
+++ b/lib/redmine/default_data/loader.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -41,6 +41,7 @@ module Redmine
Role.transaction do
# Roles
manager = Role.create! :name => l(:default_role_manager),
+ :issues_visibility => 'all',
:position => 1
manager.permissions = manager.setable_permissions.collect {|p| p.name}
manager.save!
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 5efa36c..73b83a4 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -278,6 +278,7 @@ div.issue div.subject div div { padding-left: 16px; }
div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
div.issue div.subject>div>p { margin-top: 0.5em; }
div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
+div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;}
#issue_tree table.issues, #relations table.issues { border: 0; }
#issue_tree td.checkbox, #relations td.checkbox {display:none;}
diff --git a/test/fixtures/attachments.yml b/test/fixtures/attachments.yml
index bd4a86a..438e212 100644
--- a/test/fixtures/attachments.yml
+++ b/test/fixtures/attachments.yml
@@ -169,3 +169,16 @@ attachments_014:
filename: changeset_utf8.diff
author_id: 2
content_type: text/x-diff
+attachments_015:
+ id: 15
+ created_on: 2010-07-19 21:07:27 +02:00
+ container_type: Issue
+ container_id: 14
+ downloads: 0
+ disk_filename: 060719210727_changeset_utf8.diff
+ digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+ filesize: 687
+ filename: private.diff
+ author_id: 2
+ content_type: text/x-diff
+ description: attachement of a private issue
diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml
index e138177..b001b48 100644
--- a/test/fixtures/issues.yml
+++ b/test/fixtures/issues.yml
@@ -244,3 +244,21 @@ issues_013:
root_id: 13
lft: 1
rgt: 2
+issues_014:
+ id: 14
+ created_on: <%= 15.days.ago.to_date.to_s(:db) %>
+ project_id: 3
+ updated_on: <%= 15.days.ago.to_date.to_s(:db) %>
+ priority_id: 5
+ subject: Private issue on public project
+ fixed_version_id:
+ category_id:
+ description: This is a private issue
+ tracker_id: 1
+ assigned_to_id:
+ author_id: 2
+ status_id: 1
+ is_private: true
+ root_id: 14
+ lft: 1
+ rgt: 2
diff --git a/test/fixtures/roles.yml b/test/fixtures/roles.yml
index 7491f2f..903df8c 100644
--- a/test/fixtures/roles.yml
+++ b/test/fixtures/roles.yml
@@ -3,7 +3,7 @@ roles_001:
name: Manager
id: 1
builtin: 0
- issues_visibility: default
+ issues_visibility: all
permissions: |
---
- :add_project
diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb
index f7293ff..8354ba5 100644
--- a/test/functional/attachments_controller_test.rb
+++ b/test/functional/attachments_controller_test.rb
@@ -86,6 +86,18 @@ class AttachmentsControllerTest < ActionController::TestCase
assert_equal 'application/octet-stream', @response.content_type
end
+ def test_show_file_from_private_issue_without_permission
+ get :show, :id => 15
+ assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
+ end
+
+ def test_show_file_from_private_issue_with_permission
+ @request.session[:user_id] = 2
+ get :show, :id => 15
+ assert_response :success
+ assert_tag 'h2', :content => /private.diff/
+ end
+
def test_download_text_file
get :download, :id => 4
assert_response :success
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index 31132eb..e9a9163 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -91,6 +91,13 @@ class IssuesControllerTest < ActionController::TestCase
assert_no_tag :tag => 'a', :content => /Can't print recipes/
assert_tag :tag => 'a', :content => /Subproject issue/
end
+
+ def test_index_should_list_visible_issues_only
+ get :index, :per_page => 100
+ assert_response :success
+ assert_not_nil assigns(:issues)
+ assert_nil assigns(:issues).detect {|issue| !issue.visible?}
+ end
def test_index_with_project
Setting.display_subprojects_issues = 0
@@ -317,6 +324,12 @@ class IssuesControllerTest < ActionController::TestCase
assert_response :redirect
end
+ def test_show_should_deny_anonymous_access_to_private_issue
+ Issue.update_all(["is_private = ?", true], "id = 1")
+ get :show, :id => 1
+ assert_response :redirect
+ end
+
def test_show_should_deny_non_member_access_without_permission
Role.non_member.remove_permission!(:view_issues)
@request.session[:user_id] = 9
@@ -324,6 +337,13 @@ class IssuesControllerTest < ActionController::TestCase
assert_response 403
end
+ def test_show_should_deny_non_member_access_to_private_issue
+ Issue.update_all(["is_private = ?", true], "id = 1")
+ @request.session[:user_id] = 9
+ get :show, :id => 1
+ assert_response 403
+ end
+
def test_show_should_deny_member_access_without_permission
Role.find(1).remove_permission!(:view_issues)
@request.session[:user_id] = 2
@@ -331,6 +351,35 @@ class IssuesControllerTest < ActionController::TestCase
assert_response 403
end
+ def test_show_should_deny_member_access_to_private_issue_without_permission
+ Issue.update_all(["is_private = ?", true], "id = 1")
+ @request.session[:user_id] = 3
+ get :show, :id => 1
+ assert_response 403
+ end
+
+ def test_show_should_allow_author_access_to_private_issue
+ Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
+ @request.session[:user_id] = 3
+ get :show, :id => 1
+ assert_response :success
+ end
+
+ def test_show_should_allow_assignee_access_to_private_issue
+ Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
+ @request.session[:user_id] = 3
+ get :show, :id => 1
+ assert_response :success
+ end
+
+ def test_show_should_allow_member_access_to_private_issue_with_permission
+ Issue.update_all(["is_private = ?", true], "id = 1")
+ User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
+ @request.session[:user_id] = 3
+ get :show, :id => 1
+ assert_response :success
+ end
+
def test_show_should_not_disclose_relations_to_invisible_issues
Setting.cross_project_issue_relations = '1'
IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb
index ff98711..25f664a 100644
--- a/test/unit/issue_test.rb
+++ b/test/unit/issue_test.rb
@@ -74,6 +74,7 @@ class IssueTest < ActiveSupport::TestCase
issues = Issue.visible(User.anonymous).all
assert issues.any?
assert_nil issues.detect {|issue| !issue.project.is_public?}
+ assert_nil issues.detect {|issue| issue.is_private?}
assert_visibility_match User.anonymous, issues
end
@@ -102,6 +103,7 @@ class IssueTest < ActiveSupport::TestCase
issues = Issue.visible(user).all
assert issues.any?
assert_nil issues.detect {|issue| !issue.project.is_public?}
+ assert_nil issues.detect {|issue| issue.is_private?}
assert_visibility_match user, issues
end
@@ -130,10 +132,11 @@ class IssueTest < ActiveSupport::TestCase
user = User.find(9)
# User should see issues of projects for which he has view_issues permissions only
Role.non_member.remove_permission!(:view_issues)
- Member.create!(:principal => user, :project_id => 2, :role_ids => [1])
+ Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
issues = Issue.visible(user).all
assert issues.any?
- assert_nil issues.detect {|issue| issue.project_id != 2}
+ assert_nil issues.detect {|issue| issue.project_id != 3}
+ assert_nil issues.detect {|issue| issue.is_private?}
assert_visibility_match user, issues
end
@@ -145,6 +148,8 @@ class IssueTest < ActiveSupport::TestCase
assert issues.any?
# Admin should see issues on private projects that he does not belong to
assert issues.detect {|issue| !issue.project.is_public?}
+ # Admin should see private issues of other users
+ assert issues.detect {|issue| issue.is_private? && issue.author != user}
assert_visibility_match user, issues
end
diff --git a/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb b/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb
index e5acdc4..d59c891 100644
--- a/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb
+++ b/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
-# Copyright (C) 2006-2008 Jean-Philippe Lang
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -44,11 +44,13 @@ module Redmine
end
def attachments_visible?(user=User.current)
- user.allowed_to?(self.class.attachable_options[:view_permission], self.project)
+ (respond_to?(:visible?) ? visible?(user) : true) &&
+ user.allowed_to?(self.class.attachable_options[:view_permission], self.project)
end
def attachments_deletable?(user=User.current)
- user.allowed_to?(self.class.attachable_options[:delete_permission], self.project)
+ (respond_to?(:visible?) ? visible?(user) : true) &&
+ user.allowed_to?(self.class.attachable_options[:delete_permission], self.project)
end
def initialize_unsaved_attachments