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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
# frozen_string_literal: true
class GroupMembersFinder < UnionFinder
RELATIONS = %i[direct inherited descendants shared_from_groups].freeze
DEFAULT_RELATIONS = %i[direct inherited].freeze
INVALID_RELATION_TYPE_ERROR_MSG = "is not a valid relation type. Valid relation types are #{RELATIONS.join(', ')}."
RELATIONS_DESCRIPTIONS = {
direct: 'Members in the group itself',
inherited: "Members in the group's ancestor groups",
descendants: "Members in the group's subgroups",
shared_from_groups: "Invited group's members"
}.freeze
include CreatedAtFilter
# Params can be any of the following:
# two_factor: string. 'enabled' or 'disabled' are returning different set of data, other values are not effective.
# sort: string
# search: string
# created_after: datetime
# created_before: datetime
# non_invite: boolean
# with_custom_role: boolean
attr_reader :params
def initialize(group, user = nil, params: {})
@group = group
@user = user
@params = params
end
def execute(include_relations: DEFAULT_RELATIONS)
groups = groups_by_relations(include_relations)
shared_from_groups = if include_relations&.include?(:shared_from_groups)
Group.shared_into_ancestors(group).public_or_visible_to_user(user)
end
members = all_group_members(groups, shared_from_groups)
if static_roles_only?
members = members.distinct_on_user_with_max_access_level
end
filter_members(members)
end
private
attr_reader :user, :group
def groups_by_relations(include_relations)
check_relation_arguments!(include_relations)
related_groups = []
related_groups << Group.by_id(group.id) if include_relations&.include?(:direct)
related_groups << group.ancestors if include_relations&.include?(:inherited)
related_groups << group.descendants if include_relations&.include?(:descendants)
related_groups
end
def filter_members(members)
members = members.search(params[:search]) if params[:search].present?
members = members.sort_by_attribute(params[:sort]) if params[:sort].present?
if params[:two_factor].present? && can_manage_members
members = members.filter_by_2fa(params[:two_factor])
end
if params[:access_levels].present?
members = members.by_access_level(params[:access_levels])
end
members = filter_by_user_type(members)
members = apply_additional_filters(members)
members = by_created_at(members)
members = members.non_invite if params[:non_invite]
members
end
def can_manage_members
Ability.allowed?(user, :admin_group_member, group)
end
def group_members_list
group.members
end
def all_group_members(groups, shared_from_groups)
members_of_groups(groups, shared_from_groups).non_minimal_access
end
def members_of_groups(groups, shared_from_groups)
members = GroupMember.non_request.of_groups(find_union(groups, Group))
return members if shared_from_groups.nil?
shared_members = GroupMember.non_request.of_groups(shared_from_groups)
select_attributes = GroupMember.attribute_names
members_shared_with_group_access = members_shared_with_group_access(shared_members, select_attributes)
# `members` and `members_shared_with_group_access` should have even select values
find_union([members.select(select_attributes), members_shared_with_group_access], GroupMember)
end
def members_shared_with_group_access(shared_members, select_attributes)
group_group_link_table = GroupGroupLink.arel_table
group_member_table = GroupMember.arel_table
member_columns = select_attributes.map do |column_name|
if column_name == 'access_level'
args = [group_group_link_table[:group_access], group_member_table[:access_level]]
smallest_value_arel(args, 'access_level')
else
group_member_table[column_name]
end
end
# rubocop:disable CodeReuse/ActiveRecord
shared_members
.joins("LEFT OUTER JOIN group_group_links ON members.source_id = group_group_links.shared_with_group_id")
.select(member_columns)
# rubocop:enable CodeReuse/ActiveRecord
end
def smallest_value_arel(args, column_alias)
Arel::Nodes::As.new(Arel::Nodes::NamedFunction.new('LEAST', args), Arel::Nodes::SqlLiteral.new(column_alias))
end
def check_relation_arguments!(include_relations)
unless include_relations & RELATIONS == include_relations
raise ArgumentError, "#{(include_relations - RELATIONS).first} #{INVALID_RELATION_TYPE_ERROR_MSG}"
end
end
def filter_by_user_type(members)
return members unless params[:user_type] && can_manage_members
members.filter_by_user_type(params[:user_type])
end
def apply_additional_filters(members)
# overridden in EE to include additional filtering conditions.
members
end
def static_roles_only?
true
end
end
GroupMembersFinder.prepend_mod_with('GroupMembersFinder')
|