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

effective_access_level_finder.rb « members « projects « finders « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4538fc4c8554e7a07a4d1c50bf9d3bf289bd2dd6 (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
# frozen_string_literal: true

module Projects
  module Members
    class EffectiveAccessLevelFinder
      include Gitlab::Utils::StrongMemoize

      USER_ID_AND_ACCESS_LEVEL = [:user_id, :access_level].freeze
      BATCH_SIZE = 5

      def initialize(project)
        @project = project
      end

      def execute
        return Member.none if no_members?

        # rubocop: disable CodeReuse/ActiveRecord
        Member.from(generate_from_statement(user_ids_and_access_levels_from_all_memberships))
          .select([:user_id, 'MAX(access_level) AS access_level'])
          .group(:user_id)
        # rubocop: enable CodeReuse/ActiveRecord
      end

      private

      attr_reader :project

      def generate_from_statement(user_ids_and_access_levels)
        values_list = Arel::Nodes::ValuesList.new(user_ids_and_access_levels).to_sql

        "(#{values_list}) members (user_id, access_level)"
      end

      def no_members?
        user_ids_and_access_levels_from_all_memberships.blank?
      end

      def all_possible_avenues_of_membership
        avenues = [authorizable_project_members]

        avenues << if project.personal?
                     project_owner_acting_as_maintainer
                   else
                     authorizable_group_members
                   end

        if include_membership_from_project_group_shares?
          avenues << members_from_project_group_shares
        end

        avenues
      end

      # @return [Array<[user_id, access_level]>]
      def user_ids_and_access_levels_from_all_memberships
        strong_memoize(:user_ids_and_access_levels_from_all_memberships) do
          all_possible_avenues_of_membership.flat_map do |members|
            apply_scopes(members).pluck(*USER_ID_AND_ACCESS_LEVEL) # rubocop: disable CodeReuse/ActiveRecord
          end
        end
      end

      def authorizable_project_members
        project.members.authorizable
      end

      def authorizable_group_members
        project.group.authorizable_members_with_parents
      end

      def members_from_project_group_shares
        members = []

        project.project_group_links.each_batch(of: BATCH_SIZE) do |relation|
          members_per_batch = []

          relation.includes(:group).each do |link| # rubocop: disable CodeReuse/ActiveRecord
            members_per_batch << link.group.authorizable_members_with_parents.select(*user_id_and_access_level_for_project_group_shares(link))
          end

          members << Member.from_union(members_per_batch)
        end

        Member.from_union(members)
      end

      def project_owner_acting_as_maintainer
        user_id = project.namespace.owner.id
        access_level = Gitlab::Access::MAINTAINER

        Member
          .from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord
          .limit(1)
      end

      def include_membership_from_project_group_shares?
        !project.namespace.share_with_group_lock && project.project_group_links.any?
      end

      # methods for `select` options

      def user_id_and_access_level_for_project_group_shares(link)
        least_access_level_among_group_membership_and_project_share =
          smallest_value_arel([link.group_access, GroupMember.arel_table[:access_level]], 'access_level')

        [
          :user_id,
          least_access_level_among_group_membership_and_project_share
        ]
      end

      def smallest_value_arel(args, column_alias)
        Arel::Nodes::As.new(
          Arel::Nodes::NamedFunction.new('LEAST', args),
          Arel.sql(column_alias)
        )
      end

      def apply_scopes(members)
        members
      end
    end
  end
end