##// END OF EJS Templates
Adds a role setting for controlling visibility of users: all or members of visible projects (#11724)....
Jean-Philippe Lang -
r13202:bdd3ccf8e52c
parent child
Show More
@@ -0,0 +1,9
1 class AddRolesUsersVisibility < ActiveRecord::Migration
2 def self.up
3 add_column :roles, :users_visibility, :string, :limit => 30, :default => 'all', :null => false
4 end
5
6 def self.down
7 remove_column :roles, :users_visibility
8 end
9 end
@@ -60,19 +60,17 class UsersController < ApplicationController
60 60 end
61 61
62 62 def show
63 unless @user.visible?
64 render_404
65 return
66 end
67
63 68 # show projects based on current user visibility
64 69 @memberships = @user.memberships.where(Project.visible_condition(User.current)).to_a
65 70
66 71 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
67 72 @events_by_day = events.group_by(&:event_date)
68 73
69 unless User.current.admin?
70 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
71 render_404
72 return
73 end
74 end
75
76 74 respond_to do |format|
77 75 format.html { render :layout => 'base' }
78 76 format.api
@@ -40,8 +40,9 class WatchersController < ApplicationController
40 40 else
41 41 user_ids << params[:user_id]
42 42 end
43 user_ids.flatten.compact.uniq.each do |user_id|
44 Watcher.create(:watchable => @watched, :user_id => user_id)
43 users = User.active.visible.where(:id => user_ids.flatten.compact.uniq)
44 users.each do |user|
45 Watcher.create(:watchable => @watched, :user => user)
45 46 end
46 47 respond_to do |format|
47 48 format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
@@ -53,7 +54,7 class WatchersController < ApplicationController
53 54 def append
54 55 if params[:watcher].is_a?(Hash)
55 56 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
56 @users = User.active.where(:id => user_ids).to_a
57 @users = User.active.visible.where(:id => user_ids).to_a
57 58 end
58 59 if @users.blank?
59 60 render :nothing => true
@@ -61,7 +62,7 class WatchersController < ApplicationController
61 62 end
62 63
63 64 def destroy
64 @watched.set_watcher(User.find(params[:user_id]), false)
65 @watched.set_watcher(User.visible.find(params[:user_id]), false)
65 66 respond_to do |format|
66 67 format.html { redirect_to :back }
67 68 format.js
@@ -115,12 +116,13 class WatchersController < ApplicationController
115 116 end
116 117
117 118 def users_for_new_watcher
118 users = []
119 scope = nil
119 120 if params[:q].blank? && @project.present?
120 users = @project.users.sorted
121 scope = @project.users
121 122 else
122 users = User.active.sorted.like(params[:q]).limit(100)
123 scope = User.all.limit(100)
123 124 end
125 users = scope.active.visible.sorted.like(params[:q]).to_a
124 126 if @watched
125 127 users -= @watched.watcher_users
126 128 end
@@ -131,17 +131,17 class IssueQuery < Query
131 131 issue_custom_fields = []
132 132
133 133 if project
134 principals += project.principals.sort
134 principals += project.principals.visible
135 135 unless project.leaf?
136 136 subprojects = project.descendants.visible.to_a
137 principals += Principal.member_of(subprojects)
137 principals += Principal.member_of(subprojects).visible
138 138 end
139 139 versions = project.shared_versions.to_a
140 140 categories = project.issue_categories.to_a
141 141 issue_custom_fields = project.all_issue_custom_fields
142 142 else
143 143 if all_projects.any?
144 principals += Principal.member_of(all_projects)
144 principals += Principal.member_of(all_projects).visible
145 145 end
146 146 versions = Version.visible.where(:sharing => 'system').to_a
147 147 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
@@ -185,7 +185,7 class IssueQuery < Query
185 185 :type => :list_optional, :values => assigned_to_values
186 186 ) unless assigned_to_values.empty?
187 187
188 group_values = Group.givable.collect {|g| [g.name, g.id.to_s] }
188 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
189 189 add_available_filter("member_of_group",
190 190 :type => :list_optional, :values => group_values
191 191 ) unless group_values.empty?
@@ -38,6 +38,30 class Principal < ActiveRecord::Base
38 38 # Groups and active users
39 39 scope :active, lambda { where(:status => STATUS_ACTIVE) }
40 40
41 scope :visible, lambda {|*args|
42 user = args.first || User.current
43
44 if user.admin?
45 all
46 else
47 view_all_active = false
48 if user.memberships.to_a.any?
49 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
50 else
51 view_all_active = user.builtin_role.users_visibility == 'all'
52 end
53
54 if view_all_active
55 active
56 else
57 # self and members of visible projects
58 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
59 user.id, user.visible_project_ids
60 )
61 end
62 end
63 }
64
41 65 scope :like, lambda {|q|
42 66 q = q.to_s
43 67 if q.blank?
@@ -84,6 +108,10 class Principal < ActiveRecord::Base
84 108 to_s
85 109 end
86 110
111 def visible?(user=User.current)
112 Principal.visible(user).where(:id => id).first == self
113 end
114
87 115 # Return true if the principal is a member of project
88 116 def member_of?(project)
89 117 projects.to_a.include?(project)
@@ -39,6 +39,11 class Role < ActiveRecord::Base
39 39 ['own', :label_issues_visibility_own]
40 40 ]
41 41
42 USERS_VISIBILITY_OPTIONS = [
43 ['all', :label_users_visibility_all],
44 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
45 ]
46
42 47 scope :sorted, lambda { order(:builtin, :position) }
43 48 scope :givable, lambda { order(:position).where(:builtin => 0) }
44 49 scope :builtin, lambda { |*args|
@@ -67,6 +72,9 class Role < ActiveRecord::Base
67 72 validates_inclusion_of :issues_visibility,
68 73 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
69 74 :if => lambda {|role| role.respond_to?(:issues_visibility)}
75 validates_inclusion_of :users_visibility,
76 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
77 :if => lambda {|role| role.respond_to?(:users_visibility)}
70 78
71 79 # Copies attributes from another role, arg can be an id or a Role
72 80 def copy_from(arg, options={})
@@ -40,20 +40,20 class TimeEntryQuery < Query
40 40
41 41 principals = []
42 42 if project
43 principals += project.principals.sort
43 principals += project.principals.visible.sort
44 44 unless project.leaf?
45 45 subprojects = project.descendants.visible.to_a
46 46 if subprojects.any?
47 47 add_available_filter "subproject_id",
48 48 :type => :list_subprojects,
49 49 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
50 principals += Principal.member_of(subprojects)
50 principals += Principal.member_of(subprojects).visible
51 51 end
52 52 end
53 53 else
54 54 if all_projects.any?
55 55 # members of visible projects
56 principals += Principal.member_of(all_projects)
56 principals += Principal.member_of(all_projects).visible
57 57 # project filter
58 58 project_values = []
59 59 if User.current.logged? && User.current.memberships.any?
@@ -148,6 +148,7 class User < Principal
148 148 @notified_projects_ids = nil
149 149 @notified_projects_ids_changed = false
150 150 @builtin_role = nil
151 @visible_project_ids = nil
151 152 base_reload(*args)
152 153 end
153 154
@@ -528,6 +529,11 class User < Principal
528 529 @projects_by_role = hash
529 530 end
530 531
532 # Returns the ids of visible projects
533 def visible_project_ids
534 @visible_project_ids ||= Project.visible(self).pluck(:id)
535 end
536
531 537 # Returns true if user is arg or belongs to arg
532 538 def is_or_belongs_to?(arg)
533 539 if arg.is_a?(User)
@@ -1,18 +1,22
1 1 <%= error_messages_for 'role' %>
2 2
3 <% unless @role.anonymous? %>
4 3 <div class="box tabular">
5 <% unless @role.builtin? %>
6 <p><%= f.text_field :name, :required => true %></p>
7 <p><%= f.check_box :assignable %></p>
8 <% end %>
9 <p><%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
10 <% if @role.new_record? && @roles.any? %>
11 <p><label for="copy_workflow_from"><%= l(:label_copy_workflow_from) %></label>
12 <%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@roles, :id, :name, params[:copy_workflow_from] || @copy_from.try(:id))) %></p>
13 <% end %>
4 <% unless @role.builtin? %>
5 <p><%= f.text_field :name, :required => true %></p>
6 <p><%= f.check_box :assignable %></p>
7 <% end %>
8
9 <% unless @role.anonymous? %>
10 <p><%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
11 <% end %>
12
13 <p><%= f.select :users_visibility, Role::USERS_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
14
15 <% if @role.new_record? && @roles.any? %>
16 <p><label for="copy_workflow_from"><%= l(:label_copy_workflow_from) %></label>
17 <%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@roles, :id, :name, params[:copy_workflow_from] || @copy_from.try(:id))) %></p>
18 <% end %>
14 19 </div>
15 <% end %>
16 20
17 21 <h3><%= l(:label_permissions) %></h3>
18 22 <div class="box tabular" id="permissions">
@@ -338,6 +338,7 en:
338 338 field_generate_password: Generate password
339 339 field_must_change_passwd: Must change password at next logon
340 340 field_default_status: Default status
341 field_users_visibility: Users visibility
341 342
342 343 setting_app_title: Application title
343 344 setting_app_subtitle: Application subtitle
@@ -920,6 +921,8 en:
920 921 label_latest_compatible_version: Latest compatible version
921 922 label_unknown_plugin: Unknown plugin
922 923 label_add_projects: Add projects
924 label_users_visibility_all: All active users
925 label_users_visibility_members_of_visible_projects: Members of visible projects
923 926
924 927 button_login: Login
925 928 button_submit: Submit
@@ -358,6 +358,7 fr:
358 358 field_generate_password: Générer un mot de passe
359 359 field_must_change_passwd: Doit changer de mot de passe à la prochaine connexion
360 360 field_default_status: Statut par défaut
361 field_users_visibility: Visibilité des utilisateurs
361 362
362 363 setting_app_title: Titre de l'application
363 364 setting_app_subtitle: Sous-titre de l'application
@@ -940,6 +941,8 fr:
940 941 label_latest_compatible_version: Dernière version compatible
941 942 label_unknown_plugin: Plugin inconnu
942 943 label_add_projects: Ajouter des projets
944 label_users_visibility_all: Tous les utilisateurs actifs
945 label_users_visibility_members_of_visible_projects: Membres des projets visibles
943 946
944 947 button_login: Connexion
945 948 button_submit: Soumettre
@@ -42,6 +42,7 module Redmine
42 42 # Roles
43 43 manager = Role.create! :name => l(:default_role_manager),
44 44 :issues_visibility => 'all',
45 :users_visibility => 'all',
45 46 :position => 1
46 47 manager.permissions = manager.setable_permissions.collect {|p| p.name}
47 48 manager.save!
@@ -4,6 +4,7 roles_001:
4 4 id: 1
5 5 builtin: 0
6 6 issues_visibility: all
7 users_visibility: all
7 8 permissions: |
8 9 ---
9 10 - :add_project
@@ -67,6 +68,7 roles_002:
67 68 id: 2
68 69 builtin: 0
69 70 issues_visibility: default
71 users_visibility: all
70 72 permissions: |
71 73 ---
72 74 - :edit_project
@@ -114,6 +116,7 roles_003:
114 116 id: 3
115 117 builtin: 0
116 118 issues_visibility: default
119 users_visibility: all
117 120 permissions: |
118 121 ---
119 122 - :edit_project
@@ -155,6 +158,7 roles_004:
155 158 id: 4
156 159 builtin: 1
157 160 issues_visibility: default
161 users_visibility: all
158 162 permissions: |
159 163 ---
160 164 - :view_issues
@@ -184,6 +188,7 roles_005:
184 188 id: 5
185 189 builtin: 2
186 190 issues_visibility: default
191 users_visibility: all
187 192 permissions: |
188 193 ---
189 194 - :view_issues
@@ -106,12 +106,6 class UsersControllerTest < ActionController::TestCase
106 106 assert_response 404
107 107 end
108 108
109 def test_show_should_not_reveal_users_with_no_visible_activity_or_project
110 @request.session[:user_id] = nil
111 get :show, :id => 9
112 assert_response 404
113 end
114
115 109 def test_show_inactive_by_admin
116 110 @request.session[:user_id] = 1
117 111 get :show, :id => 5
@@ -119,6 +113,15 class UsersControllerTest < ActionController::TestCase
119 113 assert_not_nil assigns(:user)
120 114 end
121 115
116 def test_show_user_who_is_not_visible_should_return_404
117 Role.anonymous.update! :users_visibility => 'members_of_visible_projects'
118 user = User.generate!
119
120 @request.session[:user_id] = nil
121 get :show, :id => user.id
122 assert_response 404
123 end
124
122 125 def test_show_displays_memberships_based_on_project_visibility
123 126 @request.session[:user_id] = 1
124 127 get :show, :id => 2
@@ -227,6 +227,21 class WatchersControllerTest < ActionController::TestCase
227 227 assert Issue.find(2).watched_by?(user)
228 228 end
229 229
230 def test_autocomplete_for_user_should_return_visible_users
231 Role.update_all :users_visibility => 'members_of_visible_projects'
232
233 hidden = User.generate!(:lastname => 'autocomplete')
234 visible = User.generate!(:lastname => 'autocomplete')
235 User.add_to_project(visible, Project.find(1))
236
237 @request.session[:user_id] = 2
238 xhr :get, :autocomplete_for_user, :q => 'autocomp', :project_id => 'ecookbook'
239 assert_response :success
240
241 assert_include visible, assigns(:users)
242 assert_not_include hidden, assigns(:users)
243 end
244
230 245 def test_append
231 246 @request.session[:user_id] = 2
232 247 assert_no_difference 'Watcher.count' do
@@ -20,7 +20,7
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class PrincipalTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :members, :member_roles
23 fixtures :users, :projects, :members, :member_roles, :roles
24 24
25 25 def test_active_scope_should_return_groups_and_active_users
26 26 result = Principal.active.to_a
@@ -30,6 +30,27 class PrincipalTest < ActiveSupport::TestCase
30 30 assert_nil result.detect {|p| p.is_a?(AnonymousUser)}
31 31 end
32 32
33 def test_visible_scope_for_admin_should_return_all_principals
34 admin = User.generate! {|u| u.admin = true}
35 assert_equal Principal.count, Principal.visible(admin).count
36 end
37
38 def test_visible_scope_for_user_with_members_of_visible_projects_visibility_should_return_active_principals
39 Role.non_member.update! :users_visibility => 'all'
40 user = User.generate!
41
42 expected = Principal.active
43 assert_equal expected.map(&:id).sort, Principal.visible(user).pluck(:id).sort
44 end
45
46 def test_visible_scope_for_user_with_members_of_visible_projects_visibility_should_return_members_of_visible_projects_and_self
47 Role.non_member.update! :users_visibility => 'members_of_visible_projects'
48 user = User.generate!
49
50 expected = Project.visible(user).map(&:member_principals).flatten.map(&:principal).uniq << user
51 assert_equal expected.map(&:id).sort, Principal.visible(user).pluck(:id).sort
52 end
53
33 54 def test_member_of_scope_should_return_the_union_of_all_members
34 55 projects = Project.find([1])
35 56 assert_equal [3, 2], Principal.member_of(projects).sort.map(&:id)
General Comments 0
You need to be logged in to leave comments. Login now