2020-04-21 11:21:10 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-05-05 17:09:42 -04:00
|
|
|
module ResourceAccessTokens
|
|
|
|
class CreateService < BaseService
|
|
|
|
def initialize(current_user, resource, params = {})
|
|
|
|
@resource_type = resource.class.name.downcase
|
2020-04-21 11:21:10 -04:00
|
|
|
@resource = resource
|
2020-05-05 17:09:42 -04:00
|
|
|
@current_user = current_user
|
2020-04-21 11:21:10 -04:00
|
|
|
@params = params.dup
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
2021-01-22 16:09:10 -05:00
|
|
|
return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create?
|
2020-04-21 11:21:10 -04:00
|
|
|
|
|
|
|
user = create_user
|
|
|
|
|
|
|
|
return error(user.errors.full_messages.to_sentence) unless user.persisted?
|
2020-11-02 16:09:10 -05:00
|
|
|
|
|
|
|
member = create_membership(resource, user)
|
|
|
|
|
|
|
|
unless member.persisted?
|
|
|
|
delete_failed_user(user)
|
|
|
|
return error("Could not provision maintainer access to project access token")
|
|
|
|
end
|
2020-04-21 11:21:10 -04:00
|
|
|
|
|
|
|
token_response = create_personal_access_token(user)
|
|
|
|
|
|
|
|
if token_response.success?
|
2021-01-22 16:09:10 -05:00
|
|
|
log_event(token_response.payload[:personal_access_token])
|
2020-04-21 11:21:10 -04:00
|
|
|
success(token_response.payload[:personal_access_token])
|
|
|
|
else
|
2020-11-02 16:09:10 -05:00
|
|
|
delete_failed_user(user)
|
2020-04-21 11:21:10 -04:00
|
|
|
error(token_response.message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2020-05-05 17:09:42 -04:00
|
|
|
attr_reader :resource_type, :resource
|
|
|
|
|
2020-04-21 11:21:10 -04:00
|
|
|
def has_permission_to_create?
|
2021-03-26 17:09:22 -04:00
|
|
|
%w(project group).include?(resource_type) && can?(current_user, :create_resource_access_tokens, resource)
|
2020-04-21 11:21:10 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def create_user
|
2020-06-30 08:08:57 -04:00
|
|
|
# Even project maintainers can create project access tokens, which in turn
|
|
|
|
# creates a bot user, and so it becomes necessary to have `skip_authorization: true`
|
|
|
|
# since someone like a project maintainer does not inherently have the ability
|
|
|
|
# to create a new user in the system.
|
|
|
|
|
2020-04-21 11:21:10 -04:00
|
|
|
Users::CreateService.new(current_user, default_user_params).execute(skip_authorization: true)
|
|
|
|
end
|
|
|
|
|
2020-11-02 16:09:10 -05:00
|
|
|
def delete_failed_user(user)
|
|
|
|
DeleteUserWorker.perform_async(current_user.id, user.id, hard_delete: true, skip_authorization: true)
|
|
|
|
end
|
|
|
|
|
2020-04-21 11:21:10 -04:00
|
|
|
def default_user_params
|
|
|
|
{
|
|
|
|
name: params[:name] || "#{resource.name.to_s.humanize} bot",
|
|
|
|
email: generate_email,
|
|
|
|
username: generate_username,
|
2020-06-30 08:08:57 -04:00
|
|
|
user_type: "#{resource_type}_bot".to_sym,
|
|
|
|
skip_confirmation: true # Bot users should always have their emails confirmed.
|
2020-04-21 11:21:10 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def generate_username
|
|
|
|
base_username = "#{resource_type}_#{resource.id}_bot"
|
|
|
|
|
|
|
|
uniquify.string(base_username) { |s| User.find_by_username(s) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def generate_email
|
|
|
|
email_pattern = "#{resource_type}#{resource.id}_bot%s@example.com"
|
|
|
|
|
|
|
|
uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
|
|
|
|
User.find_by_email(s)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def uniquify
|
|
|
|
Uniquify.new
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_personal_access_token(user)
|
2020-11-09 07:09:24 -05:00
|
|
|
PersonalAccessTokens::CreateService.new(
|
|
|
|
current_user: user, target_user: user, params: personal_access_token_params
|
|
|
|
).execute
|
2020-04-21 11:21:10 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def personal_access_token_params
|
|
|
|
{
|
2020-05-05 17:09:42 -04:00
|
|
|
name: params[:name] || "#{resource_type}_bot",
|
2020-04-21 11:21:10 -04:00
|
|
|
impersonation: false,
|
|
|
|
scopes: params[:scopes] || default_scopes,
|
|
|
|
expires_at: params[:expires_at] || nil
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_scopes
|
2020-05-05 17:09:42 -04:00
|
|
|
Gitlab::Auth.resource_bot_scopes
|
2020-04-21 11:21:10 -04:00
|
|
|
end
|
|
|
|
|
2020-11-02 16:09:10 -05:00
|
|
|
def create_membership(resource, user)
|
2020-10-15 02:09:14 -04:00
|
|
|
resource.add_user(user, :maintainer, expires_at: params[:expires_at])
|
2020-04-21 11:21:10 -04:00
|
|
|
end
|
|
|
|
|
2021-01-22 16:09:10 -05:00
|
|
|
def log_event(token)
|
|
|
|
::Gitlab::AppLogger.info "PROJECT ACCESS TOKEN CREATION: created_by: #{current_user.username}, project_id: #{resource.id}, token_user: #{token.user.name}, token_id: #{token.id}"
|
|
|
|
end
|
|
|
|
|
2020-04-21 11:21:10 -04:00
|
|
|
def error(message)
|
|
|
|
ServiceResponse.error(message: message)
|
|
|
|
end
|
|
|
|
|
|
|
|
def success(access_token)
|
|
|
|
ServiceResponse.success(payload: { access_token: access_token })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-01-22 16:09:10 -05:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
ResourceAccessTokens::CreateService.prepend_mod_with('ResourceAccessTokens::CreateService')
|