2020-04-22 19:07:51 +05:30
# frozen_string_literal: true
2020-05-24 23:13:21 +05:30
module ResourceAccessTokens
class CreateService < BaseService
2023-05-27 22:25:52 +05:30
include Gitlab :: Utils :: StrongMemoize
2020-05-24 23:13:21 +05:30
def initialize ( current_user , resource , params = { } )
@resource_type = resource . class . name . downcase
2020-04-22 19:07:51 +05:30
@resource = resource
2020-05-24 23:13:21 +05:30
@current_user = current_user
2020-04-22 19:07:51 +05:30
@params = params . dup
end
def execute
2021-03-11 19:13:27 +05:30
return error ( " User does not have permission to create #{ resource_type } access token " ) unless has_permission_to_create?
2020-04-22 19:07:51 +05:30
2022-07-23 23:45:48 +05:30
access_level = params [ :access_level ] || Gitlab :: Access :: MAINTAINER
2023-01-10 11:22:00 +05:30
return error ( " Could not provision owner access to project access token " ) if do_not_allow_owner_access_level_for_project_bot? ( access_level )
2022-07-23 23:45:48 +05:30
2020-04-22 19:07:51 +05:30
user = create_user
return error ( user . errors . full_messages . to_sentence ) unless user . persisted?
2021-01-29 00:20:46 +05:30
2021-09-04 01:27:46 +05:30
user . update! ( external : true ) if current_user . external?
2021-09-30 23:02:18 +05:30
member = create_membership ( resource , user , access_level )
2021-01-29 00:20:46 +05:30
unless member . persisted?
delete_failed_user ( user )
2023-05-27 22:25:52 +05:30
return error ( " Could not provision #{ Gitlab :: Access . human_access ( access_level . to_i ) . downcase } access to the access token. ERROR: #{ member . errors . full_messages . to_sentence } " )
2021-01-29 00:20:46 +05:30
end
2020-04-22 19:07:51 +05:30
token_response = create_personal_access_token ( user )
if token_response . success?
2021-03-11 19:13:27 +05:30
log_event ( token_response . payload [ :personal_access_token ] )
2020-04-22 19:07:51 +05:30
success ( token_response . payload [ :personal_access_token ] )
else
2021-01-29 00:20:46 +05:30
delete_failed_user ( user )
2020-04-22 19:07:51 +05:30
error ( token_response . message )
end
end
private
2020-05-24 23:13:21 +05:30
attr_reader :resource_type , :resource
2023-05-27 22:25:52 +05:30
def username_and_email_generator
Gitlab :: Utils :: UsernameAndEmailGenerator . new (
username_prefix : " #{ resource_type } _ #{ resource . id } _bot " ,
email_domain : " noreply. #{ Gitlab . config . gitlab . host } "
)
end
strong_memoize_attr :username_and_email_generator
2020-04-22 19:07:51 +05:30
def has_permission_to_create?
2021-04-29 21:17:54 +05:30
%w( project group ) . include? ( resource_type ) && can? ( current_user , :create_resource_access_tokens , resource )
2020-04-22 19:07:51 +05:30
end
def create_user
2022-11-25 23:54:43 +05:30
# Even project maintainers/owners can create project access tokens, which in turn
2020-07-28 23:09:34 +05:30
# creates a bot user, and so it becomes necessary to have `skip_authorization: true`
2022-11-25 23:54:43 +05:30
# since someone like a project maintainer/owner does not inherently have the ability
2020-07-28 23:09:34 +05:30
# to create a new user in the system.
2021-09-04 01:27:46 +05:30
:: Users :: AuthorizedCreateService . new ( current_user , default_user_params ) . execute
2020-04-22 19:07:51 +05:30
end
2021-01-29 00:20:46 +05:30
def delete_failed_user ( user )
DeleteUserWorker . perform_async ( current_user . id , user . id , hard_delete : true , skip_authorization : true )
end
2020-04-22 19:07:51 +05:30
def default_user_params
{
name : params [ :name ] || " #{ resource . name . to_s . humanize } bot " ,
2023-05-27 22:25:52 +05:30
email : username_and_email_generator . email ,
username : username_and_email_generator . username ,
2022-03-02 08:16:31 +05:30
user_type : :project_bot ,
2020-07-28 23:09:34 +05:30
skip_confirmation : true # Bot users should always have their emails confirmed.
2020-04-22 19:07:51 +05:30
}
end
def create_personal_access_token ( user )
2021-01-29 00:20:46 +05:30
PersonalAccessTokens :: CreateService . new (
current_user : user , target_user : user , params : personal_access_token_params
) . execute
2020-04-22 19:07:51 +05:30
end
def personal_access_token_params
{
2020-05-24 23:13:21 +05:30
name : params [ :name ] || " #{ resource_type } _bot " ,
2020-04-22 19:07:51 +05:30
impersonation : false ,
scopes : params [ :scopes ] || default_scopes ,
expires_at : params [ :expires_at ] || nil
}
end
def default_scopes
2020-05-24 23:13:21 +05:30
Gitlab :: Auth . resource_bot_scopes
2020-04-22 19:07:51 +05:30
end
2021-09-30 23:02:18 +05:30
def create_membership ( resource , user , access_level )
2023-07-09 08:55:56 +05:30
resource . add_member ( user , access_level , expires_at : default_pat_expiration )
end
def default_pat_expiration
if Feature . enabled? ( :default_pat_expiration )
params [ :expires_at ] . presence || PersonalAccessToken :: MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS . days . from_now
else
params [ :expires_at ]
end
2020-04-22 19:07:51 +05:30
end
2021-03-11 19:13:27 +05:30
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-22 19:07:51 +05:30
def error ( message )
ServiceResponse . error ( message : message )
end
def success ( access_token )
ServiceResponse . success ( payload : { access_token : access_token } )
end
2023-01-10 11:22:00 +05:30
def do_not_allow_owner_access_level_for_project_bot? ( access_level )
resource . is_a? ( Project ) &&
2023-03-04 22:38:38 +05:30
access_level . to_i == Gitlab :: Access :: OWNER &&
2023-01-10 11:22:00 +05:30
! current_user . can? ( :manage_owners , resource )
end
2020-04-22 19:07:51 +05:30
end
end
2021-03-11 19:13:27 +05:30
2021-06-08 01:23:25 +05:30
ResourceAccessTokens :: CreateService . prepend_mod_with ( 'ResourceAccessTokens::CreateService' )