summaryrefslogtreecommitdiff
path: root/www-apps/redmine/files/redmine-6.0.11-group-memberships-visibility.patch
blob: e0cf95ac29b04ef424c488427a696fd54fa38470 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
https://www.redmine.org/projects/redmine/wiki/security_advisories
Bug: https://www.redmine.org/issues/44468
From https://github.com/redmine/redmine/commit/39e799652b6d5108690e9d5c2f071bd85bad1130
From: Marius Balteanu <marius.balteanu@zitec.com>
Date: Mon, 28 Sep 2026 19:09:04 +0000
Subject: [PATCH] Only render memberships in visible projects in the groups API
 (#44468).
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a Member.visible scope that restricts memberships to projects the
user is allowed to view members of, use it in the groups API template,
and switch users#show to the same scope so both endpoints share one
definition of a visible membership.

Patch by Jens Krämer (user:jkraemer).

git-svn-id: https://svn.redmine.org/redmine/trunk@25164 e93f8b46-1217-0410-a6f0-8f06a7374b81
(cherry picked from commit 39e799652b6d5108690e9d5c2f071bd85bad1130)
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -94,7 +94,7 @@ class UsersController < ApplicationController
     end
 
     # show projects based on current user visibility
-    @memberships = @user.memberships.preload(:roles, :project).where(Project.visible_condition(User.current)).to_a
+    @memberships = @user.memberships.visible.preload(:roles, :project).to_a
 
     @issue_counts = {}
     @issue_counts[:assigned] = {
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -33,6 +33,10 @@ class Member < ApplicationRecord
   scope :active, (lambda do
     joins(:principal).where(:users => {:status => Principal::STATUS_ACTIVE})
   end)
+  scope :visible, (lambda do |*args|
+    joins(:project).
+      where(Project.allowed_to_condition(args.shift || User.current, :view_members, *args))
+  end)
   # Sort by first role and principal
   scope :sorted, (lambda do
     includes(:member_roles, :roles, :principal).
--- a/app/views/groups/show.api.rsb
+++ b/app/views/groups/show.api.rsb
@@ -12,7 +12,7 @@ api.group do
   end if include_in_api_response?('users') && !@group.builtin?
 
   api.array :memberships do
-    @group.memberships.preload(:roles, :project).each do |membership|
+    @group.memberships.visible.preload(:roles, :project).each do |membership|
       api.membership do
         api.id membership.id
         api.project :id => membership.project.id, :name => membership.project.name
--- a/test/integration/api_test/groups_test.rb
+++ b/test/integration/api_test/groups_test.rb
@@ -120,6 +120,32 @@ class Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base
     end
   end
 
+  test "GET /groups/:id.xml with include=memberships should only include memberships in visible projects" do
+    # group 10 is a member of the private project 5, which dlopper can not see
+    Member.create!(:principal => Group.find(10), :project_id => 1, :role_ids => [2])
+
+    get '/groups/10.xml?include=memberships', :headers => credentials('admin')
+    assert_response :success
+    assert_select 'group memberships membership', 2
+
+    get '/groups/10.xml?include=memberships', :headers => credentials('dlopper')
+    assert_response :success
+    assert_select 'group memberships' do
+      assert_select 'membership', 1
+      assert_select 'membership project[id="1"]'
+      assert_select 'membership project[id="5"]', 0
+    end
+  end
+
+  test "GET /groups/:id.json with include=memberships should only include memberships in visible projects" do
+    Member.create!(:principal => Group.find(10), :project_id => 1, :role_ids => [2])
+
+    get '/groups/10.json?include=memberships', :headers => credentials('dlopper')
+    assert_response :success
+    json = ActiveSupport::JSON.decode(response.body)
+    assert_equal [1], json['group']['memberships'].map {|membership| membership['project']['id']}
+  end
+
   test "POST /groups.xml with valid parameters should create the group" do
     assert_difference('Group.count') do
       post(
--- a/test/unit/member_test.rb
+++ b/test/unit/member_test.rb
@@ -27,6 +27,30 @@ class MemberTest < ActiveSupport::TestCase
     @jsmith = Member.find(1)
   end
 
+  def test_visible_scope_should_only_include_memberships_in_projects_visible_to_the_user
+    # dlopper is a member of project 1 only and can not see the private project 5
+    user = User.find(3)
+    memberships = Member.visible(user).to_a
+
+    assert_include Member.find(1), memberships
+    assert_equal [], memberships.select {|m| m.project_id == 5}
+    assert memberships.all? {|m| m.project.visible?(user)}
+  end
+
+  def test_visible_scope_should_default_to_current_user
+    User.current = User.find(3)
+    assert_equal Member.visible(User.find(3)).to_a, Member.visible.to_a
+  end
+
+  def test_visible_scope_should_include_all_memberships_in_active_projects_for_admin
+    assert_equal Member.count, Member.visible(User.find(1)).count
+  end
+
+  def test_visible_scope_should_exclude_memberships_in_archived_projects
+    Project.find(1).update_column :status, Project::STATUS_ARCHIVED
+    assert_not_include Member.find(1), Member.visible(User.find(1)).to_a
+  end
+
   def test_sorted_scope_on_project_members
     members = Project.find(1).members.sorted.to_a
     roles = members.map {|m| m.roles.sort.first}