Module: API::Helpers::Packages::Conan::ApiHelpers

Includes:
Gitlab::Utils::StrongMemoize
Included in:
Packages::Conan::PackagePresenter
Defined in:
lib/api/helpers/packages/conan/api_helpers.rb

Instance Method Summary collapse

Instance Method Details

#access_token_from_requestObject



256
257
258
259
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 256

def access_token_from_request
  find_personal_access_token_from_conan_jwt ||
    find_password_from_basic_auth
end

#build_package_file_upload_url(file_name) ⇒ Object



69
70
71
72
73
74
75
76
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 69

def build_package_file_upload_url(file_name)
  options = url_options(file_name).merge(
    conan_package_reference: declared(params)[:conan_package_reference],
    package_revision: ::Packages::Conan::FileMetadatum::DEFAULT_REVISION
  )

  package_file_url(options)
end

#build_recipe_file_upload_url(file_name) ⇒ Object



78
79
80
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 78

def build_recipe_file_upload_url(file_name)
  recipe_file_url(url_options(file_name))
end

#check_username_channelObject



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 10

def check_username_channel
  username = declared(params)[:package_username]
  channel = declared(params)[:package_channel]

  if username == ::Packages::Conan::Metadatum::NONE_VALUE && package_scope == :instance
    # at the instance level, username must not be empty (naming convention)
    # don't try to process the empty username and eagerly return not found.
    not_found!
  end

  ::Packages::Conan::Metadatum.validate_username_and_channel(username, channel) do |none_field|
    bad_request!("#{none_field} can't be solely blank")
  end
end

#create_package_file_with_type(file_type, current_package) ⇒ Object



218
219
220
221
222
223
224
225
226
227
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 218

def create_package_file_with_type(file_type, current_package)
  unless params[:file].empty_size?
    # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
    ::Packages::Conan::CreatePackageFileService.new(
      current_package,
      params[:file],
      params.merge(conan_file_type: file_type, build: current_authenticated_job)
    ).execute
  end
end

#decode_oauth_token_from_jwtObject



319
320
321
322
323
324
325
326
327
328
329
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 319

def decode_oauth_token_from_jwt
  jwt = Doorkeeper::OAuth::Token.from_bearer_authorization(current_request)

  return unless jwt

  token = ::Gitlab::ConanToken.decode(jwt)

  return unless token && token.access_token_id && token.user_id

  token
end

#deploy_token_from_requestObject



279
280
281
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 279

def deploy_token_from_request
  find_deploy_token_from_conan_jwt || find_deploy_token_from_http_basic_auth
end

#download_package_file(file_type) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 162

def download_package_file(file_type)
  authorize_read_package!(project)

  not_found!('Package') unless package

  package_file = ::Packages::Conan::PackageFileFinder
    .new(
      package,
      file_name: params[:file_name].to_s,
      conan_file_type: file_type,
      conan_package_reference: declared(params)[:conan_package_reference],
      recipe_revision: params[:recipe_revision],
      package_revision: declared(params)[:package_revision]
    ).execute!

  track_package_event('pull_package', :conan, category: 'API::ConanPackages', project: project, namespace: project.namespace) if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY

  present_package_file!(package_file)
end

#file_namesObject



205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 205

def file_names
  json_payload = Gitlab::Json.parse(request.body.read)
  json_payload.keys
rescue JSON::ParserError,
  Encoding::UndefinedConversionError,
  Encoding::InvalidByteSequenceError,
  Encoding::CompatibilityError
  nil
rescue StandardError => e
  Gitlab::ErrorTracking.track_exception(e)
  bad_request!(nil)
end

#find_deploy_token_from_conan_jwtObject



299
300
301
302
303
304
305
306
307
308
309
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 299

def find_deploy_token_from_conan_jwt
  token = decode_oauth_token_from_jwt

  return unless token

  deploy_token = DeployToken.active.find_by_token(token.access_token_id.to_s)
  # note: uesr_id is not a user record id, but is the attribute set on ConanToken
  return if token.user_id != deploy_token&.username

  deploy_token
end

#find_job_from_conan_jwtObject



311
312
313
314
315
316
317
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 311

def find_job_from_conan_jwt
  token = decode_oauth_token_from_jwt

  return unless token

  ::Ci::AuthJobFinder.new(token: token.access_token_id.to_s).execute
end

#find_job_from_tokenObject



283
284
285
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 283

def find_job_from_token
  find_job_from_conan_jwt || find_job_from_http_basic_auth
end

#find_oauth_access_tokenObject

We need to override this one because it looks into Bearer authorization header



289
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 289

def find_oauth_access_token; end

#find_or_create_packageObject



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 182

def find_or_create_package
  return package if package

  service_response = ::Packages::Conan::CreatePackageService.new(
    project,
    current_user,
    params.merge(build: current_authenticated_job)
  ).execute

  if service_response.error?
    forbidden!(service_response.message) if service_response.cause.package_protected?
    bad_request!(service_response.message)
  end

  service_response[:package]
end

#find_password_from_basic_authObject



262
263
264
265
266
267
268
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 262

def find_password_from_basic_auth
  return unless route_authentication_setting[:basic_auth_personal_access_token]
  return unless has_basic_credentials?(current_request)

  _username, password = user_name_and_password(current_request)
  password
end

#find_personal_access_tokenObject

