129 lines
3.8 KiB
Ruby
129 lines
3.8 KiB
Ruby
# 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 #{generate_values_expression(user_ids_and_access_levels)}) members (user_id, access_level)"
|
|
end
|
|
|
|
def generate_values_expression(user_ids_and_access_levels)
|
|
user_ids_and_access_levels.map do |user_id, access_level|
|
|
"(#{user_id}, #{access_level})"
|
|
end.join(",")
|
|
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
|