Welcome to mirror list, hosted at ThFree Co, Russian Federation.

group_members_finder.rb « finders « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 1025e0ebc9b1885fa1a4a7af22231dd1f1df76aa (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 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
  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).distinct_on_user_with_max_access_level

    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)

    by_created_at(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)
    if Feature.disabled?(:members_with_shared_group_access, @group.root_ancestor)
      groups << shared_from_groups unless shared_from_groups.nil?
      return GroupMember.non_request.of_groups(find_union(groups, Group))
    end

    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
end

GroupMembersFinder.prepend_mod_with('GroupMembersFinder')