Commit f171169c authored by jplang's avatar jplang

Adds a 'Add subprojects' permission.

* 'Add project' permission will let user create a root project
* 'Add subprojects' permission will let project members create subprojects

git-svn-id: https://svn.redmine.org/redmine/trunk@3238 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 3424025b
......@@ -73,7 +73,7 @@ class ProjectsController < ApplicationController
@project.enabled_module_names = Setting.default_projects_modules
else
@project.enabled_module_names = params[:enabled_modules]
if @project.save
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if he is not admin
unless User.current.admin?
......@@ -104,7 +104,7 @@ class ProjectsController < ApplicationController
else
@project = Project.new(params[:project])
@project.enabled_module_names = params[:enabled_modules]
if @project.copy(@source_project, :only => params[:only])
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
......@@ -156,7 +156,7 @@ class ProjectsController < ApplicationController
def edit
if request.post?
@project.attributes = params[:project]
if @project.save
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
......@@ -395,4 +395,19 @@ private
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end
end
# Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current
def validate_parent_id
return true if User.current.admin?
parent_id = params[:project] && params[:project][:parent_id]
if parent_id || @project.new_record?
parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
unless @project.allowed_parents.include?(parent)
@project.errors.add :parent_id, :invalid
return false
end
end
true
end
end
......@@ -36,7 +36,16 @@ module ProjectsHelper
end
def parent_project_select_tag(project)
options = '<option></option>' + project_tree_options_for_select(project.allowed_parents, :selected => project.parent)
selected = project.parent
# retrieve the requested parent project
parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
if parent_id
selected = (parent_id.blank? ? nil : Project.find(parent_id))
end
options = ''
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options, :name => 'project[parent_id]')
end
......
......@@ -246,7 +246,11 @@ class Project < ActiveRecord::Base
# by the current user
def allowed_parents
return @allowed_parents if @allowed_parents
@allowed_parents = (Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_project, :member => true)) - self_and_descendants)
@allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
@allowed_parents = @allowed_parents - self_and_descendants
if User.current.allowed_to?(:add_project, nil, :global => true)
@allowed_parents << nil
end
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
@allowed_parents << parent
end
......
......@@ -4,7 +4,7 @@
<!--[form:project]-->
<p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
<% unless @project.allowed_parents.empty? %>
<% unless @project.allowed_parents.compact.empty? %>
<p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
<% end %>
......
<div class="contextual">
<% if User.current.allowed_to?(:add_subprojects, @project) %>
<%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => 'icon icon-add' %>
<% end %>
</div>
<h2><%=l(:label_overview)%></h2>
<div class="splitcontentleft">
......
......@@ -867,3 +867,5 @@ bg:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -891,3 +891,5 @@ bs:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -870,3 +870,5 @@ ca:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -873,3 +873,5 @@ cs:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -893,3 +893,5 @@ da:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -893,3 +893,5 @@ de:
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -873,3 +873,5 @@ el:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -328,6 +328,7 @@ en:
setting_rest_api_enabled: Enable REST web service
permission_add_project: Create project
permission_add_subprojects: Create subprojects
permission_edit_project: Edit project
permission_select_project_modules: Select project modules
permission_manage_members: Manage members
......@@ -463,6 +464,7 @@ en:
label_auth_source_new: New authentication mode
label_auth_source_plural: Authentication modes
label_subproject_plural: Subprojects
label_subproject_new: New subproject
label_and_its_subprojects: "{{value}} and its subprojects"
label_min_max_length: Min - Max length
label_list: List
......
......@@ -917,3 +917,5 @@ es:
button_show: Mostrar
text_line_separated: Múltiples valores permitidos (un valor en cada línea).
setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -903,3 +903,5 @@ fi:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -348,6 +348,7 @@ fr:
setting_rest_api_enabled: Activer l'API REST
permission_add_project: Créer un projet
permission_add_subprojects: Créer des sous-projets
permission_edit_project: Modifier le projet
permission_select_project_modules: Choisir les modules
permission_manage_members: Gérer les members
......@@ -483,6 +484,7 @@ fr:
label_auth_source_new: Nouveau mode d'authentification
label_auth_source_plural: Modes d'authentification
label_subproject_plural: Sous-projets
label_subproject_new: Nouveau sous-projet
label_and_its_subprojects: "{{value}} et ses sous-projets"
label_min_max_length: Longueurs mini - maxi
label_list: Liste
......
......@@ -893,3 +893,5 @@ gl:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -877,3 +877,5 @@ he:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -898,3 +898,5 @@
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -885,3 +885,5 @@ id:
error_workflow_copy_source: Please select a source tracker or role
setting_start_of_week: Start calendars on
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -880,3 +880,5 @@ it:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -902,3 +902,5 @@ ja:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -933,3 +933,5 @@ ko:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -941,3 +941,5 @@ lt:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -855,3 +855,5 @@ nl:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -868,3 +868,5 @@
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -896,3 +896,5 @@ pl:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -900,3 +900,5 @@ pt-BR:
label_missing_feeds_access_key: Chave de acesso ao RSS faltando
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -885,3 +885,5 @@ pt:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -870,3 +870,5 @@ ro:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -981,3 +981,5 @@ ru:
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -872,3 +872,5 @@ sk:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -869,3 +869,5 @@ sl:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -888,3 +888,5 @@
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -923,3 +923,5 @@ sv:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -870,3 +870,5 @@ th:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -900,3 +900,5 @@ tr:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -869,3 +869,5 @@ uk:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -932,3 +932,5 @@ vi:
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -966,3 +966,5 @@
enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -897,3 +897,5 @@ zh:
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
......@@ -32,6 +32,7 @@ Redmine::AccessControl.map do |map|
map.permission :select_project_modules, {:projects => :modules}, :require => :member
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
map.permission :add_subprojects, {:projects => :add}, :require => :member
map.project_module :issue_tracking do |map|
# Issue categories
......
......@@ -89,71 +89,163 @@ class ProjectsControllerTest < ActionController::TestCase
)
end
def test_get_add
@request.session[:user_id] = 1
get :add
assert_response :success
assert_template 'add'
end
def test_get_add_by_non_admin
@request.session[:user_id] = 2
get :add
assert_response :success
assert_template 'add'
end
def test_post_add
@request.session[:user_id] = 1
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' }
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal 'weblog', project.description
assert_equal true, project.is_public?
assert_nil project.parent
end
def test_post_add_subproject
@request.session[:user_id] = 1
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' },
:parent_id => 1
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal Project.find(1), project.parent
end
def test_post_add_by_non_admin
@request.session[:user_id] = 2
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' }
}
assert_redirected_to '/projects/blog/settings'
context "#add" do
context "by admin user" do
setup do
@request.session[:user_id] = 1
end
should "accept get" do
get :add
assert_response :success
assert_template 'add'
end
should "accept post" do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' }
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal 'weblog', project.description
assert_equal true, project.is_public?
assert_nil project.parent
end
should "accept post with parent" do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' },
:parent_id => 1
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal Project.find(1), project.parent
end
end
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal 'weblog', project.description
assert_equal true, project.is_public?
context "by non-admin user with add_project permission" do
setup do
Role.non_member.add_permission! :add_project
@request.session[:user_id] = 9
end
should "accept get" do
get :add
assert_response :success
assert_template 'add'
assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}
end
should "accept post" do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' }
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
assert_kind_of Project, project
assert_equal 'weblog', project.description
assert_equal true, project.is_public?
# User should be added as a project member
assert User.find(9).member_of?(project)
assert_equal 1, project.members.size
end
should "fail with parent_id" do
assert_no_difference 'Project.count' do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' },
:parent_id => 1
}
end
assert_response :success
project = assigns(:project)
assert_kind_of Project, project
assert_not_nil project.errors.on(:parent_id)
end
end
# User should be added as a project member
assert User.find(2).member_of?(project)
assert_equal 1, project.members.size
context "by non-admin user with add_subprojects permission" do
setup do
Role.find(1).remove_permission! :add_project
Role.find(1).add_permission! :add_subprojects
@request.session[:user_id] = 2
end
should "accept get" do
get :add, :parent_id => 'ecookbook'
assert_response :success
assert_template 'add'
# parent project selected
assert_tag :select, :attributes => {:name => 'project[parent_id]'},
:child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
# no empty value
assert_no_tag :select, :attributes => {:name => 'project[parent_id]'},
:child => {:tag => 'option', :attributes => {:value => ''}}
end
should "accept post with parent_id" do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' },
:parent_id => 1
}
assert_redirected_to '/projects/blog/settings'
project = Project.find_by_name('blog')
end
should "fail without parent_id" do
assert_no_difference 'Project.count' do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' }
}
end
assert_response :success
project = assigns(:project)
assert_kind_of Project, project
assert_not_nil project.errors.on(:parent_id)
end
should "fail with unauthorized parent_id" do
assert !User.find(2).member_of?(Project.find(6))
assert_no_difference 'Project.count' do
post :add, :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
:custom_field_values => { '3' => 'Beta' },
:parent_id => 6
}
end
assert_response :success
project = assigns(:project)
assert_kind_of Project, project
assert_not_nil project.errors.on(:parent_id)
end
end
end
def test_show_routing
......
......@@ -282,7 +282,7 @@ class ProjectTest < ActiveSupport::TestCase
user = User.find(9)
assert user.memberships.empty?
User.current = user
assert Project.new.allowed_parents.empty?
assert Project.new.allowed_parents.compact.empty?
end
def test_users_by_role
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment