summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2014-09-28 14:51:08 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2014-09-28 14:51:08 +0000
commit7e7ac5340a281ed767066af0b5f4dd45a3d7076f (patch)
tree891640b0548c0d3063daddb219006d120fa312c3 /app
parent9a7fb0ad7be0ee3403f5b89eb0c16b68c991d519 (diff)
downloadredmine-7e7ac5340a281ed767066af0b5f4dd45a3d7076f.tar.gz
redmine-7e7ac5340a281ed767066af0b5f4dd45a3d7076f.zip
Adds buit-in groups to give specific permissions to anonymous and non members users per project (#17976).
git-svn-id: http://svn.redmine.org/redmine/trunk@13417 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r--app/controllers/groups_controller.rb8
-rw-r--r--app/helpers/groups_helper.rb11
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/models/group.rb35
-rw-r--r--app/models/group_anonymous.rb30
-rw-r--r--app/models/group_builtin.rb56
-rw-r--r--app/models/group_non_member.rb30
-rw-r--r--app/models/issue_query.rb7
-rw-r--r--app/models/principal.rb3
-rw-r--r--app/models/project.rb26
-rw-r--r--app/models/user.rb34
-rw-r--r--app/views/groups/_form.html.erb4
-rw-r--r--app/views/groups/_general.html.erb2
-rw-r--r--app/views/groups/edit.html.erb2
-rw-r--r--app/views/groups/index.api.rsb1
-rw-r--r--app/views/groups/index.html.erb6
-rw-r--r--app/views/groups/show.api.rsb3
-rw-r--r--app/views/users/_groups.html.erb2
18 files changed, 219 insertions, 43 deletions
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 0cd4055e5..555fc61df 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -25,12 +25,16 @@ class GroupsController < ApplicationController
helper :custom_fields
def index
- @groups = Group.sorted.all
respond_to do |format|
format.html {
+ @groups = Group.sorted.all
@user_count_by_group_id = user_count_by_group_id
}
- format.api
+ format.api {
+ scope = Group.sorted
+ scope = scope.givable unless params[:builtin] == '1'
+ @groups = scope.all
+ }
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 5629aea46..4777328b0 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -18,11 +18,12 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GroupsHelper
- def group_settings_tabs
- tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
- {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
- {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
- ]
+ def group_settings_tabs(group)
+ tabs = []
+ tabs << {:name => 'general', :partial => 'groups/general', :label => :label_general}
+ tabs << {:name => 'users', :partial => 'groups/users', :label => :label_user_plural} if group.givable?
+ tabs << {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
+ tabs
end
def render_principals_for_new_group_users(group)
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index edc2e341d..415b5fc09 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -46,7 +46,7 @@ module UsersHelper
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
]
- if Group.all.any?
+ if Group.givable.any?
tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
end
tabs
diff --git a/app/models/group.rb b/app/models/group.rb
index 9824bd998..7b82d1c1f 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -31,17 +31,18 @@ class Group < Principal
before_destroy :remove_references_before_destroy
- scope :sorted, lambda { order("#{table_name}.lastname ASC") }
+ scope :sorted, lambda { order("#{table_name}.type, #{table_name}.lastname ASC") }
scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)}
+ scope :givable, lambda {where(:type => 'Group')}
safe_attributes 'name',
'user_ids',
'custom_field_values',
'custom_fields',
- :if => lambda {|group, user| user.admin?}
+ :if => lambda {|group, user| user.admin? && !group.builtin?}
def to_s
- lastname.to_s
+ name.to_s
end
def name
@@ -52,6 +53,20 @@ class Group < Principal
self.lastname = arg
end
+ def builtin_type
+ nil
+ end
+
+ # Return true if the group is a builtin group
+ def builtin?
+ false
+ end
+
+ # Returns true if the group can be given to a user
+ def givable?
+ !builtin?
+ end
+
def user_added(user)
members.each do |member|
next if member.project.nil?
@@ -80,6 +95,18 @@ class Group < Principal
super(attr_name, *args)
end
+ def self.builtin_id(arg)
+ (arg.anonymous? ? GroupAnonymous : GroupNonMember).instance_id
+ end
+
+ def self.anonymous
+ GroupAnonymous.load_instance
+ end
+
+ def self.non_member
+ GroupNonMember.load_instance
+ end
+
private
# Removes references that are not handled by associations
@@ -89,3 +116,5 @@ class Group < Principal
Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
end
end
+
+require_dependency "group_builtin"
diff --git a/app/models/group_anonymous.rb b/app/models/group_anonymous.rb
new file mode 100644
index 000000000..c3c821bb2
--- /dev/null
+++ b/app/models/group_anonymous.rb
@@ -0,0 +1,30 @@
+# Redmine - project management software
+# Copyright (C) 2006-2014 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class GroupAnonymous < GroupBuiltin
+ def name
+ l(:label_group_anonymous)
+ end
+
+ def builtin_type
+ "anonymous"
+ end
+
+ def self.instance_id
+ @@instance_id ||= load_instance.id
+ end
+end
diff --git a/app/models/group_builtin.rb b/app/models/group_builtin.rb
new file mode 100644
index 000000000..71ecc06ab
--- /dev/null
+++ b/app/models/group_builtin.rb
@@ -0,0 +1,56 @@
+# Redmine - project management software
+# Copyright (C) 2006-2014 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class GroupBuiltin < Group
+ validate :validate_uniqueness, :on => :create
+
+ def validate_uniqueness
+ errors.add :base, 'The builtin group already exists.' if self.class.exists?
+ end
+
+ def builtin?
+ true
+ end
+
+ def destroy
+ false
+ end
+
+ def user_added(user)
+ raise 'Cannot add users to a builtin group'
+ end
+
+ class << self
+ def load_instance
+ return nil if self == GroupBuiltin
+ instance = first(:order => 'id') || create_instance
+ end
+
+ def create_instance
+ raise 'The builtin group already exists.' if exists?
+ instance = new
+ instance.lastname = name
+ instance.save :validate => false
+ raise 'Unable to create builtin group.' if instance.new_record?
+ instance
+ end
+ private :create_instance
+ end
+end
+
+require_dependency "group_anonymous"
+require_dependency "group_non_member"
diff --git a/app/models/group_non_member.rb b/app/models/group_non_member.rb
new file mode 100644
index 000000000..8b3dfd4aa
--- /dev/null
+++ b/app/models/group_non_member.rb
@@ -0,0 +1,30 @@
+# Redmine - project management software
+# Copyright (C) 2006-2014 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class GroupNonMember < GroupBuiltin
+ def name
+ l(:label_group_non_member)
+ end
+
+ def builtin_type
+ "non_member"
+ end
+
+ def self.instance_id
+ @@instance_id ||= load_instance.id
+ end
+end
diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb
index bf90f56d1..c9115100c 100644
--- a/app/models/issue_query.rb
+++ b/app/models/issue_query.rb
@@ -147,6 +147,7 @@ class IssueQuery < Query
end
principals.uniq!
principals.sort!
+ principals.reject! {|p| p.is_a?(GroupBuiltin)}
users = principals.select {|p| p.is_a?(User)}
add_available_filter "status_id",
@@ -183,7 +184,7 @@ class IssueQuery < Query
:type => :list_optional, :values => assigned_to_values
) unless assigned_to_values.empty?
- group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
+ group_values = Group.givable.collect {|g| [g.name, g.id.to_s] }
add_available_filter("member_of_group",
:type => :list_optional, :values => group_values
) unless group_values.empty?
@@ -404,10 +405,10 @@ class IssueQuery < Query
def sql_for_member_of_group_field(field, operator, value)
if operator == '*' # Any group
- groups = Group.all
+ groups = Group.givable
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*"
- groups = Group.all
+ groups = Group.givable
operator = '!' # Override the operator since we want to find by assigned_to
else
groups = Group.where(:id => value).all
diff --git a/app/models/principal.rb b/app/models/principal.rb
index 451349df2..1bbbbe88e 100644
--- a/app/models/principal.rb
+++ b/app/models/principal.rb
@@ -114,3 +114,6 @@ class Principal < ActiveRecord::Base
true
end
end
+
+require_dependency "user"
+require_dependency "group"
diff --git a/app/models/project.rb b/app/models/project.rb
index 96de85517..6a428b209 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -32,7 +32,7 @@ class Project < ActiveRecord::Base
has_many :memberships, :class_name => 'Member'
has_many :member_principals, :class_name => 'Member',
:include => :principal,
- :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})"
+ :conditions => "#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}"
has_many :enabled_modules, :dependent => :delete_all
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
@@ -191,11 +191,9 @@ class Project < ActiveRecord::Base
statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
end
end
- if user.logged?
- user.projects_by_role.each do |role, projects|
- if role.allowed_to?(permission) && projects.any?
- statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
- end
+ user.projects_by_role.each do |role, projects|
+ if role.allowed_to?(permission) && projects.any?
+ statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
end
end
if statement_by_role.empty?
@@ -213,6 +211,12 @@ class Project < ActiveRecord::Base
end
end
+ def override_roles(role)
+ @override_members ||= memberships.where(:user_id => [GroupAnonymous.instance_id, GroupNonMember.instance_id]).all
+ member = @override_members.detect {|m| role.anonymous? ^ (m.user_id == GroupNonMember.instance_id)}
+ member ? member.roles : [role]
+ end
+
def principals
@principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
end
@@ -305,6 +309,7 @@ class Project < ActiveRecord::Base
@actions_allowed = nil
@start_date = nil
@due_date = nil
+ @override_members = nil
base_reload(*args)
end
@@ -498,8 +503,13 @@ class Project < ActiveRecord::Base
# Users/groups issues can be assigned to
def assignable_users
- assignable = Setting.issue_group_assignment? ? member_principals : members
- assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
+ types = ['User']
+ types << 'Group' if Setting.issue_group_assignment?
+
+ member_principals.
+ select {|m| types.include?(m.principal.type) && m.roles.detect(&:assignable?)}.
+ map(&:principal).
+ sort
end
# Returns the mail addresses of users that should be always notified on project events
diff --git a/app/models/user.rb b/app/models/user.rb
index c79c674a9..d8590f47c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -474,15 +474,15 @@ class User < Principal
# Return user's roles for project
def roles_for_project(project)
- roles = []
# No role on archived projects
- return roles if project.nil? || project.archived?
+ return [] if project.nil? || project.archived?
if membership = membership(project)
- roles = membership.roles
+ membership.roles.dup
+ elsif project.is_public?
+ project.override_roles(builtin_role)
else
- roles << builtin_role
+ []
end
- roles
end
# Return true if the user is a member of project
@@ -494,20 +494,28 @@ class User < Principal
def projects_by_role
return @projects_by_role if @projects_by_role
- @projects_by_role = Hash.new([])
- memberships.each do |membership|
- if membership.project
- membership.roles.each do |role|
- @projects_by_role[role] = [] unless @projects_by_role.key?(role)
- @projects_by_role[role] << membership.project
+ hash = Hash.new([])
+
+ members = Member.joins(:project).
+ where("#{Project.table_name}.status <> 9").
+ where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, Group.builtin_id(self)).
+ preload(:project, :roles)
+
+ members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
+ members.each do |member|
+ if member.project
+ member.roles.each do |role|
+ hash[role] = [] unless hash.key?(role)
+ hash[role] << member.project
end
end
end
- @projects_by_role.each do |role, projects|
+
+ hash.each do |role, projects|
projects.uniq!
end
- @projects_by_role
+ @projects_by_role = hash
end
# Returns true if user is arg or belongs to arg
diff --git a/app/views/groups/_form.html.erb b/app/views/groups/_form.html.erb
index 7dc224061..9d5b087e1 100644
--- a/app/views/groups/_form.html.erb
+++ b/app/views/groups/_form.html.erb
@@ -1,7 +1,9 @@
<%= error_messages_for @group %>
<div class="box tabular">
- <p><%= f.text_field :name, :required => true, :size => 60 %></p>
+ <p><%= f.text_field :name, :required => true, :size => 60,
+ :disabled => !@group.safe_attribute?('name') %></p>
+
<% @group.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :group, value %></p>
<% end %>
diff --git a/app/views/groups/_general.html.erb b/app/views/groups/_general.html.erb
index 8c720c3ee..c48f54cd4 100644
--- a/app/views/groups/_general.html.erb
+++ b/app/views/groups/_general.html.erb
@@ -1,4 +1,4 @@
-<%= labelled_form_for @group do |f| %>
+<%= labelled_form_for @group, :url => group_path(@group) do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>
diff --git a/app/views/groups/edit.html.erb b/app/views/groups/edit.html.erb
index 43be52f30..b2f37bde1 100644
--- a/app/views/groups/edit.html.erb
+++ b/app/views/groups/edit.html.erb
@@ -1,3 +1,3 @@
<%= title [l(:label_group_plural), groups_path], @group.name %>
-<%= render_tabs group_settings_tabs %>
+<%= render_tabs group_settings_tabs(@group) %>
diff --git a/app/views/groups/index.api.rsb b/app/views/groups/index.api.rsb
index 6a4efeaf7..8ebad1baf 100644
--- a/app/views/groups/index.api.rsb
+++ b/app/views/groups/index.api.rsb
@@ -3,6 +3,7 @@ api.array :groups do
api.group do
api.id group.id
api.name group.lastname
+ api.builtin group.builtin_type if group.builtin_type
render_api_custom_values group.visible_custom_field_values, api
end
diff --git a/app/views/groups/index.html.erb b/app/views/groups/index.html.erb
index 22c89eff4..a600ca48d 100644
--- a/app/views/groups/index.html.erb
+++ b/app/views/groups/index.html.erb
@@ -12,10 +12,10 @@
</tr></thead>
<tbody>
<% @groups.each do |group| %>
- <tr id="group-<%= group.id %>" class="<%= cycle 'odd', 'even' %>">
+ <tr id="group-<%= group.id %>" class="<%= cycle 'odd', 'even' %> <%= "builtin" if group.builtin? %>">
<td class="name"><%= link_to h(group), edit_group_path(group) %></td>
- <td class="user_count"><%= @user_count_by_group_id[group.id] || 0 %></td>
- <td class="buttons"><%= delete_link group %></td>
+ <td class="user_count"><%= (@user_count_by_group_id[group.id] || 0) unless group.builtin? %></td>
+ <td class="buttons"><%= delete_link group unless group.builtin? %></td>
</tr>
<% end %>
</tbody>
diff --git a/app/views/groups/show.api.rsb b/app/views/groups/show.api.rsb
index 626474410..15211f2cf 100644
--- a/app/views/groups/show.api.rsb
+++ b/app/views/groups/show.api.rsb
@@ -1,6 +1,7 @@
api.group do
api.id @group.id
api.name @group.lastname
+ api.builtin @group.builtin_type if @group.builtin_type
render_api_custom_values @group.visible_custom_field_values, api
@@ -8,7 +9,7 @@ api.group do
@group.users.each do |user|
api.user :id => user.id, :name => user.name
end
- end if include_in_api_response?('users')
+ end if include_in_api_response?('users') && !@group.builtin?
api.array :memberships do
@group.memberships.each do |membership|
diff --git a/app/views/users/_groups.html.erb b/app/views/users/_groups.html.erb
index 1203d3b59..1f54a8944 100644
--- a/app/views/users/_groups.html.erb
+++ b/app/views/users/_groups.html.erb
@@ -1,6 +1,6 @@
<%= form_for(:user, :url => { :action => 'update' }, :html => {:method => :put}) do %>
<div class="box">
-<% Group.all.sort.each do |group| %>
+<% Group.givable.sort.each do |group| %>
<label><%= check_box_tag 'user[group_ids][]', group.id, @user.groups.include?(group), :id => nil %> <%=h group %></label><br />
<% end %>
<%= hidden_field_tag 'user[group_ids][]', '' %>