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

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Instance Method Details

#access_token_from_requestObject


235
236
237
238
239
240
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 235

def access_token_from_request
  strong_memoize(:access_token_from_request) do
    find_personal_access_token_from_conan_jwt ||
      find_password_from_basic_auth
  end
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: params[:conan_package_reference],
    package_revision: ::Packages::Conan::FileMetadatum::DEFAULT_PACKAGE_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


201
202
203
204
205
206
207
208
209
210
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 201

def create_package_file_with_type(file_type, current_package)
  unless params[:file].size == 0 # rubocop: disable Style/ZeroLengthPredicate
    # 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


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

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


259
260
261
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 259

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


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

def download_package_file(file_type)
  authorize!(:read_package, project)

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

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

  present_carrierwave_file!(package_file.file)
end

#file_namesObject


193
194
195
196
197
198
199
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 193

def file_names
  json_payload = Gitlab::Json.parse(request.body.read)

  bad_request!(nil) unless json_payload.is_a?(Hash)

  json_payload.keys
end

#find_deploy_token_from_conan_jwtObject


280
281
282
283
284
285
286
287
288
289
290
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 280

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


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

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


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

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


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

def find_oauth_access_token
end

#find_or_create_packageObject


179
180
181
182
183
184
185
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 179

def find_or_create_package
  package || ::Packages::Conan::CreatePackageService.new(
    project,
    current_user,
    params.merge(build: current_authenticated_job)
  ).execute
end

#find_password_from_basic_authObject


242
243
244
245
246
247
248
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 242

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


229
230
231
232
233
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 229

def find_personal_access_token
  strong_memoize(:find_personal_access_token) do
    PersonalAccessToken.find_by_token(access_token_from_request)
  end
end

#find_personal_access_token_from_conan_jwtObject


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

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


250
251
252
253
254
255
256
257
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 250

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


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

def package
  strong_memoize(:package) do
    project.packages
      .conan
      .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
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


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

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?)).to_h do |file_name|
                   [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: 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::ConanPackage::ConanPackageManifest, &: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::ConanPackage::ConanRecipeManifest, &:recipe_urls)
end

#projectObject


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

def project
  strong_memoize(:project) do
    case package_scope
    when :project
      find_project!(params[:id])
    when :instance
      full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
      find_project!(full_path)
    end
  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?)).to_h do |file_name|
                   [file_name, build_recipe_file_upload_url(file_name)]
                 end }
end

#tokenObject


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

def token
  strong_memoize(:token) do
    token = nil
    token = ::Gitlab::ConanToken.from_personal_access_token(find_personal_access_token.user_id, access_token_from_request) if find_personal_access_token
    token = ::Gitlab::ConanToken.from_deploy_token(deploy_token_from_request) if deploy_token_from_request
    token = ::Gitlab::ConanToken.from_job(find_job_from_token) if find_job_from_token
    token
  end
end

#track_push_package_eventObject


187
188
189
190
191
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 187

def track_push_package_event
  if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
    track_package_event('push_package', :conan, category: 'API::ConanPackages', user: current_user, project: project, namespace: project.namespace)
  end
end

#upload_package_file(file_type) ⇒ Object


212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/api/helpers/packages/conan/api_helpers.rb', line 212

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

  create_package_file_with_type(file_type, current_package)
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_RECIPE_REVISION
  }
end