We override this method from auth_finders because we need to extract the token from the Conan JWT which is specific to the Conan API



251
252
253
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 251

def find_personal_access_token
  PersonalAccessToken.active.find_by_token(access_token_from_request)
end

#find_personal_access_token_from_conan_jwtObject



291
292
293
294
295
296
297
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 291

def find_personal_access_token_from_conan_jwt
  token = decode_oauth_token_from_jwt

  return unless token

  token.access_token_id
end

#find_user_from_job_tokenObject



270
271
272
273
274
275
276
277
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 270

def find_user_from_job_token
  return unless route_authentication_setting[:job_token_allowed]

  job = find_job_from_token || return
  @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables

  job.user
end

#packageObject



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 138

def package
  ::Packages::Conan::Package
    .for_projects(project)
    .with_name(params[:package_name])
    .with_version(params[:package_version])
    .with_conan_username(params[:package_username])
    .with_conan_channel(params[:package_channel])
    .order_created
    .not_pending_destruction
    .last
end

#package_file?(file_name) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 65

def package_file?(file_name)
  file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES)
end

#package_file_url(options) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 93

def package_file_url(options)
  case package_scope
  when :project
    expose_url(
      api_v4_projects_packages_conan_v1_files_package_path(
        options.merge(id: project.id)
      )
    )
  when :instance
    expose_url(
      api_v4_packages_conan_v1_files_package_path(options)
    )
  end
end

#package_scopeObject



331
332
333
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 331

def package_scope
  params[:id].present? ? :project : :instance
end

#package_upload_urlsObject



55
56
57
58
59
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 55

def package_upload_urls
  { upload_urls: file_names.select(&method(:package_file?)).index_with do |file_name|
                   build_package_file_upload_url(file_name)
                 end }
end

#present_download_urls(entity) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 25

def present_download_urls(entity)
  authorize_read_package!(project)

  presenter = ::Packages::Conan::PackagePresenter.new(
    package,
    current_user,
    project,
    conan_package_reference: declared(params)[:conan_package_reference],
    id: params[:id]
  )

  render_api_error!("No recipe manifest found", 404) if yield(presenter).empty?

  present presenter, with: entity
end

#present_package_download_urlsObject



41
42
43
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 41

def present_package_download_urls
  present_download_urls(::API::Entities::Packages::Conan::PackageManifest, &:package_urls)
end

#present_recipe_download_urlsObject



45
46
47
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 45

def present_recipe_download_urls
  present_download_urls(::API::Entities::Packages::Conan::RecipeManifest, &:recipe_urls)
end

#projectObject



127
128
129
130
131
132
133
134
135
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 127

def project
  case package_scope
  when :project
    user_project(action: :read_package)
  when :instance
    full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
    find_project!(full_path)
  end
end

#recipeObject



123
124
125
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 123

def recipe
  "%{package_name}/%{package_version}@%{package_username}/%{package_channel}" % params.symbolize_keys
end

#recipe_file?(file_name) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 61

def recipe_file?(file_name)
  file_name.in?(::Packages::Conan::FileMetadatum::RECIPE_FILES)
end

#recipe_file_url(options) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 108

def recipe_file_url(options)
  case package_scope
  when :project
    expose_url(
      api_v4_projects_packages_conan_v1_files_export_path(
        options.merge(id: project.id)
      )
    )
  when :instance
    expose_url(
      api_v4_packages_conan_v1_files_export_path(options)
    )
  end
end

#recipe_upload_urlsObject



49
50
51
52
53
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 49

def recipe_upload_urls
  { upload_urls: file_names.select(&method(:recipe_file?)).index_with do |file_name|
                   build_recipe_file_upload_url(file_name)
                 end }
end

#search_projectObject



335
336
337
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 335

def search_project
  project
end

#tokenObject



151
152
153
154
155
156
157
158
159
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 151

def token
  if find_personal_access_token
    ::Gitlab::ConanToken.from_personal_access_token(access_token_from_request, find_personal_access_token)
  elsif deploy_token_from_request
    ::Gitlab::ConanToken.from_deploy_token(deploy_token_from_request)
  else
    ::Gitlab::ConanToken.from_job(find_job_from_token)
  end
end

#track_push_package_eventObject



199
200
201
202
203
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 199

def track_push_package_event
  if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
    track_package_event('push_package', :conan, category: 'API::ConanPackages', project: project, namespace: project.namespace)
  end
end

#upload_package_file(file_type) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 229

def upload_package_file(file_type)
  authorize_upload!(project)
  bad_request!('File is too large') if project.actual_limits.exceeded?(:conan_max_file_size, params['file.size'].to_i)

  current_package = find_or_create_package

  track_push_package_event unless params[:file].empty_size?

  service_response = create_package_file_with_type(file_type, current_package)
  return unless service_response

  bad_request!(service_response.message) if service_response.error?

  service_response[:package_file]
rescue ObjectStorage::RemoteStoreError => e
  Gitlab::ErrorTracking.track_exception(e, file_name: params[:file_name], project_id: project.id)

  forbidden!
end

#url_options(file_name) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 82

def url_options(file_name)
  {
    package_name: params[:package_name],
    package_version: params[:package_version],
    package_username: params[:package_username],
    package_channel: params[:package_channel],
    file_name: file_name,
    recipe_revision: ::Packages::Conan::FileMetadatum::DEFAULT_REVISION
  }
end