gitlab-org--gitlab-foss/app/services/members/creator_service.rb

173 lines
4.8 KiB
Ruby

# frozen_string_literal: true
module Members
# This class serves as more of an app-wide way we add/create members
# All roads to add members should take this path.
class CreatorService
class << self
def parsed_access_level(access_level)
access_levels.fetch(access_level) { access_level.to_i }
end
def access_levels
raise NotImplementedError
end
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
return [] unless users.present?
emails, users, existing_members = parse_users_list(source, users)
Member.transaction do
(emails + users).map! do |user|
new(source,
user,
access_level,
existing_members: existing_members,
current_user: current_user,
expires_at: expires_at)
.execute
end
end
end
private
def parse_users_list(source, list)
emails = []
user_ids = []
users = []
existing_members = {}
list.each do |item|
case item
when User
users << item
when Integer
user_ids << item
when /\A\d+\Z/
user_ids << item.to_i
when Devise.email_regexp
emails << item
end
end
if user_ids.present?
users.concat(User.id_in(user_ids))
# the below will automatically discard invalid user_ids
existing_members = source.members_and_requesters.where(user_id: user_ids).index_by(&:user_id) # rubocop:todo CodeReuse/ActiveRecord
end
[emails, users, existing_members]
end
end
def initialize(source, user, access_level, **args)
@source = source
@user = user
@access_level = self.class.parsed_access_level(access_level)
@args = args
end
def execute
find_or_build_member
update_member
member
end
private
attr_reader :source, :user, :access_level, :member, :args
def update_member
return unless can_update_member?
member.attributes = member_attributes
if member.request?
approve_request
else
member.save
end
end
def can_update_member?
# There is no current user for bulk actions, in which case anything is allowed
!current_user # inheriting classes will add more logic
end
# Populates the attributes of a member.
#
# This logic resides in a separate method so that EE can extend this logic,
# without having to patch the `add_user` method directly.
def member_attributes
{
created_by: member.created_by || current_user,
access_level: access_level,
expires_at: args[:expires_at]
}
end
def approve_request
::Members::ApproveAccessRequestService.new(current_user,
access_level: access_level)
.execute(
member,
skip_authorization: ldap,
skip_log_audit_event: ldap
)
end
def current_user
args[:current_user]
end
def find_or_build_member
@user = parse_user_param
@member = if user.is_a?(User)
find_or_initialize_member_by_user
else
source.members.build(invite_email: user)
end
end
# This method is used to find users that have been entered into the "Add members" field.
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
def parse_user_param
case user
when User
user
when Integer
# might not return anything - this needs enhancement
User.find_by(id: user) # rubocop:todo CodeReuse/ActiveRecord
else
# must be an email or at least we'll consider it one
User.find_by_any_email(user) || user
end
end
def find_or_initialize_member_by_user
if existing_members
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/334062
# i'm not so sure this is needed as the parse_users_list looks at members_and_requesters...
# so it is like we could just do a find or initialize by here and be fine
existing_members[user.id] || source.members.build(user_id: user.id)
else
source.members_and_requesters.find_or_initialize_by(user_id: user.id) # rubocop:todo CodeReuse/ActiveRecord
end
end
def existing_members
args[:existing_members]
end
def ldap
args[:ldap] || false
end
end
end
Members::CreatorService.prepend_mod_with('Members::CreatorService')