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
126
127
128
129
130
131
132
133
134
|
# frozen_string_literal: true
module ResourceAccessTokens
class CreateService < BaseService
include Gitlab::Utils::StrongMemoize
def initialize(current_user, resource, params = {})
@resource_type = resource.class.name.downcase
@resource = resource
@current_user = current_user
@params = params.dup
end
def execute
return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create?
access_level = params[:access_level] || Gitlab::Access::MAINTAINER
return error("Could not provision owner access to project access token") if do_not_allow_owner_access_level_for_project_bot?(access_level)
user = create_user
return error(user.errors.full_messages.to_sentence) unless user.persisted?
user.update!(external: true) if current_user.external?
member = create_membership(resource, user, access_level)
unless member.persisted?
delete_failed_user(user)
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}")
end
token_response = create_personal_access_token(user)
if token_response.success?
log_event(token_response.payload[:personal_access_token])
success(token_response.payload[:personal_access_token])
else
delete_failed_user(user)
error(token_response.message)
end
end
private
attr_reader :resource_type, :resource
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
def has_permission_to_create?
%w(project group).include?(resource_type) && can?(current_user, :create_resource_access_tokens, resource)
end
def create_user
# Even project maintainers/owners 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/owner does not inherently have the ability
# to create a new user in the system.
::Users::AuthorizedCreateService.new(current_user, default_user_params).execute
end
def delete_failed_user(user)
DeleteUserWorker.perform_async(current_user.id, user.id, hard_delete: true, skip_authorization: true)
end
def default_user_params
{
name: params[:name] || "#{resource.name.to_s.humanize} bot",
email: username_and_email_generator.email,
username: username_and_email_generator.username,
user_type: :project_bot,
skip_confirmation: true # Bot users should always have their emails confirmed.
}
end
def create_personal_access_token(user)
PersonalAccessTokens::CreateService.new(
current_user: user, target_user: user, params: personal_access_token_params
).execute
end
def personal_access_token_params
{
name: params[:name] || "#{resource_type}_bot",
impersonation: false,
scopes: params[:scopes] || default_scopes,
expires_at: params[:expires_at] || nil
}
end
def default_scopes
Gitlab::Auth.resource_bot_scopes
end
def create_membership(resource, user, access_level)
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
end
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
def error(message)
ServiceResponse.error(message: message)
end
def success(access_token)
ServiceResponse.success(payload: { access_token: access_token })
end
def do_not_allow_owner_access_level_for_project_bot?(access_level)
resource.is_a?(Project) &&
access_level.to_i == Gitlab::Access::OWNER &&
!current_user.can?(:manage_owners, resource)
end
end
end
ResourceAccessTokens::CreateService.prepend_mod_with('ResourceAccessTokens::CreateService')
|