diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-09-28 14:51:08 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-09-28 14:51:08 +0000 |
commit | 7e7ac5340a281ed767066af0b5f4dd45a3d7076f (patch) | |
tree | 891640b0548c0d3063daddb219006d120fa312c3 /app | |
parent | 9a7fb0ad7be0ee3403f5b89eb0c16b68c991d519 (diff) | |
download | redmine-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.rb | 8 | ||||
-rw-r--r-- | app/helpers/groups_helper.rb | 11 | ||||
-rw-r--r-- | app/helpers/users_helper.rb | 2 | ||||
-rw-r--r-- | app/models/group.rb | 35 | ||||
-rw-r--r-- | app/models/group_anonymous.rb | 30 | ||||
-rw-r--r-- | app/models/group_builtin.rb | 56 | ||||
-rw-r--r-- | app/models/group_non_member.rb | 30 | ||||
-rw-r--r-- | app/models/issue_query.rb | 7 | ||||
-rw-r--r-- | app/models/principal.rb | 3 | ||||
-rw-r--r-- | app/models/project.rb | 26 | ||||
-rw-r--r-- | app/models/user.rb | 34 | ||||
-rw-r--r-- | app/views/groups/_form.html.erb | 4 | ||||
-rw-r--r-- | app/views/groups/_general.html.erb | 2 | ||||
-rw-r--r-- | app/views/groups/edit.html.erb | 2 | ||||
-rw-r--r-- | app/views/groups/index.api.rsb | 1 | ||||
-rw-r--r-- | app/views/groups/index.html.erb | 6 | ||||
-rw-r--r-- | app/views/groups/show.api.rsb | 3 | ||||
-rw-r--r-- | app/views/users/_groups.html.erb | 2 |
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][]', '' %> |